From ea05539fe01aafb2baf8825a4aa264d32db66216 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 5 Sep 2022 15:58:56 +0200 Subject: [PATCH 001/197] [feat] added new namada docker image, better wasm image --- .dockerignore | 1 + .github/workflows/docker.yml | 9 ++++-- Makefile | 9 ------ docker/namada-build/Dockerfile | 21 -------------- docker/namada-wasm/Dockerfile | 4 +-- docker/namada/Dockerfile | 51 ++++++++++++++++++++++++++++++++++ wasm/checksums.json | 30 ++++++++++---------- 7 files changed, 75 insertions(+), 50 deletions(-) create mode 100644 .dockerignore delete mode 100644 docker/namada-build/Dockerfile create mode 100644 docker/namada/Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..1de565933b --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7d288d544b..4d59976ec8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,6 +6,9 @@ on: tag: description: 'The docker image tag' required: true + push: + branches: + - main env: GIT_LFS_SKIP_SMUDGE: 1 @@ -25,7 +28,7 @@ jobs: path: ./docker/namada-wasm - name: Build and Push Namada docker image image: namada - path: ./docker/namada-build + path: ./docker/namada steps: - name: Checkout repo @@ -52,7 +55,7 @@ jobs: context: . file: ${{ matrix.make.path }}/Dockerfile push: true - tags: ghcr.io/${{ github.repository }}:${{ matrix.make.image }}-${{ github.event.inputs.tag }} + tags: ghcr.io/${{ github.repository }}:${{ matrix.make.image }}-${{ github.event.inputs.tag || steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - cache-from: type=registry,ref=${{ matrix.make.image }}-${{ github.event.inputs.tag }} + cache-from: type=registry,ref=${{ matrix.make.image }}-${{ github.event.inputs.tag || steps.meta.outputs.tags }} cache-to: type=inline diff --git a/Makefile b/Makefile index 3474f6c8e6..07eaf43e4a 100644 --- a/Makefile +++ b/Makefile @@ -31,15 +31,6 @@ check-release: package: build-release scripts/make-package.sh -build-release-image-docker: - docker build -t namada-build - < docker/namada-build/Dockerfile - -build-release-docker: build-release-image-docker - docker run --rm -v ${PWD}:/var/build namada-build make build-release - -package-docker: build-release-image-docker - docker run --rm -v ${PWD}:/var/build namada-build make package - check-wasm = $(cargo) check --target wasm32-unknown-unknown --manifest-path $(wasm)/Cargo.toml check: $(cargo) check && \ diff --git a/docker/namada-build/Dockerfile b/docker/namada-build/Dockerfile deleted file mode 100644 index 822a0d60c9..0000000000 --- a/docker/namada-build/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM ubuntu:20.04 -ARG RUST_VERSION=1.61.0 -WORKDIR /var/build -ENV DEBIAN_FRONTEND noninteractive -RUN apt-get update && apt-get install -y \ - build-essential \ - clang-tools-11 \ - curl \ - git \ - libssl-dev \ - pkg-config \ - && apt-get clean -RUN curl https://sh.rustup.rs -sSf | bash -s -- -y -ENV PATH="/root/.cargo/bin:${PATH}" -RUN rustup toolchain install $RUST_VERSION --component \ - cargo \ - rls \ - rustc \ - rust-analysis \ - rust-docs \ - rust-std \ diff --git a/docker/namada-wasm/Dockerfile b/docker/namada-wasm/Dockerfile index 2c7009d101..edeaa9fabb 100644 --- a/docker/namada-wasm/Dockerfile +++ b/docker/namada-wasm/Dockerfile @@ -1,7 +1,7 @@ # This docker is used for deterministic wasm builds # The version should be matching the version set in wasm/rust-toolchain.toml -FROM rust:1.61.0 +FROM rust:1.61.0-bullseye WORKDIR /__w/namada/namada @@ -10,7 +10,7 @@ RUN rustup toolchain install 1.61.0 --profile minimal RUN rustup target add wasm32-unknown-unknown # Download binaryen and extract wasm-opt -ADD https://github.com/WebAssembly/binaryen/releases/download/version_109/binaryen-version_109-x86_64-linux.tar.gz /tmp/binaryen.tar.gz +ADD https://github.com/WebAssembly/binaryen/releases/download/version_110/binaryen-version_110-x86_64-linux.tar.gz /tmp/binaryen.tar.gz RUN tar -xf /tmp/binaryen.tar.gz RUN mv binaryen-version_*/bin/wasm-opt /usr/local/bin RUN rm -rf binaryen-version_*/ /tmp/binaryen.tar.gz diff --git a/docker/namada/Dockerfile b/docker/namada/Dockerfile new file mode 100644 index 0000000000..2335ea9f46 --- /dev/null +++ b/docker/namada/Dockerfile @@ -0,0 +1,51 @@ +FROM lukemathwalker/cargo-chef:latest-rust-1.61.0 AS chef +WORKDIR /app + +FROM chef AS planner +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +FROM chef AS builder +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + build-essential \ + clang-tools-11 \ + git \ + libssl-dev \ + pkg-config \ + && apt-get clean + +COPY --from=planner /app/recipe.json recipe.json + +RUN cargo chef cook --release --recipe-path recipe.json +COPY . . +RUN make build-release + +FROM golang:1.18.0 as tendermint-builder +WORKDIR /app + +RUN git clone -b v0.1.1-abcipp --single-branch https://github.com/heliaxdev/tendermint.git && cd tendermint && make build + +FROM debian:bullseye-slim AS runtime +ENV ANOMA_BASE_DIR=/.anoma +ENV ANOMA_LOG_COLOR=false + +RUN apt-get update && apt-get install libcurl4-openssl-dev -y && apt-get clean + +RUN useradd --create-home namada +USER namada + +COPY --from=builder --chmod=0755 /app/target/release/namada /usr/local/bin +COPY --from=builder --chmod=0755 /app/target/release/namadan /usr/local/bin +COPY --from=builder --chmod=0755 /app/target/release/namadaw /usr/local/bin +COPY --from=builder --chmod=0755 /app/target/release/namadac /usr/local/bin +COPY --from=tendermint-builder --chmod=0755 /app/tendermint/build/tendermint /usr/local/bin + +EXPOSE 26656 +EXPOSE 26660 +EXPOSE 26659 +EXPOSE 26657 + +ENTRYPOINT ["/usr/local/bin/namada"] +CMD ["--help"] \ No newline at end of file diff --git a/wasm/checksums.json b/wasm/checksums.json index 898f6e9763..5c11cb779a 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.16097490afa7378c79e6216751b20796cde3a9026c34255c3f1e5ec5a4c9482e.wasm", - "tx_from_intent.wasm": "tx_from_intent.f8d1937b17a3abaf7ea595526c870b3d57ddef8e0c1bc96f8e0a448864b186c7.wasm", - "tx_ibc.wasm": "tx_ibc.378b10551c0b22c2c892d24e2676ee5160d654e2e53a50e7925e0f2c6321497b.wasm", - "tx_init_account.wasm": "tx_init_account.adab66c2b4d635e9c42133936aafb143363f91dddff2a60f94df504ffec951a6.wasm", - "tx_init_nft.wasm": "tx_init_nft.d1065ebd80ba6ea97f29bc2268becf9ba3ba2952641992464f3e9e868df17447.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.184131576a579f9ece96460d1eb20e5970fcd149b0527c8e56b711e5c535aa5f.wasm", - "tx_init_validator.wasm": "tx_init_validator.2990747d24d467b56e19724c5d13df826a3aab83f7e1bf26558dbdf44e260f8a.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.33db14dea4a03ff7508ca44f3ae956d83c0abceb3dae5be844668e54ac22b273.wasm", - "tx_transfer.wasm": "tx_transfer.a601d62296f56f6b4dabb0a2ad082478d195e667c7469f363bdfd5fe41349bd8.wasm", - "tx_unbond.wasm": "tx_unbond.014cbf5b0aa3ac592c0a6940dd502ec8569a3af4d12782e3a5931c15dc13042f.wasm", + "tx_bond.wasm": "tx_bond.280b1a45fe036b253888708cbe575c5d03effabd266e873f260dda972726bd0d.wasm", + "tx_from_intent.wasm": "tx_from_intent.7b890393e9a82c2b46a5833e5e40bc60d645f5dc0b4c5bb1a2975b932db7cfc3.wasm", + "tx_ibc.wasm": "tx_ibc.668e339cb3e334e80837a02c6615c343ffa127f3d0e759846ab6909fe9542648.wasm", + "tx_init_account.wasm": "tx_init_account.f2e8da54936e00aaf80874d526bf1a6add431070471563faa5cc61af5e6f9c6d.wasm", + "tx_init_nft.wasm": "tx_init_nft.b2822924522b7fa351cc9a3f28b64c46edf146de36c17a77157a788312496b56.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.2dde219a83b3316e3c31e570cee048a7c305ff1f8016ee2cbb95c4ebb69a7a06.wasm", + "tx_init_validator.wasm": "tx_init_validator.fc1232dc14645b4bab50ec702807470e6566ff1c195fed2817c3f96ffdcefa6b.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.ad9d9a825908ae3e68ef87a079cdac9b66216f6cb56a35d1b4771328aa5396d5.wasm", + "tx_transfer.wasm": "tx_transfer.619e8ef0e43150783094ee36c9fd239a90e26a42c18218fc27e59efa67e1e07d.wasm", + "tx_unbond.wasm": "tx_unbond.3cb54fbb19fe34b3ed7f32937076d7b974c2f587aa54709d298effab73ce8cba.wasm", "tx_update_vp.wasm": "tx_update_vp.83d4caeb5a9ca3009cd899810493a6b87b4c07fa9ed36f297db99dc881fb9a1c.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.bcb5280be9dfeed0a7650ba5e4a3cebc2c19b76780fd74dcb345be3da766b64a.wasm", - "tx_withdraw.wasm": "tx_withdraw.8fc0a3439ee9ae66047c520519877bc1f540e0cb02abfa31afa8cce8cd069b6f.wasm", - "vp_nft.wasm": "vp_nft.2c820c728d241b82bf0ed3c552ee9e7c046bceaa4f7b6f12d3236a1a3d7c1589.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.6e762f3fda8aa7a252e2b29a4a312db91ded062d6c18b8b489883733c89dc227.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.da9e9daee0e7999d4cee0be73c90b1f63efdde61fa5f707ae5d8a45c82614483.wasm", + "tx_withdraw.wasm": "tx_withdraw.4f1215b4c7dae3ee0a071e8ceef7dbec61847edde532a05ce9a066c46049ebb5.wasm", + "vp_nft.wasm": "vp_nft.c3f5446cc021136b96825e3641af499ab0623b81f2b5af7f52aecdcac560691c.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.451292425adb97c8eb970c5a05333d987bcfcd771f73adebca52b16141c9a150.wasm", "vp_token.wasm": "vp_token.c45cc3848f12fc47713702dc206d1312ad740a6bbee7f141556714f6f89d4985.wasm", - "vp_user.wasm": "vp_user.d6cd2f4b5bc26f96df6aa300fddf4d25e1656920d59896209bd54ae8d407ecde.wasm" + "vp_user.wasm": "vp_user.26ac06a3d15232d0f4e5482093c7aa16b4bfea4b1c2fa34946784eb42c9ae287.wasm" } \ No newline at end of file From 5596b5e99b7ed7a4aa59d32658d688664419a378 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Sep 2022 12:48:33 +0100 Subject: [PATCH 002/197] WIP: Shimming main --- Makefile | 24 ++++ apps/Cargo.toml | 37 ++++-- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 118 ++++++++++++------ .../node/ledger/shims/abcipp_shim_types.rs | 118 +++++++++++++++++- shared/Cargo.toml | 31 ++++- 5 files changed, 271 insertions(+), 57 deletions(-) diff --git a/Makefile b/Makefile index 3474f6c8e6..48265102ae 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 65d7d84eed..0d7f52da18 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -39,17 +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"] +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" @@ -105,10 +123,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" @@ -116,7 +138,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/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index ffe90c1d61..3c32454b3d 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -8,11 +8,16 @@ use futures::future::FutureExt; use tendermint_proto::abci::ResponseFinalizeBlock; use tokio::sync::mpsc::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; @@ -22,6 +27,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>, @@ -52,65 +60,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, + } + } + } } } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 53ab95f15c..bbfb51171b 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"} @@ -64,8 +81,10 @@ 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} +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.0" loupe = {version = "0.1.3", optional = true} @@ -87,8 +106,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" tracing = "0.1.30" wasmer = {version = "=2.2.0", optional = true} From e9cffb7b0020c6c526e23ef5530690945ad66fa7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Sep 2022 10:49:51 +0100 Subject: [PATCH 003/197] Add unit test filter to makefile targets --- Makefile | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 48265102ae..f8e7d1a58d 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 \ @@ -111,9 +107,35 @@ 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" \ + $(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 @@ -195,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 69d752a1812abbbaa5b5a5fba0cc1562b18c486a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Sep 2022 14:13:32 +0100 Subject: [PATCH 004/197] WIP: Shimming main --- apps/src/lib/mod.rs | 18 ++++++ .../lib/node/ledger/shell/finalize_block.rs | 5 +- apps/src/lib/node/ledger/shell/init_chain.rs | 6 +- apps/src/lib/node/ledger/shell/mod.rs | 64 +++++++------------ 4 files changed, 46 insertions(+), 47 deletions(-) diff --git a/apps/src/lib/mod.rs b/apps/src/lib/mod.rs index eca89896eb..8ba2e59855 100644 --- a/apps/src/lib/mod.rs +++ b/apps/src/lib/mod.rs @@ -18,3 +18,21 @@ pub mod wasm_loader; // Taken from . #[doc(inline)] pub use std; + +pub mod facade { + //! Facade module to reason about `abcipp` feature flag logic. + + #[cfg(not(feature = "abcipp"))] + pub use { + tendermint, tendermint_config, tendermint_proto, tendermint_rpc, + tower_abci, + }; + #[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, + }; +} diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 1f758a2f2e..d90aa2b9d4 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -10,10 +10,11 @@ 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 tendermint_proto::abci::Misbehavior as Evidence; -use tendermint_proto::crypto::PublicKey as TendermintPublicKey; +use namada::types::transaction::protocol::ProtocolTxType; 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 diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index a989338751..aeaacaf307 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -5,11 +5,11 @@ 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::*; +use crate::facade::tendermint_proto::abci; +use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; +use crate::facade::tendermint_proto::google::protobuf; use crate::wasm_loader; impl Shell diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index eb47160f9c..472d2f950a 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; mod prepare_proposal; @@ -44,19 +43,21 @@ 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::UnboundedSender; -use tower_abci::{request, response}; +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::{ + 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; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; @@ -68,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())) } @@ -502,24 +498,6 @@ where } } - /// INVARIANT: This method must be stateless. - pub fn extend_vote( - &self, - _req: request::ExtendVote, - ) -> response::ExtendVote { - Default::default() - } - - /// 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 { @@ -654,11 +632,13 @@ 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::*; + 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, }; @@ -768,10 +748,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(); From 84a647923389f0296cf84f9867da9534b4b5186f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Sep 2022 14:13:44 +0100 Subject: [PATCH 005/197] Shim PrepareProposal --- .../lib/node/ledger/shell/prepare_proposal.rs | 118 ++++++++++++------ 1 file changed, 81 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 7c15180d2c..41ccb7fe2c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,8 +1,19 @@ -//! Implementation of the [`PrepareProposal`] ABCI++ method for the Shell +//! Implementation of the `PrepareProposal` ABCI++ method for the Shell -use tendermint_proto::abci::TxRecord; +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 super::*; +use super::super::*; +use crate::facade::tendermint_proto::abci::RequestPrepareProposal; +#[cfg(feature = "abcipp")] +use crate::facade::tendermint_proto::abci::{ + tx_record::TxAction, TxRecord, +}; +use crate::node::ledger::shell::{process_tx, ShellMode}; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; impl Shell @@ -58,8 +69,11 @@ where }) .to_bytes() }) - .map(record::add) .collect(); + #[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 @@ -67,18 +81,24 @@ where vec![] }; - response::PrepareProposal { - tx_records: txs, - ..Default::default() + #[cfg(feature = "abcipp")] + { + response::PrepareProposal { + tx_records: txs, + ..Default::default() + } + } + #[cfg(not(feature = "abcipp"))] + { + response::PrepareProposal { txs } } } } /// 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::*; /// Keep this transaction in the proposal @@ -113,7 +133,6 @@ mod test_prepare_proposal { use namada::types::address::xan; use namada::types::storage::Epoch; use namada::types::transaction::Fee; - use tendermint_proto::abci::tx_record::TxAction; use super::*; use crate::node::ledger::shell::test_utils::{gen_keypair, TestShell}; @@ -133,10 +152,13 @@ mod test_prepare_proposal { max_tx_bytes: 0, ..Default::default() }; + #[cfg(feature = "abcipp")] assert_eq!( shell.prepare_proposal(req).tx_records, vec![record::remove(tx.to_bytes())] ); + #[cfg(not(feature = "abcipp"))] + assert!(shell.prepare_proposal(req).txs.is_empty()); } /// Test that if an error is encountered while @@ -170,15 +192,19 @@ mod test_prepare_proposal { ), ) .to_bytes(); + #[allow(clippy::redundant_clone)] let req = RequestPrepareProposal { 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)] ); + #[cfg(not(feature = "abcipp"))] + assert!(shell.prepare_proposal(req).txs.is_empty()); } /// Test that the decrypted txs are included @@ -229,32 +255,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() - .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); + } } } From 6d622898d6cb5efdb7862808bd6d79b923d3226d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Sep 2022 15:13:57 +0100 Subject: [PATCH 006/197] Shim ProcessProposal --- apps/src/lib/node/ledger/shell/process_proposal.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 25375244ba..22bd100342 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,11 +1,10 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell -use tendermint_proto::abci::response_process_proposal::ProposalStatus; -use tendermint_proto::abci::{ - ExecTxResult, RequestProcessProposal, ResponseProcessProposal, -}; use super::*; +use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; +use crate::facade::tendermint_proto::abci::{ExecTxResult, RequestProcessProposal}; +use crate::node::ledger::shims::abcipp_shim_types::shim::response::ProcessProposal; impl Shell where @@ -30,14 +29,13 @@ where ) -> ResponseProcessProposal { let tx_results = self.process_txs(&req.txs); - ResponseProcessProposal { + ProcessProposal { status: if tx_results.iter().any(|res| res.code > 3) { ProposalStatus::Reject as i32 } else { ProposalStatus::Accept as i32 }, tx_results, - ..Default::default() } } @@ -200,8 +198,6 @@ mod test_process_proposal { use namada::types::token::Amount; use namada::types::transaction::encrypted::EncryptedTx; use namada::types::transaction::{EncryptionKey, Fee}; - use tendermint_proto::abci::RequestInitChain; - use tendermint_proto::google::protobuf::Timestamp; use super::*; use crate::node::ledger::shell::test_utils::{ From c05d3202e601a8040297300f5abf1463594f1e3f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Sep 2022 15:18:38 +0100 Subject: [PATCH 007/197] Shim queries --- apps/src/lib/node/ledger/shell/queries.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index a643501d9e..8d779acff4 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -10,11 +10,11 @@ use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::{Key, PrefixValue}; use namada::types::token::{self, Amount}; -use tendermint_proto::crypto::{ProofOp, ProofOps}; -use tendermint_proto::google::protobuf; -use tendermint_proto::types::EvidenceParams; use super::*; +use crate::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; impl Shell @@ -211,7 +211,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); } From 1f0a56fdd47695ba348c6e0a9e0e34b84c7581bf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Sep 2022 15:25:12 +0100 Subject: [PATCH 008/197] WIP: Shimming main --- apps/src/lib/node/ledger/events.rs | 11 ++++++++-- .../lib/node/ledger/shell/prepare_proposal.rs | 4 +--- .../lib/node/ledger/shell/process_proposal.rs | 4 +++- apps/src/lib/node/ledger/tendermint_node.rs | 20 +++++++++++++------ shared/src/lib.rs | 7 +++++++ 5 files changed, 34 insertions(+), 12 deletions(-) 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/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 41ccb7fe2c..a35e8d6122 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -10,9 +10,7 @@ use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] -use crate::facade::tendermint_proto::abci::{ - tx_record::TxAction, TxRecord, -}; +use crate::facade::tendermint_proto::abci::{tx_record::TxAction, TxRecord}; use crate::node::ledger::shell::{process_tx, ShellMode}; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 22bd100342..af8f2a37b1 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 super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; -use crate::facade::tendermint_proto::abci::{ExecTxResult, RequestProcessProposal}; +use crate::facade::tendermint_proto::abci::{ + ExecTxResult, RequestProcessProposal, +}; use crate::node::ledger::shims::abcipp_shim_types::shim::response::ProcessProposal; impl Shell diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 25dd49ba63..5918ab814f 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -9,15 +9,17 @@ 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 +116,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 +382,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 +403,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) 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; From 7b8260be43c20ef088845ec3085df0a347348f1a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Sep 2022 15:34:22 +0100 Subject: [PATCH 009/197] Remove unused feature flag from Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f8e7d1a58d..d4b1afc951 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 586f3e940175b838cdaa83f3396d7da64640ab03 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Sep 2022 15:34:30 +0100 Subject: [PATCH 010/197] Cargo lock nonsense --- Cargo.lock | 380 +++++++++++++++++++++++++++++------------------------ 1 file changed, 210 insertions(+), 170 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eafd05b2ff..adc7cad4f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -519,12 +519,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" @@ -1107,12 +1101,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" @@ -1284,18 +1272,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" @@ -1326,16 +1302,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" @@ -1518,15 +1484,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" @@ -1689,18 +1646,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" @@ -1748,24 +1693,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" @@ -1961,16 +1888,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" @@ -2359,17 +2276,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" @@ -2546,16 +2452,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" @@ -2728,6 +2624,33 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "ibc" +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", + "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" @@ -2736,7 +2659,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", @@ -2747,14 +2670,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#99a761657a51f6e5f074f3217426903e53632934" +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" @@ -2764,7 +2700,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", ] @@ -2993,19 +2929,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" @@ -4040,8 +3963,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", @@ -4061,8 +3986,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", "tonic-build", @@ -4143,10 +4070,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", @@ -4155,7 +4086,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", @@ -5567,17 +5499,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" @@ -5877,18 +5798,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 = "security-framework" version = "2.3.1" @@ -6193,10 +6102,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" @@ -6471,6 +6376,34 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tendermint" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" +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" @@ -6482,12 +6415,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", @@ -6496,11 +6427,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" +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" @@ -6509,11 +6453,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" +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" @@ -6522,8 +6479,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" +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", ] @@ -6544,6 +6518,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" +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" @@ -6565,9 +6572,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", @@ -6577,6 +6584,21 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tendermint-testgen" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" +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" @@ -6588,7 +6610,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", ] @@ -7028,6 +7050,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" @@ -7037,7 +7077,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", From 6eafbb083297204374dde208d4e3ef3db0560bf7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Sep 2022 16:20:20 +0100 Subject: [PATCH 011/197] WIP: Shimming main --- apps/src/lib/node/ledger/shell/mod.rs | 5 ----- .../lib/node/ledger/shell/process_proposal.rs | 12 ++++-------- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 1 - .../lib/node/ledger/shims/abcipp_shim_types.rs | 18 +++++++----------- 4 files changed, 11 insertions(+), 25 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 472d2f950a..2e5d3d81fd 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -46,17 +46,12 @@ use num_traits::{FromPrimitive, ToPrimitive}; use thiserror::Error; 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::{ 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 af8f2a37b1..3414e4ce79 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -3,9 +3,7 @@ use super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; -use crate::facade::tendermint_proto::abci::{ - ExecTxResult, RequestProcessProposal, -}; +use crate::facade::tendermint_proto::abci::RequestProcessProposal; use crate::node::ledger::shims::abcipp_shim_types::shim::response::ProcessProposal; impl Shell @@ -28,7 +26,7 @@ where pub fn process_proposal( &self, req: RequestProcessProposal, - ) -> ResponseProcessProposal { + ) -> ProcessProposal { let tx_results = self.process_txs(&req.txs); ProcessProposal { @@ -42,13 +40,11 @@ where } /// Check all the given txs. - pub fn process_txs(&self, txs: &[Vec]) -> Vec { + pub fn process_txs(&self, txs: &[Vec]) -> Vec { let mut tx_queue_iter = self.storage.tx_queue.iter(); txs.iter() .map(|tx_bytes| { - ExecTxResult::from( - self.process_single_tx(tx_bytes, &mut tx_queue_iter), - ) + self.process_single_tx(tx_bytes, &mut tx_queue_iter) }) .collect() } diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 3c32454b3d..9b5ade1604 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -5,7 +5,6 @@ use std::pin::Pin; use std::task::{Context, Poll}; use futures::future::FutureExt; -use tendermint_proto::abci::ResponseFinalizeBlock; use tokio::sync::mpsc::UnboundedSender; use tower::Service; #[cfg(not(feature = "abcipp"))] 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..c1370fcf8d 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -267,20 +267,16 @@ 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, + 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 tendermint_proto_abcipp::abci::{ - Event as TmEvent, ExecTxResult, ResponseFinalizeBlock, - ValidatorUpdate, + use crate::facade::tendermint_proto::{ + abci::{ExecTxResult, ResponseFinalizeBlock}, + types::ConsensusParams, }; - #[cfg(feature = "abcipp")] - use tendermint_proto_abcipp::types::ConsensusParams; - - use super::*; use crate::node::ledger::events::Event; #[cfg(feature = "abcipp")] use crate::node::ledger::events::EventLevel; From a0e4df89f69a21abba9e6a0a68d0b16e074b7133 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Sep 2022 16:33:44 +0100 Subject: [PATCH 012/197] More fixes --- .../lib/node/ledger/shell/prepare_proposal.rs | 40 ++++++++++++------- 1 file changed, 26 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 a35e8d6122..157aee3c45 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -40,6 +40,7 @@ where // filter in half of the new txs from Tendermint, only keeping // wrappers let number_of_new_txs = 1 + req.txs.len() / 2; + #[cfg(feature = "abcipp")] let mut txs: Vec = req .txs .into_iter() @@ -54,24 +55,35 @@ where } }) .collect(); + #[cfg(not(feature = "abcipp"))] + let mut txs: Vec = req + .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(); // 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() + let decrypted_txs = self.storage.tx_queue.iter().map(|tx| { + Tx::from(match tx.decrypt(privkey) { + Ok(tx) => DecryptedTx::Decrypted(tx), + _ => DecryptedTx::Undecryptable(tx.clone()), }) - .collect(); + .to_bytes() + }); #[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: Vec<_> = + decrypted_txs.map(record::add).collect(); + #[cfg(not(feature = "abcipp"))] + let mut decrypted_txs: Vec<_> = decrypted_txs.collect(); txs.append(&mut decrypted_txs); txs From c802f3876948851c534c4ee456e278b68b6771ed Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Tue, 6 Sep 2022 11:21:21 +0200 Subject: [PATCH 013/197] [feat] update wasm image, improve docker ci --- .github/workflows/docker.yml | 19 ++++++++++++------- docker/namada-wasm/Dockerfile | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4d59976ec8..5898d62e6b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -9,15 +9,20 @@ on: push: branches: - main + tags: + - "v[0-9]+.[0-9]+.[0-9]+" env: GIT_LFS_SKIP_SMUDGE: 1 +permissions: + contents: write + packages: write + + jobs: docker: runs-on: ${{ matrix.os }} - permissions: - packages: write strategy: fail-fast: false matrix: @@ -39,9 +44,9 @@ jobs: uses: docker/setup-buildx-action@v2 - name: Docker meta id: meta - uses: docker/metadata-action@v3 + uses: docker/metadata-action@v4 with: - images: ghcr.io/anoma/namada + images: ghcr.io/${{ github.repository_owner }}/namada github-token: ${{ secrets.GITHUB_TOKEN }} - name: Login to GHCR uses: docker/login-action@v2 @@ -55,7 +60,7 @@ jobs: context: . file: ${{ matrix.make.path }}/Dockerfile push: true - tags: ghcr.io/${{ github.repository }}:${{ matrix.make.image }}-${{ github.event.inputs.tag || steps.meta.outputs.tags }} + tags: ghcr.io/${{ github.repository_owner }}/namada:${{ matrix.make.image }}-${{ github.event.inputs.tag || steps.meta.outputs.version }} labels: ${{ steps.meta.outputs.labels }} - cache-from: type=registry,ref=${{ matrix.make.image }}-${{ github.event.inputs.tag || steps.meta.outputs.tags }} - cache-to: type=inline + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/docker/namada-wasm/Dockerfile b/docker/namada-wasm/Dockerfile index edeaa9fabb..5b47fa35f4 100644 --- a/docker/namada-wasm/Dockerfile +++ b/docker/namada-wasm/Dockerfile @@ -13,4 +13,4 @@ RUN rustup target add wasm32-unknown-unknown ADD https://github.com/WebAssembly/binaryen/releases/download/version_110/binaryen-version_110-x86_64-linux.tar.gz /tmp/binaryen.tar.gz RUN tar -xf /tmp/binaryen.tar.gz RUN mv binaryen-version_*/bin/wasm-opt /usr/local/bin -RUN rm -rf binaryen-version_*/ /tmp/binaryen.tar.gz +RUN rm -rf binaryen-version_*/ /tmp/binaryen.tar.gz \ No newline at end of file From 4a1ab4e4324ecb52992588e97d767224c750b0d6 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 7 Sep 2022 09:48:41 +0200 Subject: [PATCH 014/197] [feat] improve .dockerignore --- .dockerignore | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 1de565933b..221534df6b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,9 @@ -target \ No newline at end of file +target +.changelog +.github +.vscode +audit +test +wasm_for_tests +genesis +docker \ No newline at end of file From 7c1550cdbc2c89045cfeb6d0b016a711eb1a86f8 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 7 Sep 2022 10:19:46 +0200 Subject: [PATCH 015/197] [fix] cron deps --- .github/workflows/cron.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index 86f9940a77..51e19208e3 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -63,8 +63,8 @@ jobs: - name: ${{ matrix.make.name }} working-directory: ./.github/workflows/scripts run: | - pip3 install github3.py >/dev/null 2>&1 pip3 install -Iv cryptography==37.0.4 >/dev/null 2>&1 + pip3 install github3.py >/dev/null 2>&1 python3 ${{ matrix.make.command }}.py env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From aeeb94e730db194eb325301eb94488a87ddc7734 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Sep 2022 09:35:46 +0100 Subject: [PATCH 016/197] Add ConsensusParams import --- apps/src/lib/node/ledger/shell/finalize_block.rs | 1 + apps/src/lib/node/ledger/shell/init_chain.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index d90aa2b9d4..1938802e66 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -15,6 +15,7 @@ use namada::types::transaction::protocol::ProtocolTxType; use super::*; use crate::facade::tendermint_proto::abci::Misbehavior as Evidence; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; +use crate::facade::tendermint_proto::types::ConsensusParams; use crate::node::ledger::events::EventType; impl Shell diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index aeaacaf307..cf7236e5e0 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -9,6 +9,7 @@ use sha2::{Digest, Sha256}; use super::*; use crate::facade::tendermint_proto::abci; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; +use crate::facade::tendermint_proto::types::ConsensusParams; use crate::facade::tendermint_proto::google::protobuf; use crate::wasm_loader; From 9df7ab5583bbc7dc7aa1c69f1a73b5bdf498b03b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Sep 2022 09:37:39 +0100 Subject: [PATCH 017/197] Find and replace Tendermint imports --- 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_websocket_client.rs | 9 +++++---- 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/mod.rs | 2 +- apps/src/lib/node/ledger/rpc.rs | 3 ++- apps/src/lib/node/ledger/shell/init_chain.rs | 2 +- apps/src/lib/node/matchmaker.rs | 4 ++-- shared/src/ledger/storage/merkle_tree.rs | 2 +- shared/src/ledger/storage/mod.rs | 2 +- shared/src/types/hash.rs | 5 +++-- shared/src/types/time.rs | 2 +- 17 files changed, 38 insertions(+), 34 deletions(-) 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 34652ac825..ea361ed2f1 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_websocket_client.rs b/apps/src/lib/client/tendermint_websocket_client.rs index d5af3bcee1..02b357bc01 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:?}")] 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 1d41ebbc77..b3a6141ee6 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 987077d5cd..e4788b4ae6 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/mod.rs b/apps/src/lib/node/ledger/mod.rs index f71e73ea74..ce3b96485a 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -18,13 +18,13 @@ 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 tower::ServiceBuilder; 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::facade::tendermint_proto::abci::CheckTxType; use crate::node::ledger::broadcaster::Broadcaster; use crate::node::ledger::shell::{Error, MempoolTxType, Shell}; use crate::node::ledger::shims::abcipp_shim::AbcippShim; 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/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index cf7236e5e0..66d47c856b 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -9,8 +9,8 @@ use sha2::{Digest, Sha256}; use super::*; use crate::facade::tendermint_proto::abci; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; -use crate::facade::tendermint_proto::types::ConsensusParams; use crate::facade::tendermint_proto::google::protobuf; +use crate::facade::tendermint_proto::types::ConsensusParams; use crate::wasm_loader; impl Shell 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/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 0b3d19a742..d60602df9e 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")] diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index e2fed30379..718cecee83 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; 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 6724815d62db37cb4945e5a718eeba8590b79829 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Sep 2022 10:17:45 +0100 Subject: [PATCH 018/197] Add Cargo.lock file diffs --- 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 | 186 +++----------------------- 4 files changed, 44 insertions(+), 628 deletions(-) diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 4e85d68f2c..b929dda16f 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" @@ -409,12 +403,6 @@ dependencies = [ "syn", ] -[[package]] -name = "const-oid" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" - [[package]] name = "constant_time_eq" version = "0.1.5" @@ -549,18 +537,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" -[[package]] -name = "crypto-bigint" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" -dependencies = [ - "generic-array", - "rand_core 0.6.3", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.3" @@ -571,16 +547,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -642,15 +608,6 @@ dependencies = [ "syn", ] -[[package]] -name = "der" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" -dependencies = [ - "const-oid", -] - [[package]] name = "derivative" version = "2.2.0" @@ -719,18 +676,6 @@ dependencies = [ "memmap2", ] -[[package]] -name = "ecdsa" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" -dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", -] - [[package]] name = "ed25519" version = "1.4.0" @@ -773,24 +718,6 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -[[package]] -name = "elliptic-curve" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" -dependencies = [ - "base16ct", - "crypto-bigint", - "der", - "ff", - "generic-array", - "group", - "rand_core 0.6.3", - "sec1", - "subtle", - "zeroize", -] - [[package]] name = "enum-iterator" version = "0.7.0" @@ -870,16 +797,6 @@ dependencies = [ "serde_bytes", ] -[[package]] -name = "ff" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" -dependencies = [ - "rand_core 0.6.3", - "subtle", -] - [[package]] name = "fixedbitset" version = "0.4.1" @@ -1011,17 +928,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" @@ -1094,16 +1000,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" @@ -1177,7 +1073,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#99a761657a51f6e5f074f3217426903e53632934" dependencies = [ "bytes", "derive_more", @@ -1204,7 +1100,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#99a761657a51f6e5f074f3217426903e53632934" dependencies = [ "bytes", "prost", @@ -1297,19 +1193,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" @@ -2132,17 +2015,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" @@ -2303,18 +2175,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" @@ -2434,10 +2294,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" @@ -2563,7 +2419,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "async-trait", "bytes", @@ -2571,12 +2427,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2593,7 +2447,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "flex-error", "serde", @@ -2606,7 +2460,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "derive_more", "flex-error", @@ -2619,7 +2473,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "bytes", "flex-error", @@ -2636,7 +2490,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "bytes", "flex-error", @@ -2660,7 +2514,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 25070a9319..17f07177d3 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" @@ -409,12 +403,6 @@ dependencies = [ "syn", ] -[[package]] -name = "const-oid" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" - [[package]] name = "constant_time_eq" version = "0.1.5" @@ -549,18 +537,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" -[[package]] -name = "crypto-bigint" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" -dependencies = [ - "generic-array", - "rand_core 0.6.3", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.3" @@ -571,16 +547,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -642,15 +608,6 @@ dependencies = [ "syn", ] -[[package]] -name = "der" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" -dependencies = [ - "const-oid", -] - [[package]] name = "derivative" version = "2.2.0" @@ -719,18 +676,6 @@ dependencies = [ "memmap2", ] -[[package]] -name = "ecdsa" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" -dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", -] - [[package]] name = "ed25519" version = "1.4.0" @@ -773,24 +718,6 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -[[package]] -name = "elliptic-curve" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" -dependencies = [ - "base16ct", - "crypto-bigint", - "der", - "ff", - "generic-array", - "group", - "rand_core 0.6.3", - "sec1", - "subtle", - "zeroize", -] - [[package]] name = "enum-iterator" version = "0.7.0" @@ -870,16 +797,6 @@ dependencies = [ "serde_bytes", ] -[[package]] -name = "ff" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" -dependencies = [ - "rand_core 0.6.3", - "subtle", -] - [[package]] name = "fixedbitset" version = "0.4.1" @@ -1011,17 +928,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" @@ -1094,16 +1000,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" @@ -1177,7 +1073,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#99a761657a51f6e5f074f3217426903e53632934" dependencies = [ "bytes", "derive_more", @@ -1204,7 +1100,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#99a761657a51f6e5f074f3217426903e53632934" dependencies = [ "bytes", "prost", @@ -1297,19 +1193,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" @@ -2132,17 +2015,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" @@ -2303,18 +2175,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" @@ -2434,10 +2294,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" @@ -2563,7 +2419,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "async-trait", "bytes", @@ -2571,12 +2427,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2593,7 +2447,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "flex-error", "serde", @@ -2606,7 +2460,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "derive_more", "flex-error", @@ -2619,7 +2473,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "bytes", "flex-error", @@ -2636,7 +2490,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "bytes", "flex-error", @@ -2660,7 +2514,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 269535735e..654f177a08 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -205,12 +205,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - [[package]] name = "base64" version = "0.13.0" @@ -409,12 +403,6 @@ dependencies = [ "syn", ] -[[package]] -name = "const-oid" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" - [[package]] name = "constant_time_eq" version = "0.1.5" @@ -549,18 +537,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" -[[package]] -name = "crypto-bigint" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" -dependencies = [ - "generic-array", - "rand_core 0.6.3", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.3" @@ -571,16 +547,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -642,15 +608,6 @@ dependencies = [ "syn", ] -[[package]] -name = "der" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" -dependencies = [ - "const-oid", -] - [[package]] name = "derivative" version = "2.2.0" @@ -719,18 +676,6 @@ dependencies = [ "memmap2", ] -[[package]] -name = "ecdsa" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" -dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", -] - [[package]] name = "ed25519" version = "1.4.0" @@ -773,24 +718,6 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -[[package]] -name = "elliptic-curve" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" -dependencies = [ - "base16ct", - "crypto-bigint", - "der", - "ff", - "generic-array", - "group", - "rand_core 0.6.3", - "sec1", - "subtle", - "zeroize", -] - [[package]] name = "enum-iterator" version = "0.7.0" @@ -870,16 +797,6 @@ dependencies = [ "serde_bytes", ] -[[package]] -name = "ff" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" -dependencies = [ - "rand_core 0.6.3", - "subtle", -] - [[package]] name = "fixedbitset" version = "0.4.1" @@ -1011,17 +928,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" @@ -1094,16 +1000,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" @@ -1177,7 +1073,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#99a761657a51f6e5f074f3217426903e53632934" dependencies = [ "bytes", "derive_more", @@ -1204,7 +1100,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#99a761657a51f6e5f074f3217426903e53632934" dependencies = [ "bytes", "prost", @@ -1297,19 +1193,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" @@ -2158,17 +2041,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" @@ -2329,18 +2201,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" @@ -2460,10 +2320,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" @@ -2589,7 +2445,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "async-trait", "bytes", @@ -2597,12 +2453,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2619,7 +2473,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "flex-error", "serde", @@ -2632,7 +2486,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "derive_more", "flex-error", @@ -2645,7 +2499,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "bytes", "flex-error", @@ -2662,7 +2516,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "bytes", "flex-error", @@ -2686,7 +2540,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 9df5195b2f..a3a1b85460 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" @@ -409,12 +403,6 @@ dependencies = [ "syn", ] -[[package]] -name = "const-oid" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" - [[package]] name = "constant_time_eq" version = "0.1.5" @@ -550,18 +538,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" @@ -572,16 +548,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" @@ -643,15 +609,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" @@ -720,18 +677,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" @@ -774,24 +719,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" @@ -871,16 +798,6 @@ dependencies = [ "serde_bytes", ] -[[package]] -name = "ff" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" -dependencies = [ - "rand_core 0.6.3", - "subtle", -] - [[package]] name = "fixedbitset" version = "0.4.1" @@ -1012,17 +929,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" @@ -1104,16 +1010,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" @@ -1187,7 +1083,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#99a761657a51f6e5f074f3217426903e53632934" dependencies = [ "bytes", "derive_more", @@ -1214,7 +1110,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#99a761657a51f6e5f074f3217426903e53632934" dependencies = [ "bytes", "prost", @@ -1307,19 +1203,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" @@ -1357,7 +1240,7 @@ dependencies = [ [[package]] name = "libsecp256k1" version = "0.7.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", @@ -1373,7 +1256,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1383,7 +1266,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1391,7 +1274,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1527,7 +1410,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1576,7 +1459,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1584,7 +1467,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -1593,7 +1476,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1611,7 +1494,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1619,7 +1502,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -1629,7 +1512,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1637,7 +1520,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "getrandom", @@ -2164,17 +2047,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" @@ -2335,18 +2207,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" @@ -2466,10 +2326,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" @@ -2595,7 +2451,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "async-trait", "bytes", @@ -2603,12 +2459,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2625,7 +2479,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "flex-error", "serde", @@ -2638,7 +2492,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "derive_more", "flex-error", @@ -2651,7 +2505,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "bytes", "flex-error", @@ -2668,7 +2522,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "bytes", "flex-error", @@ -2692,7 +2546,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#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "ed25519-dalek", "gumdrop", From 44d52dd9efc0b3126eb65227fc015aaa199bdb23 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Sep 2022 10:17:57 +0100 Subject: [PATCH 019/197] Fix abciplus --- apps/src/lib/cli.rs | 4 ++-- apps/src/lib/client/tendermint_rpc_types.rs | 5 ++--- apps/src/lib/client/tendermint_websocket_client.rs | 6 +++--- apps/src/lib/node/ledger/events.rs | 6 ------ apps/src/lib/node/ledger/mod.rs | 4 +++- apps/src/lib/node/ledger/shell/finalize_block.rs | 1 - apps/src/lib/node/ledger/shell/mod.rs | 2 +- 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/shims/abcipp_shim.rs | 14 +++++++------- 10 files changed, 22 insertions(+), 26 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/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 02b357bc01..6f70464e58 100644 --- a/apps/src/lib/client/tendermint_websocket_client.rs +++ b/apps/src/lib/client/tendermint_websocket_client.rs @@ -50,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/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/mod.rs b/apps/src/lib/node/ledger/mod.rs index ce3b96485a..58a25202f3 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -19,12 +19,12 @@ use namada::types::storage::Key; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; use tower::ServiceBuilder; -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::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; @@ -105,9 +105,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 1938802e66..2e8f02a76c 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -10,7 +10,6 @@ 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::transaction::protocol::ProtocolTxType; use super::*; use crate::facade::tendermint_proto::abci::Misbehavior as Evidence; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2e5d3d81fd..a89d0090ea 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -44,7 +44,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::UnboundedSender; use super::rpc; use crate::config::{genesis, TendermintMode}; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 157aee3c45..c8bf0b654a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -2,7 +2,6 @@ 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}; @@ -140,9 +139,10 @@ pub(super) mod record { #[cfg(test)] mod test_prepare_proposal { + use borsh::BorshSerialize; use namada::types::address::xan; use namada::types::storage::Epoch; - use namada::types::transaction::Fee; + use namada::types::transaction::{Fee, WrapperTx}; use super::*; use crate::node::ledger::shell::test_utils::{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 3414e4ce79..5572e00495 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -198,6 +198,8 @@ mod test_process_proposal { use namada::types::transaction::{EncryptionKey, Fee}; use super::*; + use crate::facade::tendermint_proto::abci::RequestInitChain; + use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::node::ledger::shell::test_utils::{ gen_keypair, ProcessProposal, TestError, TestShell, }; diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 9b5ade1604..a2492e0f65 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -5,20 +5,20 @@ use std::pin::Pin; use std::task::{Context, Poll}; use futures::future::FutureExt; +#[cfg(not(feature = "abcipp"))] +use namada::types::hash::Hash; +use namada::types::storage::BlockHash; +use namada::types::transaction::hash_tx; use tokio::sync::mpsc::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 From 6b5311342a86a57a13a37edb7218ac949d80bdc3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Sep 2022 10:23:04 +0100 Subject: [PATCH 020/197] Add missing shell methods --- apps/src/lib/node/ledger/shell/mod.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index a89d0090ea..016b65d3c9 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -493,6 +493,24 @@ where } } + /// INVARIANT: This method must be stateless. + pub fn extend_vote( + &self, + _req: request::ExtendVote, + ) -> response::ExtendVote { + Default::default() + } + + /// 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 { From 26154f1e35de4fe31833020e2bcbe56b85e15348 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Sep 2022 10:31:37 +0100 Subject: [PATCH 021/197] Fix abcipp --- apps/src/lib/client/tendermint_rpc_types.rs | 2 +- apps/src/lib/client/tm_jsonrpc_client.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 4 ++++ shared/src/ledger/ibc/handler.rs | 6 +++--- vm_env/Cargo.toml | 12 ++++++++++-- 5 files changed, 19 insertions(+), 7 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/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 016b65d3c9..29d6cc7d39 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -48,6 +48,8 @@ use tokio::sync::mpsc::UnboundedSender; use super::rpc; use crate::config::{genesis, TendermintMode}; +#[cfg(feature = "abcipp")] +use crate::facade::tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; use crate::facade::tendermint_proto::abci::{ Misbehavior as Evidence, MisbehaviorType as EvidenceType, ValidatorUpdate, }; @@ -494,6 +496,7 @@ where } /// INVARIANT: This method must be stateless. + #[cfg(feature = "abcipp")] pub fn extend_vote( &self, _req: request::ExtendVote, @@ -502,6 +505,7 @@ where } /// INVARIANT: This method must be stateless. + #[cfg(feature = "abcipp")] pub fn verify_vote_extension( &self, _req: request::VerifyVoteExtension, 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(), 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 a828214802595a18fed96584ef5897ce48567ba9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Sep 2022 10:34:06 +0100 Subject: [PATCH 022/197] Remaining fixes --- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 2 ++ apps/src/lib/node/ledger/shims/abcipp_shim_types.rs | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index a2492e0f65..a5bd420bec 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -7,7 +7,9 @@ use std::task::{Context, Poll}; use futures::future::FutureExt; #[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; 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 c1370fcf8d..5d9f2c420c 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -27,8 +27,7 @@ pub mod shim { ResponseCheckTx, ResponseCommit, ResponseEcho, ResponseExtendVote, ResponseFlush, ResponseInfo, ResponseInitChain, ResponseListSnapshots, ResponseLoadSnapshotChunk, ResponseOfferSnapshot, - ResponsePrepareProposal, ResponseProcessProposal, ResponseQuery, - ResponseVerifyVoteExtension, + ResponsePrepareProposal, ResponseQuery, ResponseVerifyVoteExtension, }; use thiserror::Error; From 3e5673ac2cca99ef7e91f90d125fc4db3549e737 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 6 Sep 2022 14:28:05 +0200 Subject: [PATCH 023/197] Removed tm events log, put websocket back everywhere in client --- 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 | 110 +++-- apps/src/lib/node/ledger/tendermint_node.rs | 8 - 7 files changed, 532 insertions(+), 539 deletions(-) delete mode 100644 apps/src/lib/client/tm_jsonrpc_client.rs diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 0d7f52da18..f128d58667 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -79,7 +79,6 @@ byteorder = "1.4.2" 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" 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 b3a6141ee6..92f801fe06 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"; @@ -1120,7 +1120,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, @@ -1158,7 +1158,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 { @@ -1177,56 +1177,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 00fc54154eca89f352e73cdeecb814c6aaf67404 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Sep 2022 11:00:25 +0100 Subject: [PATCH 024/197] Add Cargo.lock changes --- Cargo.lock | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index adc7cad4f8..af1eb8ab56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1353,36 +1353,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" @@ -4024,7 +3994,6 @@ dependencies = [ "clap 3.0.0-beta.2", "color-eyre", "config", - "curl", "derivative", "directories", "ed25519-consensus", From b8eccf3d04fefa61203a5fa6b1a534156e256313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 8 Sep 2022 17:11:57 +0200 Subject: [PATCH 025/197] deps: replace hex with data-encoding --- Cargo.lock | 6 +++--- apps/Cargo.toml | 2 +- apps/src/lib/client/rpc.rs | 3 ++- apps/src/lib/config/genesis.rs | 12 ++++++------ apps/src/lib/node/gossip/p2p/identity.rs | 6 ++++-- apps/src/lib/wallet/keys.rs | 7 ++++--- apps/src/lib/wasm_loader/mod.rs | 4 ++-- shared/Cargo.toml | 2 +- shared/src/proto/mod.rs | 5 +++-- shared/src/proto/types.rs | 3 ++- shared/src/types/key/common.rs | 13 +++++++++---- shared/src/types/key/dkg_session_keys.rs | 7 +++++-- shared/src/types/key/ed25519.rs | 13 +++++++++---- shared/src/types/key/mod.rs | 6 +++--- tests/Cargo.toml | 2 +- tests/src/e2e/ledger_tests.rs | 3 ++- wasm/wasm_source/Cargo.lock | 8 +++++++- wasm_for_tests/wasm_source/Cargo.lock | 8 +++++++- 18 files changed, 71 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7fce8b64d..3aef73a09a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3843,12 +3843,12 @@ dependencies = [ "byte-unit", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ferveo", "ferveo-common", "group-threshold-cryptography", - "hex", "ibc", "ibc-proto", "ics23", @@ -3905,6 +3905,7 @@ dependencies = [ "color-eyre", "config", "curl", + "data-encoding", "derivative", "directories", "ed25519-consensus", @@ -3915,7 +3916,6 @@ dependencies = [ "flate2", "futures 0.3.21", "git2", - "hex", "itertools 0.10.3", "jsonpath_lib", "libc", @@ -4007,13 +4007,13 @@ dependencies = [ "chrono", "color-eyre", "concat-idents", + "data-encoding", "derivative", "escargot", "expectrl", "eyre", "file-serve", "fs_extra", - "hex", "itertools 0.10.3", "libp2p", "namada", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 7a4dffac76..3fcd3af977 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -62,6 +62,7 @@ clap = {git = "https://github.com/clap-rs/clap/", tag = "v3.0.0-beta.2", default color-eyre = "0.5.10" config = "0.11.0" curl = "0.4.43" +data-encoding = "2.3.2" derivative = "2.2.0" directories = "4.0.1" ed25519-consensus = "1.2.0" @@ -71,7 +72,6 @@ eyre = "0.6.5" flate2 = "1.0.22" file-lock = "2.0.2" futures = "0.3" -hex = "0.4.3" itertools = "0.10.1" jsonpath_lib = "0.3.0" libc = "0.2.97" diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 34652ac825..67e81c1588 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -11,6 +11,7 @@ use async_std::fs::{self}; use async_std::path::PathBuf; use async_std::prelude::*; use borsh::BorshDeserialize; +use data_encoding::HEXLOWER; use itertools::Itertools; use namada::ledger::governance::storage as gov_storage; use namada::ledger::governance::utils::Votes; @@ -81,7 +82,7 @@ pub async fn query_raw_bytes(_ctx: Context, args: args::QueryRawBytes) { .unwrap(); match response.code { Code::Ok => { - println!("{}", hex::encode(&response.value)); + println!("{}", HEXLOWER.encode(&response.value)); } Code::Err(err) => { eprintln!( diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 5bd3dc803f..9c1fff3dcc 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -26,7 +26,7 @@ pub mod genesis_config { use std::path::Path; use std::str::FromStr; - use hex; + use data_encoding::HEXLOWER; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::{EpochDuration, Parameters}; use namada::ledger::pos::types::BasisPoints; @@ -50,12 +50,12 @@ pub mod genesis_config { impl HexString { pub fn to_bytes(&self) -> Result, HexKeyError> { - let bytes = hex::decode(&self.0)?; + let bytes = HEXLOWER.decode(self.0.as_ref())?; Ok(bytes) } pub fn to_sha256_bytes(&self) -> Result<[u8; 32], HexKeyError> { - let bytes = hex::decode(&self.0)?; + let bytes = HEXLOWER.decode(self.0.as_ref())?; let slice = bytes.as_slice(); let array: [u8; 32] = slice.try_into()?; Ok(array) @@ -76,15 +76,15 @@ pub mod genesis_config { #[derive(Error, Debug)] pub enum HexKeyError { #[error("Invalid hex string: {0:?}")] - InvalidHexString(hex::FromHexError), + InvalidHexString(data_encoding::DecodeError), #[error("Invalid sha256 checksum: {0}")] InvalidSha256(TryFromSliceError), #[error("Invalid public key: {0}")] InvalidPublicKey(ParsePublicKeyError), } - impl From for HexKeyError { - fn from(err: hex::FromHexError) -> Self { + impl From for HexKeyError { + fn from(err: data_encoding::DecodeError) -> Self { Self::InvalidHexString(err) } } diff --git a/apps/src/lib/node/gossip/p2p/identity.rs b/apps/src/lib/node/gossip/p2p/identity.rs index 42442054c8..3227060014 100644 --- a/apps/src/lib/node/gossip/p2p/identity.rs +++ b/apps/src/lib/node/gossip/p2p/identity.rs @@ -21,6 +21,7 @@ pub struct Identity { // TODO this is needed because libp2p does not export ed255519 serde // feature maybe a MR for libp2p to export theses functions ? mod keypair_serde { + use data_encoding::HEXLOWER; use libp2p::identity::ed25519::Keypair; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -33,7 +34,7 @@ mod keypair_serde { S: Serializer, { let bytes = value.encode(); - let string = hex::encode(&bytes[..]); + let string = HEXLOWER.encode(&bytes[..]); string.serialize(serializer) } pub fn deserialize<'d, D>(deserializer: D) -> Result @@ -41,7 +42,8 @@ mod keypair_serde { D: Deserializer<'d>, { let string = String::deserialize(deserializer)?; - let mut bytes = hex::decode(&string).map_err(Error::custom)?; + let mut bytes = + HEXLOWER.decode(string.as_ref()).map_err(Error::custom)?; Keypair::decode(bytes.as_mut()).map_err(Error::custom) } } diff --git a/apps/src/lib/wallet/keys.rs b/apps/src/lib/wallet/keys.rs index 1c521e7515..e922c7df5a 100644 --- a/apps/src/lib/wallet/keys.rs +++ b/apps/src/lib/wallet/keys.rs @@ -5,6 +5,7 @@ use std::rc::Rc; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; +use data_encoding::HEXLOWER; use namada::types::key::*; use orion::{aead, kdf}; use serde::{Deserialize, Serialize}; @@ -108,15 +109,15 @@ pub struct EncryptedKeypair(Vec); impl Display for EncryptedKeypair { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0)) + write!(f, "{}", HEXLOWER.encode(self.0.as_ref())) } } impl FromStr for EncryptedKeypair { - type Err = hex::FromHexError; + type Err = data_encoding::DecodeError; fn from_str(s: &str) -> Result { - hex::decode(s).map(Self) + HEXLOWER.decode(s.as_ref()).map(Self) } } diff --git a/apps/src/lib/wasm_loader/mod.rs b/apps/src/lib/wasm_loader/mod.rs index b6cb424457..e41efdbf77 100644 --- a/apps/src/lib/wasm_loader/mod.rs +++ b/apps/src/lib/wasm_loader/mod.rs @@ -4,8 +4,8 @@ use std::collections::HashMap; use std::fs; use std::path::Path; +use data_encoding::HEXLOWER; use futures::future::join_all; -use hex; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -144,7 +144,7 @@ pub async fn pre_fetch_wasm(wasm_directory: impl AsRef) { Ok(bytes) => { let mut hasher = Sha256::new(); hasher.update(bytes); - let result = hex::encode(hasher.finalize()); + let result = HEXLOWER.encode(&hasher.finalize()); let derived_name = format!( "{}.{}.wasm", &name.split('.').collect::>()[0], diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 241fd034da..7bff772557 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -52,11 +52,11 @@ borsh = "0.9.0" chrono = "0.4.19" # Using unreleased commit on top of version 0.5.0 that adds Sync to the CLruCache clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} +data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} -hex = "0.4.3" tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo"} # TODO using the same version of tendermint-rs as we do here. ibc = {git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc", default-features = false} diff --git a/shared/src/proto/mod.rs b/shared/src/proto/mod.rs index aa971f0b96..1ee5da63c8 100644 --- a/shared/src/proto/mod.rs +++ b/shared/src/proto/mod.rs @@ -9,6 +9,7 @@ pub use types::{ #[cfg(test)] mod tests { + use data_encoding::HEXLOWER; use generated::types::Tx; use prost::Message; @@ -23,8 +24,8 @@ mod tests { }; let mut tx_bytes = vec![]; tx.encode(&mut tx_bytes).unwrap(); - let tx_hex = hex::encode(tx_bytes); - let tx_from_hex = hex::decode(tx_hex).unwrap(); + let tx_hex = HEXLOWER.encode(&tx_bytes); + let tx_from_hex = HEXLOWER.decode(tx_hex.as_ref()).unwrap(); let tx_from_bytes = Tx::decode(&tx_from_hex[..]).unwrap(); assert_eq!(tx, tx_from_bytes); } diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 7a936dd58f..6c6aa23234 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -4,6 +4,7 @@ use std::fmt::Display; use std::hash::{Hash, Hasher}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; use prost::Message; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -374,7 +375,7 @@ impl>> From for IntentId { impl Display for IntentId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0)) + write!(f, "{}", HEXLOWER.encode(&self.0)) } } diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index 27e7c29b89..bc3257f06a 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -4,6 +4,7 @@ use std::fmt::Display; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -57,7 +58,7 @@ impl super::PublicKey for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.try_to_vec().unwrap())) + write!(f, "{}", HEXLOWER.encode(&self.try_to_vec().unwrap())) } } @@ -65,7 +66,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(str: &str) -> Result { - let vec = hex::decode(str).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(str.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; Self::try_from_slice(vec.as_slice()) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -152,7 +155,7 @@ impl RefTo for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.try_to_vec().unwrap())) + write!(f, "{}", HEXLOWER.encode(&self.try_to_vec().unwrap())) } } @@ -160,7 +163,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(str: &str) -> Result { - let vec = hex::decode(str).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(str.as_ref()) + .map_err(ParseSecretKeyError::InvalidHex)?; Self::try_from_slice(vec.as_slice()) .map_err(ParseSecretKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/dkg_session_keys.rs b/shared/src/types/key/dkg_session_keys.rs index 26f6fffa00..f2cafb639c 100644 --- a/shared/src/types/key/dkg_session_keys.rs +++ b/shared/src/types/key/dkg_session_keys.rs @@ -7,6 +7,7 @@ use std::str::FromStr; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; use serde::{Deserialize, Serialize}; use crate::types::address::Address; @@ -142,7 +143,7 @@ impl Display for DkgPublicKey { let vec = self .try_to_vec() .expect("Encoding public key shouldn't fail"); - write!(f, "{}", hex::encode(&vec)) + write!(f, "{}", HEXLOWER.encode(&vec)) } } @@ -150,7 +151,9 @@ impl FromStr for DkgPublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/ed25519.rs b/shared/src/types/key/ed25519.rs index 12e5093bd2..90ab540f19 100644 --- a/shared/src/types/key/ed25519.rs +++ b/shared/src/types/key/ed25519.rs @@ -6,6 +6,7 @@ use std::io::Write; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -107,7 +108,7 @@ impl Ord for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.to_bytes())) + write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes())) } } @@ -115,7 +116,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -204,7 +207,7 @@ impl BorshSchema for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.to_bytes())) + write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes())) } } @@ -212,7 +215,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParseSecretKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParseSecretKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index a1343cd7e5..28a0fe1bf6 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -79,7 +79,7 @@ pub enum VerifySigError { #[derive(Error, Debug)] pub enum ParsePublicKeyError { #[error("Invalid public key hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid public key encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed public key does not belong to desired scheme")] @@ -90,7 +90,7 @@ pub enum ParsePublicKeyError { #[derive(Error, Debug)] pub enum ParseSignatureError { #[error("Invalid signature hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid signature encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed signature does not belong to desired scheme")] @@ -101,7 +101,7 @@ pub enum ParseSignatureError { #[derive(Error, Debug)] pub enum ParseSecretKeyError { #[error("Invalid secret key hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid secret key encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed secret key does not belong to desired scheme")] diff --git a/tests/Cargo.toml b/tests/Cargo.toml index cc437b3686..6872e912b7 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -31,13 +31,13 @@ namada_apps = {path = "../apps", default-features = false, features = ["testing" assert_cmd = "1.0.7" borsh = "0.9.1" color-eyre = "0.5.11" +data-encoding = "2.3.2" # NOTE: enable "print" feature to see output from builds ran by e2e tests escargot = {version = "0.5.7"} # , features = ["print"]} expectrl = {version = "=0.5.2"} eyre = "0.6.5" file-serve = "0.2.0" fs_extra = "1.2.0" -hex = "0.4.3" itertools = "0.10.0" libp2p = "0.38.0" pretty_assertions = "0.7.2" diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 552e675e67..307112ac2a 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -17,6 +17,7 @@ use std::time::{Duration, Instant}; use borsh::BorshSerialize; use color_eyre::eyre::Result; +use data_encoding::HEXLOWER; use namada::types::token; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, @@ -386,7 +387,7 @@ fn ledger_txs_and_queries() -> Result<()> { &validator_one_rpc, ], // expect hex encoded of borsh encoded bytes - hex::encode(christel_balance.try_to_vec().unwrap()), + HEXLOWER.encode(&christel_balance.try_to_vec().unwrap()), ), ]; for (query_args, expected) in &query_args_and_expected_response { diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 19a50588dc..0a186f3fd4 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -602,6 +602,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1359,10 +1365,10 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ferveo-common", - "hex", "ibc", "ibc-proto", "ics23", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 3e7bbbe365..e82a61634b 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -603,6 +603,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1370,10 +1376,10 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ferveo-common", - "hex", "ibc", "ibc-proto", "ics23", From b47d1c1d16699a538fb81886615cf930046687b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 8 Sep 2022 17:12:52 +0200 Subject: [PATCH 026/197] shared/storage/key: add support for int/uint keys that maintain order --- shared/src/types/storage.rs | 71 ++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index fc87bc8d51..1892334031 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -6,6 +6,7 @@ use std::ops::{Add, Div, Mul, Rem, Sub}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::BASE32HEX_NOPAD; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -27,6 +28,8 @@ pub enum Error { ParseAddressFromKey, #[error("Reserved prefix or string is specified: {0}")] InvalidKeySeg(String), + #[error("Error parsing key segment {0}")] + ParseKeySeg(String), } /// Result for functions that may fail @@ -193,6 +196,7 @@ impl Header { BorshDeserialize, BorshSchema, Debug, + Default, Eq, PartialEq, Ord, @@ -446,14 +450,12 @@ impl KeySeg for String { impl KeySeg for BlockHeight { fn parse(string: String) -> Result { - let h = string.parse::().map_err(|e| Error::Temporary { - error: format!("Unexpected height value {}, {}", string, e), - })?; + let h: u64 = KeySeg::parse(string)?; Ok(BlockHeight(h)) } fn raw(&self) -> String { - format!("{}", self.0) + self.0.raw() } fn to_db_key(&self) -> DbKeySeg { @@ -481,6 +483,67 @@ impl KeySeg for Address { } } +/// Implement [`KeySeg`] for a type via base32hex of its BE bytes (using +/// `to_le_bytes()` and `from_le_bytes` methods) that maintains sort order of +/// the original data. +// TODO this could be a bit more efficient without the string conversion (atm +// with base32hex), if we can use bytes for storage key directly (which we can +// with rockDB, but atm, we're calling `to_string()` using the custom `Display` +// impl from here) +macro_rules! impl_int_key_seg { + ($unsigned:ty, $signed:ty, $len:literal) => { + impl KeySeg for $unsigned { + fn parse(string: String) -> Result { + let bytes = + BASE32HEX_NOPAD.decode(string.as_ref()).map_err(|err| { + Error::ParseKeySeg(format!( + "Failed parsing {} with {}", + string, err + )) + })?; + let mut fixed_bytes = [0; $len]; + fixed_bytes.copy_from_slice(&bytes); + Ok(<$unsigned>::from_be_bytes(fixed_bytes)) + } + + fn raw(&self) -> String { + BASE32HEX_NOPAD.encode(&self.to_be_bytes()) + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } + } + + impl KeySeg for $signed { + fn parse(string: String) -> Result { + // get signed int from a unsigned int complemented with a min + // value + let complemented = <$unsigned>::parse(string)?; + let signed = (complemented as $signed) ^ <$signed>::MIN; + Ok(signed) + } + + fn raw(&self) -> String { + // signed int is converted to unsigned int that preserves the + // order by complementing it with a min value + let complemented = (*self ^ <$signed>::MIN) as $unsigned; + complemented.raw() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } + } + }; +} + +impl_int_key_seg!(u8, i8, 1); +impl_int_key_seg!(u16, i16, 2); +impl_int_key_seg!(u32, i32, 4); +impl_int_key_seg!(u64, i64, 8); +impl_int_key_seg!(u128, i128, 16); + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From e64808559e6f8673c482dd8a3081bb3bfda8a845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 8 Sep 2022 17:27:14 +0200 Subject: [PATCH 027/197] test/vm_host_env: check prefix iter order in tx and VP --- tests/src/vm_host_env/mod.rs | 67 +++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 98f95e7fae..f846325c40 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -157,11 +157,14 @@ mod tests { should yield an empty iterator." ); - // Write some values directly into the storage first - let prefix = Key::parse("prefix").unwrap(); + let prefix = storage::Key::parse("prefix").unwrap(); + // We'll write sub-key in some random order to check prefix iter's order + let sub_keys = [2_i32, 1, i32::MAX, -1, 260, -2, i32::MIN, 5, 0]; + + // Write the values directly into the storage first tx_host_env::with(|env| { - for i in 0..10_i32 { - let key = prefix.join(&Key::parse(i.to_string()).unwrap()); + for i in sub_keys.iter() { + let key = prefix.push(i).unwrap(); let value = i.try_to_vec().unwrap(); env.storage.write(&key, value).unwrap(); } @@ -171,11 +174,14 @@ mod tests { // Then try to iterate over their prefix let iter = namada_tx_prelude::iter_prefix(tx::ctx(), &prefix) .unwrap() - .map(|item| item.unwrap()); - let expected = (0..10).map(|i| { - (storage::Key::parse(format!("{}/{}", prefix, i)).unwrap(), i) - }); - itertools::assert_equal(iter.sorted(), expected.sorted()); + .map(Result::unwrap); + + // The order has to be sorted by sub-key value + let expected = sub_keys + .iter() + .sorted() + .map(|i| (prefix.push(i).unwrap(), *i)); + itertools::assert_equal(iter, expected); } #[test] @@ -251,7 +257,7 @@ mod tests { // We can add some data to the environment let key_raw = "key"; - let key = Key::parse(key_raw).unwrap(); + let key = storage::Key::parse(key_raw).unwrap(); let value = "test".to_string(); let value_raw = value.try_to_vec().unwrap(); vp_host_env::with(|env| { @@ -269,7 +275,7 @@ mod tests { let mut tx_env = TestTxEnv::default(); let addr = address::testing::established_address_1(); - let addr_key = Key::from(addr.to_db_key()); + let addr_key = storage::Key::from(addr.to_db_key()); // Write some value to storage let existing_key = @@ -354,12 +360,15 @@ mod tests { let mut tx_env = TestTxEnv::default(); let addr = address::testing::established_address_1(); - let addr_key = Key::from(addr.to_db_key()); + let addr_key = storage::Key::from(addr.to_db_key()); - // Write some value to storage let prefix = addr_key.join(&Key::parse("prefix").unwrap()); - for i in 0..10_i32 { - let key = prefix.join(&Key::parse(i.to_string()).unwrap()); + // We'll write sub-key in some random order to check prefix iter's order + let sub_keys = [2_i32, 1, i32::MAX, -1, 260, -2, i32::MIN, 5, 0]; + + // Write some values to storage + for i in sub_keys.iter() { + let key = prefix.push(i).unwrap(); let value = i.try_to_vec().unwrap(); tx_env.storage.write(&key, value).unwrap(); } @@ -367,8 +376,8 @@ mod tests { // In a transaction, write override the existing key's value and add // another key-value - let existing_key = prefix.join(&Key::parse(5.to_string()).unwrap()); - let new_key = prefix.join(&Key::parse(11.to_string()).unwrap()); + let existing_key = prefix.push(&5).unwrap(); + let new_key = prefix.push(&11).unwrap(); // Initialize the VP environment via a transaction vp_host_env::init_from_tx(addr, tx_env, |_addr| { @@ -383,23 +392,25 @@ mod tests { let iter_pre = namada_vp_prelude::iter_prefix(&ctx_pre, &prefix) .unwrap() .map(|item| item.unwrap()); - let expected_pre = (0..10).map(|i| { - (storage::Key::parse(format!("{}/{}", prefix, i)).unwrap(), i) - }); - itertools::assert_equal(iter_pre.sorted(), expected_pre.sorted()); + + // The order in pre has to be sorted by sub-key value + let expected_pre = sub_keys + .iter() + .sorted() + .map(|i| (prefix.push(i).unwrap(), *i)); + itertools::assert_equal(iter_pre, expected_pre); let ctx_post = vp::CTX.post(); let iter_post = namada_vp_prelude::iter_prefix(&ctx_post, &prefix) .unwrap() .map(|item| item.unwrap()); - let expected_post = (0..10).map(|i| { - let val = if i == 5 { 100 } else { i }; - ( - storage::Key::parse(format!("{}/{}", prefix, i)).unwrap(), - val, - ) + + // The order in post also has to be sorted + let expected_post = sub_keys.iter().sorted().map(|i| { + let val = if *i == 5 { 100 } else { *i }; + (prefix.push(i).unwrap(), val) }); - itertools::assert_equal(iter_post.sorted(), expected_post.sorted()); + itertools::assert_equal(iter_post, expected_post); } #[test] From 1429b929923c5973be8e3ea1e8970e1a3d63e090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 8 Sep 2022 18:04:55 +0200 Subject: [PATCH 028/197] add support for rev_iter_prefix in storage and VP and tx envs --- apps/src/lib/node/ledger/storage/rocksdb.rs | 42 ++++++---- shared/src/ledger/native_vp.rs | 25 ++++++ shared/src/ledger/storage/mockdb.rs | 48 +++++++++-- shared/src/ledger/storage/mod.rs | 25 +++++- shared/src/ledger/storage_api/mod.rs | 89 +++++++++++++++++++-- shared/src/ledger/vp_env.rs | 30 ++++++- shared/src/vm/host_env.rs | 68 +++++++++++++++- shared/src/vm/wasm/host_env.rs | 2 + tests/src/vm_host_env/tx.rs | 1 + tests/src/vm_host_env/vp.rs | 1 + tx_prelude/src/lib.rs | 14 +++- vm_env/src/lib.rs | 20 ++++- vp_prelude/src/lib.rs | 37 ++++++++- 13 files changed, 364 insertions(+), 38 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 71ed305ea5..44f55fa8e3 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -806,24 +806,36 @@ impl<'iter> DBIter<'iter> for RocksDB { &'iter self, prefix: &Key, ) -> PersistentPrefixIterator<'iter> { - let db_prefix = "subspace/".to_owned(); - let prefix = format!("{}{}", db_prefix, prefix); + iter_prefix(self, prefix, Direction::Forward) + } - let mut read_opts = ReadOptions::default(); - // don't use the prefix bloom filter - read_opts.set_total_order_seek(true); - let mut upper_prefix = prefix.clone().into_bytes(); - if let Some(last) = upper_prefix.pop() { - upper_prefix.push(last + 1); - } - read_opts.set_iterate_upper_bound(upper_prefix); + fn rev_iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter { + iter_prefix(self, prefix, Direction::Reverse) + } +} - let iter = self.0.iterator_opt( - IteratorMode::From(prefix.as_bytes(), Direction::Forward), - read_opts, - ); - PersistentPrefixIterator(PrefixIterator::new(iter, db_prefix)) +fn iter_prefix<'iter>( + db: &'iter RocksDB, + prefix: &Key, + direction: Direction, +) -> PersistentPrefixIterator<'iter> { + let db_prefix = "subspace/".to_owned(); + let prefix = format!("{}{}", db_prefix, prefix); + + let mut read_opts = ReadOptions::default(); + // don't use the prefix bloom filter + read_opts.set_total_order_seek(true); + let mut upper_prefix = prefix.clone().into_bytes(); + if let Some(last) = upper_prefix.pop() { + upper_prefix.push(last + 1); } + read_opts.set_iterate_upper_bound(upper_prefix); + + let iter = db.0.iterator_opt( + IteratorMode::From(prefix.as_bytes(), direction), + read_opts, + ); + PersistentPrefixIterator(PrefixIterator::new(iter, db_prefix)) } #[derive(Debug)] diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 5cbab7d639..845a44c0ed 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -186,6 +186,13 @@ where self.ctx.iter_prefix(prefix).into_storage_result() } + fn rev_iter_prefix( + &self, + prefix: &crate::types::storage::Key, + ) -> storage_api::Result { + self.ctx.rev_iter_prefix(prefix).into_storage_result() + } + fn iter_next( &self, iter: &mut Self::PrefixIter, @@ -247,6 +254,13 @@ where self.ctx.iter_prefix(prefix).into_storage_result() } + fn rev_iter_prefix( + &self, + prefix: &crate::types::storage::Key, + ) -> storage_api::Result { + self.ctx.rev_iter_prefix(prefix).into_storage_result() + } + fn iter_next( &self, iter: &mut Self::PrefixIter, @@ -400,6 +414,17 @@ where ) } + fn rev_iter_prefix( + &self, + prefix: &Key, + ) -> Result { + vp_env::rev_iter_prefix( + &mut *self.gas_meter.borrow_mut(), + self.storage, + prefix, + ) + } + fn iter_pre_next( &self, iter: &mut Self::PrefixIter, diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 3cc7e8863c..a9e3e8057b 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -431,7 +431,28 @@ impl<'iter> DBIter<'iter> for MockDB { let db_prefix = "subspace/".to_owned(); let prefix = format!("{}{}", db_prefix, prefix); let iter = self.0.borrow().clone().into_iter(); - MockPrefixIterator::new(MockIterator { prefix, iter }, db_prefix) + MockPrefixIterator::new( + MockIterator { + prefix, + iter, + reverse_order: false, + }, + db_prefix, + ) + } + + fn rev_iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter { + let db_prefix = "subspace/".to_owned(); + let prefix = format!("{}{}", db_prefix, prefix); + let iter = self.0.borrow().clone().into_iter(); + MockPrefixIterator::new( + MockIterator { + prefix, + iter, + reverse_order: true, + }, + db_prefix, + ) } } @@ -441,6 +462,8 @@ pub struct MockIterator { prefix: String, /// The concrete iterator pub iter: btree_map::IntoIter>, + /// Is the iterator in reverse order? + reverse_order: bool, } /// A prefix iterator for the [`MockDB`]. @@ -450,12 +473,23 @@ impl Iterator for MockIterator { type Item = KVBytes; fn next(&mut self) -> Option { - for (key, val) in &mut self.iter { - if key.starts_with(&self.prefix) { - return Some(( - Box::from(key.as_bytes()), - Box::from(val.as_slice()), - )); + if self.reverse_order { + for (key, val) in (&mut self.iter).rev() { + if key.starts_with(&self.prefix) { + return Some(( + Box::from(key.as_bytes()), + Box::from(val.as_slice()), + )); + } + } + } else { + for (key, val) in &mut self.iter { + if key.starts_with(&self.prefix) { + return Some(( + Box::from(key.as_bytes()), + Box::from(val.as_slice()), + )); + } } } None diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index d8c8e28091..347d0596cf 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -243,8 +243,13 @@ pub trait DBIter<'iter> { /// The concrete type of the iterator type PrefixIter: Debug + Iterator, u64)>; - /// Read account subspace key value pairs with the given prefix from the DB + /// Read account subspace key value pairs with the given prefix from the DB, + /// ordered by the storage keys. fn iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter; + + /// Read account subspace key value pairs with the given prefix from the DB, + /// reverse ordered by the storage keys. + fn rev_iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter; } /// Atomic batch write. @@ -411,7 +416,7 @@ where } } - /// Returns a prefix iterator and the gas cost + /// Returns a prefix iterator, ordered by storage keys, and the gas cost pub fn iter_prefix( &self, prefix: &Key, @@ -419,6 +424,15 @@ where (self.db.iter_prefix(prefix), prefix.len() as _) } + /// Returns a prefix iterator, reverse ordered by storage keys, and the gas + /// cost + pub fn rev_iter_prefix( + &self, + prefix: &Key, + ) -> (>::PrefixIter, u64) { + (self.db.rev_iter_prefix(prefix), prefix.len() as _) + } + /// Write a value to the specified subspace and returns the gas cost and the /// size difference pub fn write( @@ -729,6 +743,13 @@ where Ok(self.db.iter_prefix(prefix)) } + fn rev_iter_prefix( + &'iter self, + prefix: &crate::types::storage::Key, + ) -> std::result::Result { + Ok(self.db.rev_iter_prefix(prefix)) + } + fn iter_next( &self, iter: &mut Self::PrefixIter, diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index ab2f73784f..39b44d24d9 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -45,8 +45,8 @@ pub trait StorageRead<'iter> { /// Storage `has_key` in. It will try to read from the storage. fn has_key(&self, key: &storage::Key) -> Result; - /// Storage prefix iterator. It will try to get an iterator from the - /// storage. + /// Storage prefix iterator ordered by the storage keys. It will try to get + /// an iterator from the storage. /// /// For a more user-friendly iterator API, use [`fn@iter_prefix`] or /// [`fn@iter_prefix_bytes`] instead. @@ -55,7 +55,17 @@ pub trait StorageRead<'iter> { prefix: &storage::Key, ) -> Result; - /// Storage prefix iterator for. It will try to read from the storage. + /// Storage prefix iterator in reverse order of the storage keys. It will + /// try to get an iterator from the storage. + /// + /// For a more user-friendly iterator API, use [`fn@rev_iter_prefix`] or + /// [`fn@rev_iter_prefix_bytes`] instead. + fn rev_iter_prefix( + &'iter self, + prefix: &storage::Key, + ) -> Result; + + /// Storage prefix iterator. It will try to read from the storage. fn iter_next( &self, iter: &mut Self::PrefixIter, @@ -97,7 +107,7 @@ pub trait StorageWrite { fn delete(&mut self, key: &storage::Key) -> Result<()>; } -/// Iterate items matching the given prefix. +/// Iterate items matching the given prefix, ordered by the storage keys. pub fn iter_prefix_bytes<'a>( storage: &'a impl StorageRead<'a>, prefix: &crate::types::storage::Key, @@ -125,7 +135,8 @@ pub fn iter_prefix_bytes<'a>( Ok(iter) } -/// Iterate Borsh encoded items matching the given prefix. +/// Iterate Borsh encoded items matching the given prefix, ordered by the +/// storage keys. pub fn iter_prefix<'a, T>( storage: &'a impl StorageRead<'a>, prefix: &crate::types::storage::Key, @@ -162,3 +173,71 @@ where }); Ok(iter) } + +/// Iterate items matching the given prefix, reverse ordered by the storage +/// keys. +pub fn rev_iter_prefix_bytes<'a>( + storage: &'a impl StorageRead<'a>, + prefix: &crate::types::storage::Key, +) -> Result)>> + 'a> { + let iter = storage.rev_iter_prefix(prefix)?; + let iter = itertools::unfold(iter, |iter| { + match storage.iter_next(iter) { + Ok(Some((key, val))) => { + let key = match storage::Key::parse(key).into_storage_result() { + Ok(key) => key, + Err(err) => { + // Propagate key encoding errors into Iterator's Item + return Some(Err(err)); + } + }; + Some(Ok((key, val))) + } + Ok(None) => None, + Err(err) => { + // Propagate `iter_next` errors into Iterator's Item + Some(Err(err)) + } + } + }); + Ok(iter) +} + +/// Iterate Borsh encoded items matching the given prefix, reverse ordered by +/// the storage keys. +pub fn rev_iter_prefix<'a, T>( + storage: &'a impl StorageRead<'a>, + prefix: &crate::types::storage::Key, +) -> Result> + 'a> +where + T: BorshDeserialize, +{ + let iter = storage.rev_iter_prefix(prefix)?; + let iter = itertools::unfold(iter, |iter| { + match storage.iter_next(iter) { + Ok(Some((key, val))) => { + let key = match storage::Key::parse(key).into_storage_result() { + Ok(key) => key, + Err(err) => { + // Propagate key encoding errors into Iterator's Item + return Some(Err(err)); + } + }; + let val = match T::try_from_slice(&val).into_storage_result() { + Ok(val) => val, + Err(err) => { + // Propagate val encoding errors into Iterator's Item + return Some(Err(err)); + } + }; + Some(Ok((key, val))) + } + Ok(None) => None, + Err(err) => { + // Propagate `iter_next` errors into Iterator's Item + Some(Err(err)) + } + } + }); + Ok(iter) +} diff --git a/shared/src/ledger/vp_env.rs b/shared/src/ledger/vp_env.rs index 95e7c48782..9af4a992d3 100644 --- a/shared/src/ledger/vp_env.rs +++ b/shared/src/ledger/vp_env.rs @@ -91,13 +91,20 @@ pub trait VpEnv { /// current transaction is being applied. fn get_block_epoch(&self) -> Result; - /// Storage prefix iterator. It will try to get an iterator from the - /// storage. + /// Storage prefix iterator, ordered by storage keys. It will try to get an + /// iterator from the storage. fn iter_prefix( &self, prefix: &Key, ) -> Result; + /// Storage prefix iterator, reverse ordered by storage keys. It will try to + /// get an iterator from the storage. + fn rev_iter_prefix( + &self, + prefix: &Key, + ) -> Result; + /// Storage prefix iterator for prior state (before tx execution). It will /// try to read from the storage. fn iter_pre_next( @@ -396,7 +403,8 @@ where Ok(epoch) } -/// Storage prefix iterator. It will try to get an iterator from the storage. +/// Storage prefix iterator, ordered by storage keys. It will try to get an +/// iterator from the storage. pub fn iter_prefix<'a, DB, H>( gas_meter: &mut VpGasMeter, storage: &'a Storage, @@ -411,6 +419,22 @@ where Ok(iter) } +/// Storage prefix iterator, reverse ordered by storage keys. It will try to get +/// an iterator from the storage. +pub fn rev_iter_prefix<'a, DB, H>( + gas_meter: &mut VpGasMeter, + storage: &'a Storage, + prefix: &Key, +) -> EnvResult<>::PrefixIter> +where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, +{ + let (iter, gas) = storage.rev_iter_prefix(prefix); + add_gas(gas_meter, gas)?; + Ok(iter) +} + /// Storage prefix iterator for prior state (before tx execution). It will try /// to read from the storage. pub fn iter_pre_next( diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index fc7fd33e0c..533a05d94e 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -665,7 +665,7 @@ where /// Storage prefix iterator function exposed to the wasm VM Tx environment. /// It will try to get an iterator from the storage and return the corresponding -/// ID of the iterator. +/// ID of the iterator, ordered by storage keys. pub fn tx_iter_prefix( env: &TxVmEnv, prefix_ptr: u64, @@ -695,6 +695,38 @@ where Ok(iterators.insert(iter).id()) } +/// Storage prefix iterator function exposed to the wasm VM Tx environment. +/// It will try to get an iterator from the storage and return the corresponding +/// ID of the iterator, reverse ordered by storage keys. +pub fn tx_rev_iter_prefix( + env: &TxVmEnv, + prefix_ptr: u64, + prefix_len: u64, +) -> TxResult +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + let (prefix, gas) = env + .memory + .read_string(prefix_ptr, prefix_len as _) + .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; + tx_add_gas(env, gas)?; + + tracing::debug!("tx_rev_iter_prefix {}, prefix {}", prefix, prefix_ptr); + + let prefix = + Key::parse(prefix).map_err(TxRuntimeError::StorageDataError)?; + + let storage = unsafe { env.ctx.storage.get() }; + let iterators = unsafe { env.ctx.iterators.get() }; + let (iter, gas) = storage.rev_iter_prefix(&prefix); + tx_add_gas(env, gas)?; + Ok(iterators.insert(iter).id()) +} + /// Storage prefix iterator next function exposed to the wasm VM Tx environment. /// It will try to read from the write log first and if no entry found then from /// the storage. @@ -1195,7 +1227,7 @@ where /// Storage prefix iterator function exposed to the wasm VM VP environment. /// It will try to get an iterator from the storage and return the corresponding -/// ID of the iterator. +/// ID of the iterator, ordered by storage keys. pub fn vp_iter_prefix( env: &VpVmEnv, prefix_ptr: u64, @@ -1225,6 +1257,38 @@ where Ok(iterators.insert(iter).id()) } +/// Storage prefix iterator function exposed to the wasm VM VP environment. +/// It will try to get an iterator from the storage and return the corresponding +/// ID of the iterator, reverse ordered by storage keys. +pub fn vp_rev_iter_prefix( + env: &VpVmEnv, + prefix_ptr: u64, + prefix_len: u64, +) -> vp_env::EnvResult +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + EVAL: VpEvaluator, + CA: WasmCacheAccess, +{ + let (prefix, gas) = env + .memory + .read_string(prefix_ptr, prefix_len as _) + .map_err(|e| vp_env::RuntimeError::MemoryError(Box::new(e)))?; + let gas_meter = unsafe { env.ctx.gas_meter.get() }; + vp_env::add_gas(gas_meter, gas)?; + + let prefix = + Key::parse(prefix).map_err(vp_env::RuntimeError::StorageDataError)?; + tracing::debug!("vp_rev_iter_prefix {}", prefix); + + let storage = unsafe { env.ctx.storage.get() }; + let iter = vp_env::rev_iter_prefix(gas_meter, storage, &prefix)?; + let iterators = unsafe { env.ctx.iterators.get() }; + Ok(iterators.insert(iter).id()) +} + /// Storage prefix iterator for prior state (before tx execution) function /// exposed to the wasm VM VP environment. It will try to read from the storage. /// diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index 1a50c5533d..06d45fa4f9 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -67,6 +67,7 @@ where "anoma_tx_write_temp" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_write_temp), "anoma_tx_delete" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_delete), "anoma_tx_iter_prefix" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_iter_prefix), + "anoma_tx_rev_iter_prefix" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_rev_iter_prefix), "anoma_tx_iter_next" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_iter_next), "anoma_tx_insert_verifier" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_insert_verifier), "anoma_tx_update_validity_predicate" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_update_validity_predicate), @@ -107,6 +108,7 @@ where "anoma_vp_has_key_pre" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_has_key_pre), "anoma_vp_has_key_post" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_has_key_post), "anoma_vp_iter_prefix" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_prefix), + "anoma_vp_rev_iter_prefix" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_rev_iter_prefix), "anoma_vp_iter_pre_next" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_pre_next), "anoma_vp_iter_post_next" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_post_next), "anoma_vp_get_chain_id" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_chain_id), diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 346cb6bdd4..728c488bca 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -335,6 +335,7 @@ mod native_tx_host_env { )); native_host_fn!(tx_delete(key_ptr: u64, key_len: u64)); native_host_fn!(tx_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64); + native_host_fn!(tx_rev_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64); native_host_fn!(tx_iter_next(iter_id: u64) -> i64); native_host_fn!(tx_insert_verifier(addr_ptr: u64, addr_len: u64)); native_host_fn!(tx_update_validity_predicate( diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index d849a11487..88aa63d530 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -326,6 +326,7 @@ mod native_vp_host_env { native_host_fn!(vp_has_key_pre(key_ptr: u64, key_len: u64) -> i64); native_host_fn!(vp_has_key_post(key_ptr: u64, key_len: u64) -> i64); native_host_fn!(vp_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64); + native_host_fn!(vp_rev_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64); native_host_fn!(vp_iter_pre_next(iter_id: u64) -> i64); native_host_fn!(vp_iter_post_next(iter_id: u64) -> i64); native_host_fn!(vp_get_chain_id(result_ptr: u64)); diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index bd1833ec58..c609f944dd 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -24,7 +24,8 @@ pub use namada::ledger::parameters::storage as parameters_storage; pub use namada::ledger::storage::types::encode; use namada::ledger::storage_api; pub use namada::ledger::storage_api::{ - iter_prefix, iter_prefix_bytes, StorageRead, StorageWrite, + iter_prefix, iter_prefix_bytes, rev_iter_prefix, rev_iter_prefix_bytes, + StorageRead, StorageWrite, }; pub use namada::ledger::treasury::storage as treasury_storage; pub use namada::ledger::tx_env::TxEnv; @@ -178,6 +179,17 @@ impl StorageRead<'_> for Ctx { Ok(KeyValIterator(iter_id, PhantomData)) } + fn rev_iter_prefix( + &self, + prefix: &storage::Key, + ) -> storage_api::Result { + let prefix = prefix.to_string(); + let iter_id = unsafe { + anoma_tx_rev_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) + }; + Ok(KeyValIterator(iter_id, PhantomData)) + } + fn iter_next( &self, iter: &mut Self::PrefixIter, diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 53c594dbab..1421dbde48 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -48,9 +48,17 @@ pub mod tx { // Delete the given key and its value pub fn anoma_tx_delete(key_ptr: u64, key_len: u64); - // Get an ID of a data iterator with key prefix + // Get an ID of a data iterator with key prefix, ordered by storage + // keys. pub fn anoma_tx_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64; + // Get an ID of a data iterator with key prefix, reverse ordered by + // storage keys. + pub fn anoma_tx_rev_iter_prefix( + prefix_ptr: u64, + prefix_len: u64, + ) -> u64; + // Returns the size of the value (can be 0), or -1 if there's no next // value. If a value is found, it will be placed in the read // cache, because we cannot allocate a buffer for it before we know @@ -133,9 +141,17 @@ pub mod vp { // Returns 1 if the key is present in posterior state, -1 otherwise. pub fn anoma_vp_has_key_post(key_ptr: u64, key_len: u64) -> i64; - // Get an ID of a data iterator with key prefix + // Get an ID of a data iterator with key prefix, ordered by storage + // keys. pub fn anoma_vp_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64; + // Get an ID of a data iterator with key prefix, reverse ordered by + // storage keys. + pub fn anoma_vp_rev_iter_prefix( + prefix_ptr: u64, + prefix_len: u64, + ) -> u64; + // Read variable-length prior state when we don't know the size // up-front, returns the size of the value (can be 0), or -1 if // the key is not present. If a value is found, it will be placed in the diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 3ee761f78f..862071084a 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -23,7 +23,8 @@ pub use borsh::{BorshDeserialize, BorshSerialize}; pub use error::*; pub use namada::ledger::governance::storage as gov_storage; pub use namada::ledger::storage_api::{ - self, iter_prefix, iter_prefix_bytes, StorageRead, + self, iter_prefix, iter_prefix_bytes, rev_iter_prefix, + rev_iter_prefix_bytes, StorageRead, }; pub use namada::ledger::vp_env::VpEnv; pub use namada::ledger::{parameters, pos as proof_of_stake}; @@ -238,6 +239,14 @@ impl VpEnv for Ctx { self.pre().iter_prefix(prefix).into_env_result() } + fn rev_iter_prefix( + &self, + prefix: &storage::Key, + ) -> Result { + // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl + self.pre().rev_iter_prefix(prefix).into_env_result() + } + fn iter_pre_next( &self, iter: &mut Self::PrefixIter, @@ -339,6 +348,14 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { iter_prefix_impl(prefix) } + fn rev_iter_prefix( + &self, + prefix: &storage::Key, + ) -> storage_api::Result { + // Note that this is the same as `CtxPostStorageRead` + rev_iter_prefix_impl(prefix) + } + fn iter_next( &self, iter: &mut Self::PrefixIter, @@ -416,6 +433,14 @@ impl StorageRead<'_> for CtxPostStorageRead<'_> { iter_prefix_impl(prefix) } + fn rev_iter_prefix( + &self, + prefix: &storage::Key, + ) -> storage_api::Result { + // Note that this is the same as `CtxPreStorageRead` + rev_iter_prefix_impl(prefix) + } + fn iter_next( &self, iter: &mut Self::PrefixIter, @@ -454,6 +479,16 @@ fn iter_prefix_impl( Ok(KeyValIterator(iter_id, PhantomData)) } +fn rev_iter_prefix_impl( + prefix: &storage::Key, +) -> Result)>, storage_api::Error> { + let prefix = prefix.to_string(); + let iter_id = unsafe { + anoma_vp_rev_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) + }; + Ok(KeyValIterator(iter_id, PhantomData)) +} + fn get_chain_id() -> Result { let result = Vec::with_capacity(CHAIN_ID_LENGTH); unsafe { From 881c93cbd9406bf8f48062f6fafbbb3750f91e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 8 Sep 2022 18:13:13 +0200 Subject: [PATCH 029/197] tests: extend prefix iter tests for reverse order --- tests/src/vm_host_env/mod.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index f846325c40..e585ab7924 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -182,6 +182,19 @@ mod tests { .sorted() .map(|i| (prefix.push(i).unwrap(), *i)); itertools::assert_equal(iter, expected); + + // Try to iterate over their prefix in reverse + let iter = namada_tx_prelude::rev_iter_prefix(tx::ctx(), &prefix) + .unwrap() + .map(Result::unwrap); + + // The order has to be reverse sorted by sub-key value + let expected = sub_keys + .iter() + .sorted() + .rev() + .map(|i| (prefix.push(i).unwrap(), *i)); + itertools::assert_equal(iter, expected); } #[test] @@ -411,6 +424,19 @@ mod tests { (prefix.push(i).unwrap(), val) }); itertools::assert_equal(iter_post, expected_post); + + // Try to iterate over their prefix in reverse + let iter_pre = namada_vp_prelude::rev_iter_prefix(&ctx_pre, &prefix) + .unwrap() + .map(|item| item.unwrap()); + + // The order in has to be reverse sorted by sub-key value + let expected_pre = sub_keys + .iter() + .sorted() + .rev() + .map(|i| (prefix.push(i).unwrap(), *i)); + itertools::assert_equal(iter_pre, expected_pre); } #[test] From d786a4aa979195ad65d381ac0ab61119db2f9712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 8 Sep 2022 18:16:51 +0200 Subject: [PATCH 030/197] changelog: add #458 --- .changelog/unreleased/improvements/409-sorted-prefix-iter.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/improvements/409-sorted-prefix-iter.md diff --git a/.changelog/unreleased/improvements/409-sorted-prefix-iter.md b/.changelog/unreleased/improvements/409-sorted-prefix-iter.md new file mode 100644 index 0000000000..2f95505960 --- /dev/null +++ b/.changelog/unreleased/improvements/409-sorted-prefix-iter.md @@ -0,0 +1,3 @@ +- Fix order of prefix iterator to be sorted by storage + keys and add support for a reverse order prefix iterator. + ([#409](https://github.com/anoma/namada/issues/409)) \ No newline at end of file From 1d2f1dd15364890a6001139d592f4cda1a396f80 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 9 Sep 2022 05:57:59 +0000 Subject: [PATCH 031/197] [ci skip] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 0c7b7cf504..f5295c8f1a 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.d80c5ac518c223eea92a51eeb033462e9ea4498530d12de5b6e6ca6b3fa59ea8.wasm", - "tx_from_intent.wasm": "tx_from_intent.49565974c6e2c3d64a05729bd57217b244d804fc9f4e5369d1f3515aeaa8397f.wasm", - "tx_ibc.wasm": "tx_ibc.0d9d639037a8dc54c53ecbedc8396ea103ee7c8f485790ddf7223f4e7e6a9779.wasm", - "tx_init_account.wasm": "tx_init_account.97bfee0b78c87abc217c58351f1d6242990a06c7379b27f948950f04f36b49a2.wasm", - "tx_init_nft.wasm": "tx_init_nft.6207eabda37cd356b6093d069f388bd84463b5e1b8860811a36a9b63da84951f.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.1073d69f69172276c61c104f7b4678019723df19ddb30aedf6293a00de973846.wasm", - "tx_init_validator.wasm": "tx_init_validator.40f0152c1bd59f46ec26123d98d0b49a0a458335b6012818cf8504b7708bf625.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.f1c8039a6fb5e01e7441cfa2e3fb2f84a1cb60ed660745ddc172d0d01f1b0ab1.wasm", - "tx_transfer.wasm": "tx_transfer.191d28c340900e6dc297e66b054cd83b690ae0357a8e13b37962b265b2e17da8.wasm", - "tx_unbond.wasm": "tx_unbond.ea73369f68abef405c4f7a3a09c3da6aa68493108d48b1e4e243d26766f00283.wasm", - "tx_update_vp.wasm": "tx_update_vp.a304b3c70361cde5fda458ba5637d6825eb002198e73990a1c74351113b83d43.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.5e51a66746df8c7e786cc6837a74f87d239442ace67d1c4ef4e6751aa725514f.wasm", - "tx_withdraw.wasm": "tx_withdraw.f776265133f972e6705797561b6bb37f9e21de07f3611b23cfdd6e39cb252c0f.wasm", - "vp_nft.wasm": "vp_nft.1a32d37b32c616119d156128560a0eeb43206e91069e2c3875e74211ed3ad01f.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.c30baa6d2dab2c336a6b2485e3ccf41cd548b135ca5245b80fab15858c70365c.wasm", - "vp_token.wasm": "vp_token.9f27c2e823691487bb6631fbfecb39837c1f404ef9b263e810a5a2a76f4c5957.wasm", - "vp_user.wasm": "vp_user.59b7a9262c951ff451d3aec0604ae3dd0fc4729f8d994c114be6320ce5d38712.wasm" + "tx_bond.wasm": "tx_bond.bef50a074940c1b8cd788b93376162553bc11ec3521ffbbc0e8913ae27edfac1.wasm", + "tx_from_intent.wasm": "tx_from_intent.41b56839c7b797407b71a9b44b1b58b0b9f98069ccfbbdba1d5ce9501a78ddea.wasm", + "tx_ibc.wasm": "tx_ibc.b0ab86c7b67a612ed559b57e2277e307139223f3fed245b62e6d0524e5785cad.wasm", + "tx_init_account.wasm": "tx_init_account.913c7218e5f24ee4d86b89d9020624e54340bd597278b0596baba721a2944e70.wasm", + "tx_init_nft.wasm": "tx_init_nft.2f2c198643996a99ef0621f7a1c5ad824e176258f8f5755209378cf5b7770f64.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.d4332ff996775b3e243afacdf311be7e0b3f32dc3c15edfa89d0ce9ee6b6bb6b.wasm", + "tx_init_validator.wasm": "tx_init_validator.5f6b566d549ab182874688a635f1c2900605d7a7a8d6850fa4c084ce5277d212.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.19a9c2b62ff8301979cb038e2ad26fcf8f67c089ec22cb915e1c17e4f95adf1e.wasm", + "tx_transfer.wasm": "tx_transfer.c90e697f22ce49d30e9f8120f5e37452bcd8bf5b7d0f65426da7283a9c141ab9.wasm", + "tx_unbond.wasm": "tx_unbond.f01fea78e3225ae0029b99a735f72e9309a9e68c80bf8cd3e620456898edd49c.wasm", + "tx_update_vp.wasm": "tx_update_vp.f8eef21b09d932da44babf0faf2a4137fa075d952ea92bb0b652ce141475ad42.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.fb2f1b95798704a8df8860c18777e59ad8bf0b69e31f928f1bb5ed0c8c0eed55.wasm", + "tx_withdraw.wasm": "tx_withdraw.843be20010c36500170b231f10fdf80c4486b7afef81217c4f0ac2600229db67.wasm", + "vp_nft.wasm": "vp_nft.10393706d5d717a17285fc16128fee8703f2a0b07bf952e275c8836b7df4b1fd.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.f86a33e5e023a07f420f4d44a01f0c686e84989790a9c47c718cb83b6fce65fb.wasm", + "vp_token.wasm": "vp_token.b2d86a59d1024992ade25ae2a513abdddd41a3f13f33fff0eea35bec0e5faca0.wasm", + "vp_user.wasm": "vp_user.f66dae6078241f8a9a564bf58928bf9f005a5ee1318e245850c8cbfb45474a5c.wasm" } \ No newline at end of file From 74d83f247ee2082c51798fbe9b487e9850720c19 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Sep 2022 08:58:00 +0100 Subject: [PATCH 032/197] Change the way we call Tendermint --- apps/src/lib/node/ledger/tendermint_node.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 6f5cd41b0f36301df24c0cecf4a220d365e2a079 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 6 Sep 2022 16:44:08 +0200 Subject: [PATCH 033/197] 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/tendermint_node.rs | 4 +--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 92f801fe06..240ba69e0f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1186,9 +1186,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 @@ -1204,7 +1215,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/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 0f75154b12f5a83f9279654c904a4da6f9fbbc26 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Sep 2022 09:15:23 +0100 Subject: [PATCH 034/197] Fix websockets and parsing of events returned from Tm --- apps/src/lib/client/tendermint_rpc_types.rs | 52 ++------------------- apps/src/lib/client/tx.rs | 13 ++---- 2 files changed, 8 insertions(+), 57 deletions(-) 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 240ba69e0f..7574054e09 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1197,6 +1197,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), @@ -1207,7 +1208,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 @@ -1218,7 +1219,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 }; @@ -1231,9 +1232,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: {}", @@ -1246,9 +1245,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() From a700e4902a177408f8945f1014e557a3d62b84c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 13 Sep 2022 15:31:10 +0200 Subject: [PATCH 035/197] ledger: use storage_api::Error in VpEnv and TxEnv instead of generic This inverts the wrapping of errors, storage_api::Error now wraps NativeVp:Error and some other IBC and PoS custom errors --- shared/src/ledger/ibc/handler.rs | 9 ++ shared/src/ledger/native_vp.rs | 55 ++++++----- shared/src/ledger/pos/mod.rs | 36 ++++++- shared/src/ledger/pos/vp.rs | 12 +-- shared/src/ledger/storage_api/error.rs | 23 +++++ shared/src/ledger/storage_api/mod.rs | 2 +- shared/src/ledger/tx_env.rs | 25 ++--- shared/src/ledger/vp_env.rs | 67 ++++++------- tx_prelude/src/error.rs | 112 ---------------------- tx_prelude/src/ibc.rs | 7 -- tx_prelude/src/lib.rs | 30 +++--- tx_prelude/src/proof_of_stake.rs | 47 ++------- vp_prelude/src/error.rs | 110 --------------------- vp_prelude/src/lib.rs | 88 ++++++++--------- wasm/wasm_source/src/tx_bond.rs | 4 +- wasm/wasm_source/src/tx_from_intent.rs | 4 +- wasm/wasm_source/src/tx_ibc.rs | 2 +- wasm/wasm_source/src/tx_init_account.rs | 4 +- wasm/wasm_source/src/tx_init_nft.rs | 4 +- wasm/wasm_source/src/tx_init_proposal.rs | 4 +- wasm/wasm_source/src/tx_init_validator.rs | 4 +- wasm/wasm_source/src/tx_mint_nft.rs | 4 +- wasm/wasm_source/src/tx_transfer.rs | 4 +- wasm/wasm_source/src/tx_unbond.rs | 4 +- wasm/wasm_source/src/tx_update_vp.rs | 4 +- wasm/wasm_source/src/tx_vote_proposal.rs | 4 +- wasm/wasm_source/src/tx_withdraw.rs | 4 +- wasm_for_tests/wasm_source/src/lib.rs | 4 +- 28 files changed, 237 insertions(+), 440 deletions(-) delete mode 100644 tx_prelude/src/error.rs delete mode 100644 vp_prelude/src/error.rs diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index 5cbee20756..4a3fe528a9 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -70,6 +70,7 @@ use crate::ibc::events::IbcEvent; use crate::ibc::mock::client_state::{MockClientState, MockConsensusState}; use crate::ibc::timestamp::Timestamp; use crate::ledger::ibc::storage; +use crate::ledger::storage_api; use crate::tendermint::Time; use crate::tendermint_proto::{Error as ProtoError, Protobuf}; use crate::types::address::{Address, InternalAddress}; @@ -117,6 +118,14 @@ pub enum Error { ReceivingToken(String), } +// This is needed to use `ibc::Handler::Error` with `IbcActions` in +// `tx_prelude/src/ibc.rs` +impl From for storage_api::Error { + fn from(err: Error) -> Self { + storage_api::Error::new(err) + } +} + /// for handling IBC modules pub type Result = std::result::Result; diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index f17b339086..b98794d12c 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -17,7 +17,9 @@ use crate::vm::prefix_iter::PrefixIterators; use crate::vm::WasmCacheAccess; /// Possible error in a native VP host function call -pub type Error = vp_env::RuntimeError; +/// The `storage_api::Error` may wrap the `vp_env::RuntimeError` and can +/// be extended with other custom errors when using `trait VpEnv`. +pub type Error = storage_api::Error; /// A native VP module should implement its validation logic using this trait. pub trait NativeVp { @@ -201,23 +203,23 @@ where &self, prefix: &crate::types::storage::Key, ) -> Result { - self.ctx.iter_prefix(prefix).into_storage_result() + self.ctx.iter_prefix(prefix) } fn get_chain_id(&self) -> Result { - self.ctx.get_chain_id().into_storage_result() + self.ctx.get_chain_id() } fn get_block_height(&self) -> Result { - self.ctx.get_block_height().into_storage_result() + self.ctx.get_block_height() } fn get_block_hash(&self) -> Result { - self.ctx.get_block_hash().into_storage_result() + self.ctx.get_block_hash() } fn get_block_epoch(&self) -> Result { - self.ctx.get_block_epoch().into_storage_result() + self.ctx.get_block_epoch() } } @@ -275,23 +277,23 @@ where &self, prefix: &crate::types::storage::Key, ) -> Result { - self.ctx.iter_prefix(prefix).into_storage_result() + self.ctx.iter_prefix(prefix) } fn get_chain_id(&self) -> Result { - self.ctx.get_chain_id().into_storage_result() + self.ctx.get_chain_id() } fn get_block_height(&self) -> Result { - self.ctx.get_block_height().into_storage_result() + self.ctx.get_block_height() } fn get_block_hash(&self) -> Result { - self.ctx.get_block_hash().into_storage_result() + self.ctx.get_block_hash() } fn get_block_epoch(&self) -> Result { - self.ctx.get_block_epoch().into_storage_result() + self.ctx.get_block_epoch() } } @@ -301,7 +303,6 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - type Error = Error; type Post = CtxPostStorageRead<'view, 'a, DB, H, CA>; type Pre = CtxPreStorageRead<'view, 'a, DB, H, CA>; type PrefixIter = >::PrefixIter; @@ -317,61 +318,70 @@ where fn read_temp( &self, key: &Key, - ) -> Result, Self::Error> { + ) -> Result, storage_api::Error> { vp_env::read_temp( &mut *self.gas_meter.borrow_mut(), self.write_log, key, ) .map(|data| data.and_then(|t| T::try_from_slice(&t[..]).ok())) + .into_storage_result() } fn read_bytes_temp( &self, key: &Key, - ) -> Result>, Self::Error> { + ) -> Result>, storage_api::Error> { vp_env::read_temp( &mut *self.gas_meter.borrow_mut(), self.write_log, key, ) + .into_storage_result() } - fn get_chain_id(&'view self) -> Result { + fn get_chain_id(&'view self) -> Result { vp_env::get_chain_id(&mut *self.gas_meter.borrow_mut(), self.storage) + .into_storage_result() } - fn get_block_height(&'view self) -> Result { + fn get_block_height( + &'view self, + ) -> Result { vp_env::get_block_height( &mut *self.gas_meter.borrow_mut(), self.storage, ) + .into_storage_result() } - fn get_block_hash(&'view self) -> Result { + fn get_block_hash(&'view self) -> Result { vp_env::get_block_hash(&mut *self.gas_meter.borrow_mut(), self.storage) + .into_storage_result() } - fn get_block_epoch(&'view self) -> Result { + fn get_block_epoch(&'view self) -> Result { vp_env::get_block_epoch(&mut *self.gas_meter.borrow_mut(), self.storage) + .into_storage_result() } fn iter_prefix( &'view self, prefix: &Key, - ) -> Result { + ) -> Result { vp_env::iter_prefix( &mut *self.gas_meter.borrow_mut(), self.storage, prefix, ) + .into_storage_result() } fn eval( &self, vp_code: Vec, input_data: Vec, - ) -> Result { + ) -> Result { #[cfg(feature = "wasm-runtime")] { use std::marker::PhantomData; @@ -429,11 +439,12 @@ where &self, pk: &crate::types::key::common::PublicKey, sig: &crate::types::key::common::Signature, - ) -> Result { + ) -> Result { Ok(self.tx.verify_sig(pk, sig).is_ok()) } - fn get_tx_code_hash(&self) -> Result { + fn get_tx_code_hash(&self) -> Result { vp_env::get_tx_code_hash(&mut *self.gas_meter.borrow_mut(), self.tx) + .into_storage_result() } } diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index a9d72e84bb..3b498727df 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -13,6 +13,7 @@ use namada_proof_of_stake::PosBase; pub use storage::*; pub use vp::PosVP; +use super::storage_api; use crate::ledger::storage::{self as ledger_storage, Storage, StorageHasher}; use crate::types::address::{self, Address, InternalAddress}; use crate::types::storage::Epoch; @@ -88,6 +89,40 @@ impl From for Epoch { } } +// The error conversions are needed to implement `PosActions` in +// `tx_prelude/src/proof_of_stake.rs` +impl From> + for storage_api::Error +{ + fn from(err: namada_proof_of_stake::BecomeValidatorError
) -> Self { + Self::new(err) + } +} + +impl From> for storage_api::Error { + fn from(err: namada_proof_of_stake::BondError
) -> Self { + Self::new(err) + } +} + +impl From> + for storage_api::Error +{ + fn from( + err: namada_proof_of_stake::UnbondError, + ) -> Self { + Self::new(err) + } +} + +impl From> + for storage_api::Error +{ + fn from(err: namada_proof_of_stake::WithdrawError
) -> Self { + Self::new(err) + } +} + #[macro_use] mod macros { /// Implement `PosReadOnly` for a type that implements @@ -115,7 +150,6 @@ mod macros { $( $any )* { type Address = $crate::types::address::Address; - // type Error = $crate::ledger::native_vp::Error; type $error = $err_ty; type PublicKey = $crate::types::key::common::PublicKey; type TokenAmount = $crate::types::token::Amount; diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 2f19b6d1a4..0551c5de7f 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -47,8 +47,6 @@ use crate::vm::WasmCacheAccess; pub enum Error { #[error("Native VP error: {0}")] NativeVpError(native_vp::Error), - #[error("Storage error: {0}")] - StorageApi(storage_api::Error), } /// PoS functions result @@ -324,7 +322,7 @@ where } impl_pos_read_only! { - type Error = native_vp::Error; + type Error = storage_api::Error; impl<'f, 'a, DB, H, CA> PosReadOnly for CtxPreStorageRead<'f, 'a, DB, H, CA> where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter> +'static, @@ -333,7 +331,7 @@ impl_pos_read_only! { } impl_pos_read_only! { - type Error = native_vp::Error; + type Error = storage_api::Error; impl<'f, 'a, DB, H, CA> PosReadOnly for CtxPostStorageRead<'f, 'a, DB, H, CA> where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter> +'static, @@ -346,9 +344,3 @@ impl From for Error { Self::NativeVpError(err) } } - -impl From for Error { - fn from(err: storage_api::Error) -> Self { - Self::StorageApi(err) - } -} diff --git a/shared/src/ledger/storage_api/error.rs b/shared/src/ledger/storage_api/error.rs index d01fbfd287..8af95be723 100644 --- a/shared/src/ledger/storage_api/error.rs +++ b/shared/src/ledger/storage_api/error.rs @@ -6,6 +6,8 @@ use thiserror::Error; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { + #[error("{0}")] + SimpleMessage(&'static str), #[error("{0}")] Custom(CustomError), #[error("{0}: {1}")] @@ -48,6 +50,12 @@ impl Error { Self::Custom(CustomError(error.into())) } + /// Create an [`enum@Error`] from a static message. + #[inline] + pub const fn new_const(msg: &'static str) -> Self { + Self::SimpleMessage(msg) + } + /// Wrap another [`std::error::Error`] with a static message. pub fn wrap(msg: &'static str, error: E) -> Self where @@ -66,3 +74,18 @@ impl std::fmt::Display for CustomError { self.0.fmt(f) } } + +/// An extension to [`Option`] to allow turning `None` case to an Error from a +/// static string (handy for WASM). +pub trait OptionExt { + /// Transforms the [`Option`] into a [`Result`], mapping + /// [`Some(v)`] to [`Ok(v)`] and [`None`] to the given static error + /// message. + fn ok_or_err_msg(self, msg: &'static str) -> Result; +} + +impl OptionExt for Option { + fn ok_or_err_msg(self, msg: &'static str) -> Result { + self.ok_or_else(|| Error::new_const(msg)) + } +} diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index 94be4d8568..5dfec7e76e 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -4,7 +4,7 @@ mod error; use borsh::{BorshDeserialize, BorshSerialize}; -pub use error::{CustomError, Error, Result, ResultExt}; +pub use error::{CustomError, Error, OptionExt, Result, ResultExt}; use crate::types::storage::{self, BlockHash, BlockHeight, Epoch}; diff --git a/shared/src/ledger/tx_env.rs b/shared/src/ledger/tx_env.rs index 1db8fad09b..7672ac6505 100644 --- a/shared/src/ledger/tx_env.rs +++ b/shared/src/ledger/tx_env.rs @@ -3,7 +3,7 @@ use borsh::BorshSerialize; -use crate::ledger::storage_api::{StorageRead, StorageWrite}; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::address::Address; use crate::types::ibc::IbcEvent; use crate::types::storage; @@ -11,23 +11,20 @@ use crate::types::time::Rfc3339String; /// Transaction host functions pub trait TxEnv<'iter>: StorageRead<'iter> + StorageWrite { - /// Host env functions possible errors - type Error; - /// Write a temporary value to be encoded with Borsh at the given key to /// storage. fn write_temp( &mut self, key: &storage::Key, val: T, - ) -> Result<(), Self::Error>; + ) -> Result<(), storage_api::Error>; /// Write a temporary value as bytes at the given key to storage. fn write_bytes_temp( &mut self, key: &storage::Key, val: impl AsRef<[u8]>, - ) -> Result<(), Self::Error>; + ) -> Result<(), storage_api::Error>; /// Insert a verifier address. This address must exist on chain, otherwise /// the transaction will be rejected. @@ -35,26 +32,32 @@ pub trait TxEnv<'iter>: StorageRead<'iter> + StorageWrite { /// Validity predicates of each verifier addresses inserted in the /// transaction will validate the transaction and will receive all the /// changed storage keys and initialized accounts in their inputs. - fn insert_verifier(&mut self, addr: &Address) -> Result<(), Self::Error>; + fn insert_verifier( + &mut self, + addr: &Address, + ) -> Result<(), storage_api::Error>; /// Initialize a new account generates a new established address and /// writes the given code as its validity predicate into the storage. fn init_account( &mut self, code: impl AsRef<[u8]>, - ) -> Result; + ) -> Result; /// Update a validity predicate fn update_validity_predicate( &mut self, addr: &Address, code: impl AsRef<[u8]>, - ) -> Result<(), Self::Error>; + ) -> Result<(), storage_api::Error>; /// Emit an IBC event. There can be only one event per transaction. On /// multiple calls, only the last emitted event will be used. - fn emit_ibc_event(&mut self, event: &IbcEvent) -> Result<(), Self::Error>; + fn emit_ibc_event( + &mut self, + event: &IbcEvent, + ) -> Result<(), storage_api::Error>; /// Get time of the current block header as rfc 3339 string - fn get_block_time(&self) -> Result; + fn get_block_time(&self) -> Result; } diff --git a/shared/src/ledger/vp_env.rs b/shared/src/ledger/vp_env.rs index e1f72d8505..8398f37cda 100644 --- a/shared/src/ledger/vp_env.rs +++ b/shared/src/ledger/vp_env.rs @@ -22,9 +22,6 @@ pub trait VpEnv<'view> { /// Storage read prefix iterator type PrefixIter; - /// Host functions possible errors, extensible with custom user errors. - type Error: From; - /// Type to read storage state before the transaction execution type Pre: StorageRead<'view, PrefixIter = Self::PrefixIter>; @@ -43,36 +40,37 @@ pub trait VpEnv<'view> { fn read_temp( &self, key: &Key, - ) -> Result, Self::Error>; + ) -> Result, storage_api::Error>; /// Storage read temporary state raw bytes (after tx execution). It will try /// to read from only the write log. fn read_bytes_temp( &self, key: &Key, - ) -> Result>, Self::Error>; + ) -> Result>, storage_api::Error>; /// Getting the chain ID. - fn get_chain_id(&'view self) -> Result; + fn get_chain_id(&'view self) -> Result; /// Getting the block height. The height is that of the block to which the /// current transaction is being applied. - fn get_block_height(&'view self) -> Result; + fn get_block_height(&'view self) + -> Result; /// Getting the block hash. The height is that of the block to which the /// current transaction is being applied. - fn get_block_hash(&'view self) -> Result; + fn get_block_hash(&'view self) -> Result; /// Getting the block epoch. The epoch is that of the block to which the /// current transaction is being applied. - fn get_block_epoch(&'view self) -> Result; + fn get_block_epoch(&'view self) -> Result; /// Storage prefix iterator. It will try to get an iterator from the /// storage. fn iter_prefix( &'view self, prefix: &Key, - ) -> Result; + ) -> Result; /// Evaluate a validity predicate with given data. The address, changed /// storage keys and verifiers will have the same values as the input to @@ -84,7 +82,7 @@ pub trait VpEnv<'view> { &self, vp_code: Vec, input_data: Vec, - ) -> Result; + ) -> Result; /// Verify a transaction signature. The signature is expected to have been /// produced on the encoded transaction [`crate::proto::Tx`] @@ -93,10 +91,10 @@ pub trait VpEnv<'view> { &self, pk: &common::PublicKey, sig: &common::Signature, - ) -> Result; + ) -> Result; /// Get a tx hash - fn get_tx_code_hash(&self) -> Result; + fn get_tx_code_hash(&self) -> Result; // ---- Methods below have default implementation via `pre/post` ---- @@ -105,8 +103,8 @@ pub trait VpEnv<'view> { fn read_pre( &'view self, key: &Key, - ) -> Result, Self::Error> { - self.pre().read(key).map_err(Into::into) + ) -> Result, storage_api::Error> { + self.pre().read(key) } /// Storage read prior state raw bytes (before tx execution). It @@ -114,8 +112,8 @@ pub trait VpEnv<'view> { fn read_bytes_pre( &'view self, key: &Key, - ) -> Result>, Self::Error> { - self.pre().read_bytes(key).map_err(Into::into) + ) -> Result>, storage_api::Error> { + self.pre().read_bytes(key) } /// Storage read posterior state Borsh encoded value (after tx execution). @@ -124,8 +122,8 @@ pub trait VpEnv<'view> { fn read_post( &'view self, key: &Key, - ) -> Result, Self::Error> { - self.post().read(key).map_err(Into::into) + ) -> Result, storage_api::Error> { + self.post().read(key) } /// Storage read posterior state raw bytes (after tx execution). It will try @@ -134,20 +132,23 @@ pub trait VpEnv<'view> { fn read_bytes_post( &'view self, key: &Key, - ) -> Result>, Self::Error> { - self.post().read_bytes(key).map_err(Into::into) + ) -> Result>, storage_api::Error> { + self.post().read_bytes(key) } /// Storage `has_key` in prior state (before tx execution). It will try to /// read from the storage. - fn has_key_pre(&'view self, key: &Key) -> Result { - self.pre().has_key(key).map_err(Into::into) + fn has_key_pre(&'view self, key: &Key) -> Result { + self.pre().has_key(key) } /// Storage `has_key` in posterior state (after tx execution). It will try /// to check the write log first and if no entry found then the storage. - fn has_key_post(&'view self, key: &Key) -> Result { - self.post().has_key(key).map_err(Into::into) + fn has_key_post( + &'view self, + key: &Key, + ) -> Result { + self.post().has_key(key) } /// Storage prefix iterator for prior state (before tx execution). It will @@ -155,8 +156,8 @@ pub trait VpEnv<'view> { fn iter_pre_next( &'view self, iter: &mut Self::PrefixIter, - ) -> Result)>, Self::Error> { - self.pre().iter_next(iter).map_err(Into::into) + ) -> Result)>, storage_api::Error> { + self.pre().iter_next(iter) } /// Storage prefix iterator next for posterior state (after tx execution). @@ -165,8 +166,8 @@ pub trait VpEnv<'view> { fn iter_post_next( &'view self, iter: &mut Self::PrefixIter, - ) -> Result)>, Self::Error> { - self.post().iter_next(iter).map_err(Into::into) + ) -> Result)>, storage_api::Error> { + self.post().iter_next(iter) } } @@ -190,8 +191,6 @@ pub enum RuntimeError { ReadTemporaryValueError, #[error("Trying to read a permament value with read_temp")] ReadPermanentValueError, - #[error("Storage error: {0}")] - StorageApi(storage_api::Error), } /// VP environment function result @@ -495,9 +494,3 @@ where } Ok(None) } - -impl From for RuntimeError { - fn from(err: storage_api::Error) -> Self { - Self::StorageApi(err) - } -} diff --git a/tx_prelude/src/error.rs b/tx_prelude/src/error.rs deleted file mode 100644 index ce7b9fa5e9..0000000000 --- a/tx_prelude/src/error.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! Helpers for error handling in WASM -//! -//! This module is currently duplicated in tx_prelude and vp_prelude crates to -//! be able to implement `From` conversion on error types from other crates, -//! avoiding `error[E0117]: only traits defined in the current crate can be -//! implemented for arbitrary types` - -use namada::ledger::storage_api; -use thiserror::Error; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("{0}")] - SimpleMessage(&'static str), - #[error("{0}")] - Custom(CustomError), - #[error("{0}: {1}")] - CustomWithMessage(&'static str, CustomError), -} - -/// Result of transaction or VP. -pub type EnvResult = Result; - -pub trait ResultExt { - /// Replace a possible error with a static message in [`EnvResult`]. - fn err_msg(self, msg: &'static str) -> EnvResult; -} - -// This is separate from `ResultExt`, because the implementation requires -// different bounds for `T`. -pub trait ResultExt2 { - /// Convert a [`Result`] into [`EnvResult`]. - fn into_env_result(self) -> EnvResult; - - /// Add a static message to a possible error in [`EnvResult`]. - fn wrap_err(self, msg: &'static str) -> EnvResult; -} - -pub trait OptionExt { - /// Transforms the [`Option`] into a [`EnvResult`], mapping - /// [`Some(v)`] to [`Ok(v)`] and [`None`] to the given static error - /// message. - fn ok_or_err_msg(self, msg: &'static str) -> EnvResult; -} - -impl ResultExt for Result { - fn err_msg(self, msg: &'static str) -> EnvResult { - self.map_err(|_err| Error::new_const(msg)) - } -} - -impl ResultExt2 for Result -where - E: std::error::Error + Send + Sync + 'static, -{ - fn into_env_result(self) -> EnvResult { - self.map_err(Error::new) - } - - fn wrap_err(self, msg: &'static str) -> EnvResult { - self.map_err(|err| Error::wrap(msg, err)) - } -} - -impl OptionExt for Option { - fn ok_or_err_msg(self, msg: &'static str) -> EnvResult { - self.ok_or_else(|| Error::new_const(msg)) - } -} - -impl Error { - /// Create an [`enum@Error`] from a static message. - #[inline] - pub const fn new_const(msg: &'static str) -> Self { - Self::SimpleMessage(msg) - } - - /// Create an [`enum@Error`] from another [`std::error::Error`]. - pub fn new(error: E) -> Self - where - E: Into>, - { - Self::Custom(CustomError(error.into())) - } - - /// Wrap another [`std::error::Error`] with a static message. - pub fn wrap(msg: &'static str, error: E) -> Self - where - E: Into>, - { - Self::CustomWithMessage(msg, CustomError(error.into())) - } -} - -/// A custom error -#[derive(Debug)] -pub struct CustomError(Box); - -impl std::fmt::Display for CustomError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl From for Error { - fn from(err: storage_api::Error) -> Self { - // storage_api::Error::Custom(CustomError {Box}) - // Error:Custom(storage_api::Error::Custom(CustomError {Box})) - Self::new(err) - } -} diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 4a8a3ef3a9..494d5e7cd3 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -12,13 +12,6 @@ use namada::types::token::Amount; use crate::token::transfer; use crate::Ctx; -// This is needed to use `ibc::Handler::Error` with `IbcActions` below -impl From for crate::Error { - fn from(err: Error) -> Self { - crate::Error::new(err) - } -} - impl IbcActions for Ctx { type Error = crate::Error; diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index ade290bf65..a18d59c9a8 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -6,7 +6,6 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] -mod error; pub mod governance; pub mod ibc; pub mod intent; @@ -18,12 +17,13 @@ use core::slice; use std::marker::PhantomData; pub use borsh::{BorshDeserialize, BorshSerialize}; -pub use error::*; pub use namada::ledger::governance::storage as gov_storage; pub use namada::ledger::parameters::storage as parameters_storage; pub use namada::ledger::storage::types::encode; use namada::ledger::storage_api; -pub use namada::ledger::storage_api::{StorageRead, StorageWrite}; +pub use namada::ledger::storage_api::{ + Error, OptionExt, ResultExt, StorageRead, StorageWrite, +}; pub use namada::ledger::treasury::storage as treasury_storage; pub use namada::ledger::tx_env::TxEnv; pub use namada::proto::{Signed, SignedTxData}; @@ -89,6 +89,10 @@ impl Ctx { } } +/// Result of `TxEnv`, `storage_api::StorageRead` or `storage_api::StorageWrite` +/// method call +pub type EnvResult = Result; + /// Transaction result pub type TxResult = EnvResult<()>; @@ -101,7 +105,7 @@ impl StorageRead<'_> for Ctx { fn read_bytes( &self, key: &namada::types::storage::Key, - ) -> Result>, storage_api::Error> { + ) -> Result>, Error> { let key = key.to_string(); let read_result = unsafe { anoma_tx_read(key.as_ptr() as _, key.len() as _) }; @@ -111,14 +115,14 @@ impl StorageRead<'_> for Ctx { fn has_key( &self, key: &namada::types::storage::Key, - ) -> Result { + ) -> Result { let key = key.to_string(); let found = unsafe { anoma_tx_has_key(key.as_ptr() as _, key.len() as _) }; Ok(HostEnvResult::is_success(found)) } - fn get_chain_id(&self) -> Result { + fn get_chain_id(&self) -> Result { let result = Vec::with_capacity(CHAIN_ID_LENGTH); unsafe { anoma_tx_get_chain_id(result.as_ptr() as _); @@ -131,13 +135,13 @@ impl StorageRead<'_> for Ctx { fn get_block_height( &self, - ) -> Result { + ) -> Result { Ok(BlockHeight(unsafe { anoma_tx_get_block_height() })) } fn get_block_hash( &self, - ) -> Result { + ) -> Result { let result = Vec::with_capacity(BLOCK_HASH_LENGTH); unsafe { anoma_tx_get_block_hash(result.as_ptr() as _); @@ -148,16 +152,14 @@ impl StorageRead<'_> for Ctx { Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) } - fn get_block_epoch( - &self, - ) -> Result { + fn get_block_epoch(&self) -> Result { Ok(Epoch(unsafe { anoma_tx_get_block_epoch() })) } fn iter_prefix( &self, prefix: &namada::types::storage::Key, - ) -> Result { + ) -> Result { let prefix = prefix.to_string(); let iter_id = unsafe { anoma_tx_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) @@ -168,7 +170,7 @@ impl StorageRead<'_> for Ctx { fn iter_next( &self, iter: &mut Self::PrefixIter, - ) -> Result)>, storage_api::Error> { + ) -> Result)>, Error> { let read_result = unsafe { anoma_tx_iter_next(iter.0) }; Ok(read_key_val_bytes_from_buffer( read_result, @@ -206,8 +208,6 @@ impl StorageWrite for Ctx { } impl TxEnv<'_> for Ctx { - type Error = Error; - fn get_block_time(&self) -> Result { let read_result = unsafe { anoma_tx_get_block_time() }; let time_value = read_from_buffer(read_result, anoma_tx_result_buffer) diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 65a6c3f6cd..97a258365c 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -118,34 +118,6 @@ namada::impl_pos_read_only! { impl namada_proof_of_stake::PosReadOnly for Ctx } -impl From> for Error { - fn from(err: namada_proof_of_stake::BecomeValidatorError
) -> Self { - Self::new(err) - } -} - -impl From> for Error { - fn from(err: namada_proof_of_stake::BondError
) -> Self { - Self::new(err) - } -} - -impl From> - for Error -{ - fn from( - err: namada_proof_of_stake::UnbondError, - ) -> Self { - Self::new(err) - } -} - -impl From> for Error { - fn from(err: namada_proof_of_stake::WithdrawError
) -> Self { - Self::new(err) - } -} - impl namada_proof_of_stake::PosActions for Ctx { type BecomeValidatorError = crate::Error; type BondError = crate::Error; @@ -156,7 +128,7 @@ impl namada_proof_of_stake::PosActions for Ctx { &mut self, params: &PosParams, ) -> Result<(), Self::Error> { - self.write(¶ms_key(), params).into_env_result() + self.write(¶ms_key(), params) } fn write_validator_address_raw_hash( @@ -165,7 +137,6 @@ impl namada_proof_of_stake::PosActions for Ctx { ) -> Result<(), Self::Error> { let raw_hash = address.raw_hash().unwrap().to_owned(); self.write(&validator_address_raw_hash_key(raw_hash), address) - .into_env_result() } fn write_validator_staking_reward_address( @@ -174,7 +145,6 @@ impl namada_proof_of_stake::PosActions for Ctx { value: Self::Address, ) -> Result<(), Self::Error> { self.write(&validator_staking_reward_address_key(key), &value) - .into_env_result() } fn write_validator_consensus_key( @@ -183,7 +153,6 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorConsensusKeys, ) -> Result<(), Self::Error> { self.write(&validator_consensus_key_key(key), &value) - .into_env_result() } fn write_validator_state( @@ -192,7 +161,6 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorStates, ) -> Result<(), Self::Error> { self.write(&validator_state_key(key), &value) - .into_env_result() } fn write_validator_total_deltas( @@ -201,7 +169,6 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorTotalDeltas, ) -> Result<(), Self::Error> { self.write(&validator_total_deltas_key(key), &value) - .into_env_result() } fn write_validator_voting_power( @@ -210,7 +177,6 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorVotingPowers, ) -> Result<(), Self::Error> { self.write(&validator_voting_power_key(key), &value) - .into_env_result() } fn write_bond( @@ -218,7 +184,7 @@ impl namada_proof_of_stake::PosActions for Ctx { key: &BondId, value: Bonds, ) -> Result<(), Self::Error> { - self.write(&bond_key(key), &value).into_env_result() + self.write(&bond_key(key), &value) } fn write_unbond( @@ -226,14 +192,14 @@ impl namada_proof_of_stake::PosActions for Ctx { key: &BondId, value: Unbonds, ) -> Result<(), Self::Error> { - self.write(&unbond_key(key), &value).into_env_result() + self.write(&unbond_key(key), &value) } fn write_validator_set( &mut self, value: ValidatorSets, ) -> Result<(), Self::Error> { - self.write(&validator_set_key(), &value).into_env_result() + self.write(&validator_set_key(), &value) } fn write_total_voting_power( @@ -241,15 +207,14 @@ impl namada_proof_of_stake::PosActions for Ctx { value: TotalVotingPowers, ) -> Result<(), Self::Error> { self.write(&total_voting_power_key(), &value) - .into_env_result() } fn delete_bond(&mut self, key: &BondId) -> Result<(), Self::Error> { - self.delete(&bond_key(key)).into_env_result() + self.delete(&bond_key(key)) } fn delete_unbond(&mut self, key: &BondId) -> Result<(), Self::Error> { - self.delete(&unbond_key(key)).into_env_result() + self.delete(&unbond_key(key)) } fn transfer( diff --git a/vp_prelude/src/error.rs b/vp_prelude/src/error.rs deleted file mode 100644 index 099ae2540a..0000000000 --- a/vp_prelude/src/error.rs +++ /dev/null @@ -1,110 +0,0 @@ -//! Helpers for error handling in WASM -//! -//! This module is currently duplicated in tx_prelude and vp_prelude crates to -//! be able to implement `From` conversion on error types from other crates, -//! avoiding `error[E0117]: only traits defined in the current crate can be -//! implemented for arbitrary types` - -use namada::ledger::storage_api; -use thiserror::Error; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("{0}")] - SimpleMessage(&'static str), - #[error("{0}")] - Custom(CustomError), - #[error("{0}: {1}")] - CustomWithMessage(&'static str, CustomError), -} - -/// Result of transaction or VP. -pub type EnvResult = Result; - -pub trait ResultExt { - /// Replace a possible error with a static message in [`EnvResult`]. - fn err_msg(self, msg: &'static str) -> EnvResult; -} - -// This is separate from `ResultExt`, because the implementation requires -// different bounds for `T`. -pub trait ResultExt2 { - /// Convert a [`Result`] into [`EnvResult`]. - fn into_env_result(self) -> EnvResult; - - /// Add a static message to a possible error in [`EnvResult`]. - fn wrap_err(self, msg: &'static str) -> EnvResult; -} - -pub trait OptionExt { - /// Transforms the [`Option`] into a [`EnvResult`], mapping - /// [`Some(v)`] to [`Ok(v)`] and [`None`] to the given static error - /// message. - fn ok_or_err_msg(self, msg: &'static str) -> EnvResult; -} - -impl ResultExt for Result { - fn err_msg(self, msg: &'static str) -> EnvResult { - self.map_err(|_err| Error::new_const(msg)) - } -} - -impl ResultExt2 for Result -where - E: std::error::Error + Send + Sync + 'static, -{ - fn into_env_result(self) -> EnvResult { - self.map_err(Error::new) - } - - fn wrap_err(self, msg: &'static str) -> EnvResult { - self.map_err(|err| Error::wrap(msg, err)) - } -} - -impl OptionExt for Option { - fn ok_or_err_msg(self, msg: &'static str) -> EnvResult { - self.ok_or_else(|| Error::new_const(msg)) - } -} - -impl Error { - /// Create an [`enum@Error`] from a static message. - #[inline] - pub const fn new_const(msg: &'static str) -> Self { - Self::SimpleMessage(msg) - } - - /// Create an [`enum@Error`] from another [`std::error::Error`]. - pub fn new(error: E) -> Self - where - E: Into>, - { - Self::Custom(CustomError(error.into())) - } - - /// Wrap another [`std::error::Error`] with a static message. - pub fn wrap(msg: &'static str, error: E) -> Self - where - E: Into>, - { - Self::CustomWithMessage(msg, CustomError(error.into())) - } -} - -/// A custom error -#[derive(Debug)] -pub struct CustomError(Box); - -impl std::fmt::Display for CustomError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl From for Error { - fn from(err: storage_api::Error) -> Self { - Self::new(err) - } -} diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index d1d3845a48..46432a3b52 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -6,7 +6,6 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] -mod error; pub mod intent; pub mod key; pub mod nft; @@ -20,9 +19,10 @@ use std::convert::TryFrom; use std::marker::PhantomData; pub use borsh::{BorshDeserialize, BorshSerialize}; -pub use error::*; pub use namada::ledger::governance::storage as gov_storage; -pub use namada::ledger::storage_api::{self, StorageRead}; +pub use namada::ledger::storage_api::{ + self, Error, OptionExt, ResultExt, StorageRead, +}; pub use namada::ledger::vp_env::VpEnv; pub use namada::ledger::{parameters, pos as proof_of_stake}; pub use namada::proto::{Signed, SignedTxData}; @@ -134,6 +134,9 @@ pub struct CtxPostStorageRead<'a> { _ctx: &'a Ctx, } +/// Result of `VpEnv` or `storage_api::StorageRead` method call +pub type EnvResult = Result; + /// Validity predicate result pub type VpResult = EnvResult; @@ -151,7 +154,6 @@ pub fn reject() -> VpResult { pub struct KeyValIterator(pub u64, pub PhantomData); impl<'view> VpEnv<'view> for Ctx { - type Error = Error; type Post = CtxPostStorageRead<'view>; type Pre = CtxPreStorageRead<'view>; type PrefixIter = KeyValIterator<(String, Vec)>; @@ -167,7 +169,7 @@ impl<'view> VpEnv<'view> for Ctx { fn read_temp( &self, key: &storage::Key, - ) -> Result, Self::Error> { + ) -> Result, Error> { let key = key.to_string(); let read_result = unsafe { anoma_vp_read_temp(key.as_ptr() as _, key.len() as _) }; @@ -178,46 +180,46 @@ impl<'view> VpEnv<'view> for Ctx { fn read_bytes_temp( &self, key: &storage::Key, - ) -> Result>, Self::Error> { + ) -> Result>, Error> { let key = key.to_string(); let read_result = unsafe { anoma_vp_read_temp(key.as_ptr() as _, key.len() as _) }; Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) } - fn get_chain_id(&'view self) -> Result { + fn get_chain_id(&'view self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl - get_chain_id().into_env_result() + get_chain_id() } - fn get_block_height(&'view self) -> Result { + fn get_block_height(&'view self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl - get_block_height().into_env_result() + get_block_height() } - fn get_block_hash(&'view self) -> Result { + fn get_block_hash(&'view self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl - get_block_hash().into_env_result() + get_block_hash() } - fn get_block_epoch(&'view self) -> Result { + fn get_block_epoch(&'view self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl - get_block_epoch().into_env_result() + get_block_epoch() } fn iter_prefix( &self, prefix: &storage::Key, - ) -> Result { + ) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl - iter_prefix(prefix).into_env_result() + iter_prefix(prefix) } fn eval( &self, vp_code: Vec, input_data: Vec, - ) -> Result { + ) -> Result { let result = unsafe { anoma_vp_eval( vp_code.as_ptr() as _, @@ -233,7 +235,7 @@ impl<'view> VpEnv<'view> for Ctx { &self, pk: &common::PublicKey, sig: &common::Signature, - ) -> Result { + ) -> Result { let pk = BorshSerialize::try_to_vec(pk).unwrap(); let sig = BorshSerialize::try_to_vec(sig).unwrap(); let valid = unsafe { @@ -247,7 +249,7 @@ impl<'view> VpEnv<'view> for Ctx { Ok(HostEnvResult::is_success(valid)) } - fn get_tx_code_hash(&self) -> Result { + fn get_tx_code_hash(&self) -> Result { let result = Vec::with_capacity(HASH_LENGTH); unsafe { anoma_vp_get_tx_code_hash(result.as_ptr() as _); @@ -261,17 +263,14 @@ impl<'view> VpEnv<'view> for Ctx { impl StorageRead<'_> for CtxPreStorageRead<'_> { type PrefixIter = KeyValIterator<(String, Vec)>; - fn read_bytes( - &self, - key: &storage::Key, - ) -> Result>, storage_api::Error> { + fn read_bytes(&self, key: &storage::Key) -> Result>, Error> { let key = key.to_string(); let read_result = unsafe { anoma_vp_read_pre(key.as_ptr() as _, key.len() as _) }; Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) } - fn has_key(&self, key: &storage::Key) -> Result { + fn has_key(&self, key: &storage::Key) -> Result { let key = key.to_string(); let found = unsafe { anoma_vp_has_key_pre(key.as_ptr() as _, key.len() as _) }; @@ -281,7 +280,7 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { fn iter_next( &self, iter: &mut Self::PrefixIter, - ) -> Result)>, storage_api::Error> { + ) -> Result)>, Error> { let read_result = unsafe { anoma_vp_iter_pre_next(iter.0) }; Ok(read_key_val_bytes_from_buffer( read_result, @@ -294,23 +293,23 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { fn iter_prefix( &self, prefix: &storage::Key, - ) -> Result { + ) -> Result { iter_prefix(prefix) } - fn get_chain_id(&self) -> Result { + fn get_chain_id(&self) -> Result { get_chain_id() } - fn get_block_height(&self) -> Result { + fn get_block_height(&self) -> Result { get_block_height() } - fn get_block_hash(&self) -> Result { + fn get_block_hash(&self) -> Result { get_block_hash() } - fn get_block_epoch(&self) -> Result { + fn get_block_epoch(&self) -> Result { get_block_epoch() } } @@ -318,17 +317,14 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { impl StorageRead<'_> for CtxPostStorageRead<'_> { type PrefixIter = KeyValIterator<(String, Vec)>; - fn read_bytes( - &self, - key: &storage::Key, - ) -> Result>, storage_api::Error> { + fn read_bytes(&self, key: &storage::Key) -> Result>, Error> { let key = key.to_string(); let read_result = unsafe { anoma_vp_read_post(key.as_ptr() as _, key.len() as _) }; Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) } - fn has_key(&self, key: &storage::Key) -> Result { + fn has_key(&self, key: &storage::Key) -> Result { let key = key.to_string(); let found = unsafe { anoma_vp_has_key_post(key.as_ptr() as _, key.len() as _) }; @@ -338,7 +334,7 @@ impl StorageRead<'_> for CtxPostStorageRead<'_> { fn iter_next( &self, iter: &mut Self::PrefixIter, - ) -> Result)>, storage_api::Error> { + ) -> Result)>, Error> { let read_result = unsafe { anoma_vp_iter_post_next(iter.0) }; Ok(read_key_val_bytes_from_buffer( read_result, @@ -351,30 +347,30 @@ impl StorageRead<'_> for CtxPostStorageRead<'_> { fn iter_prefix( &self, prefix: &storage::Key, - ) -> Result { + ) -> Result { iter_prefix(prefix) } - fn get_chain_id(&self) -> Result { + fn get_chain_id(&self) -> Result { get_chain_id() } - fn get_block_height(&self) -> Result { + fn get_block_height(&self) -> Result { get_block_height() } - fn get_block_hash(&self) -> Result { + fn get_block_hash(&self) -> Result { get_block_hash() } - fn get_block_epoch(&self) -> Result { + fn get_block_epoch(&self) -> Result { get_block_epoch() } } fn iter_prefix( prefix: &storage::Key, -) -> Result)>, storage_api::Error> { +) -> Result)>, Error> { let prefix = prefix.to_string(); let iter_id = unsafe { anoma_vp_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) @@ -382,7 +378,7 @@ fn iter_prefix( Ok(KeyValIterator(iter_id, PhantomData)) } -fn get_chain_id() -> Result { +fn get_chain_id() -> Result { let result = Vec::with_capacity(CHAIN_ID_LENGTH); unsafe { anoma_vp_get_chain_id(result.as_ptr() as _); @@ -395,11 +391,11 @@ fn get_chain_id() -> Result { ) } -fn get_block_height() -> Result { +fn get_block_height() -> Result { Ok(BlockHeight(unsafe { anoma_vp_get_block_height() })) } -fn get_block_hash() -> Result { +fn get_block_hash() -> Result { let result = Vec::with_capacity(BLOCK_HASH_LENGTH); unsafe { anoma_vp_get_block_hash(result.as_ptr() as _); @@ -409,6 +405,6 @@ fn get_block_hash() -> Result { Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) } -fn get_block_epoch() -> Result { +fn get_block_epoch() -> Result { Ok(Epoch(unsafe { anoma_vp_get_block_epoch() })) } diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 9b04e2a939..e880380802 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -5,10 +5,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let bond = transaction::pos::Bond::try_from_slice(&data[..]) - .err_msg("failed to decode Bond")?; + .wrap_err("failed to decode Bond")?; ctx.bond_tokens(bond.source.as_ref(), &bond.validator, bond.amount) } diff --git a/wasm/wasm_source/src/tx_from_intent.rs b/wasm/wasm_source/src/tx_from_intent.rs index a299070393..deeb5f3eb0 100644 --- a/wasm/wasm_source/src/tx_from_intent.rs +++ b/wasm/wasm_source/src/tx_from_intent.rs @@ -7,10 +7,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let tx_data = intent::IntentTransfers::try_from_slice(&data[..]) - .err_msg("failed to decode IntentTransfers")?; + .wrap_err("failed to decode IntentTransfers")?; // make sure that the matchmaker has to validate this tx ctx.insert_verifier(&tx_data.source)?; diff --git a/wasm/wasm_source/src/tx_ibc.rs b/wasm/wasm_source/src/tx_ibc.rs index 08b3c60d60..79cbc6cf96 100644 --- a/wasm/wasm_source/src/tx_ibc.rs +++ b/wasm/wasm_source/src/tx_ibc.rs @@ -8,7 +8,7 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; ctx.dispatch_ibc_action(&data) } diff --git a/wasm/wasm_source/src/tx_init_account.rs b/wasm/wasm_source/src/tx_init_account.rs index 05789751b2..e0fe700d63 100644 --- a/wasm/wasm_source/src/tx_init_account.rs +++ b/wasm/wasm_source/src/tx_init_account.rs @@ -6,10 +6,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let tx_data = transaction::InitAccount::try_from_slice(&data[..]) - .err_msg("failed to decode InitAccount")?; + .wrap_err("failed to decode InitAccount")?; debug_log!("apply_tx called to init a new established account"); let address = ctx.init_account(&tx_data.vp_code)?; diff --git a/wasm/wasm_source/src/tx_init_nft.rs b/wasm/wasm_source/src/tx_init_nft.rs index ace54fc161..de67dfbb53 100644 --- a/wasm/wasm_source/src/tx_init_nft.rs +++ b/wasm/wasm_source/src/tx_init_nft.rs @@ -5,10 +5,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let tx_data = transaction::nft::CreateNft::try_from_slice(&data[..]) - .err_msg("failed to decode CreateNft")?; + .wrap_err("failed to decode CreateNft")?; log_string("apply_tx called to create a new NFT"); let _address = nft::init_nft(ctx, tx_data)?; diff --git a/wasm/wasm_source/src/tx_init_proposal.rs b/wasm/wasm_source/src/tx_init_proposal.rs index 728d7613ae..cb7fe9ffbb 100644 --- a/wasm/wasm_source/src/tx_init_proposal.rs +++ b/wasm/wasm_source/src/tx_init_proposal.rs @@ -5,11 +5,11 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let tx_data = transaction::governance::InitProposalData::try_from_slice(&data[..]) - .err_msg("failed to decode InitProposalData")?; + .wrap_err("failed to decode InitProposalData")?; log_string("apply_tx called to create a new governance proposal"); governance::init_proposal(ctx, tx_data) diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index 2bfed44fac..2d5f1a6256 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -7,10 +7,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let init_validator = InitValidator::try_from_slice(&data[..]) - .err_msg("failed to decode InitValidator")?; + .wrap_err("failed to decode InitValidator")?; debug_log!("apply_tx called to init a new validator account"); // Register the validator in PoS diff --git a/wasm/wasm_source/src/tx_mint_nft.rs b/wasm/wasm_source/src/tx_mint_nft.rs index f132b74158..d3ab17e7ad 100644 --- a/wasm/wasm_source/src/tx_mint_nft.rs +++ b/wasm/wasm_source/src/tx_mint_nft.rs @@ -5,10 +5,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let tx_data = transaction::nft::MintNft::try_from_slice(&data[..]) - .err_msg("failed to decode MintNft")?; + .wrap_err("failed to decode MintNft")?; log_string("apply_tx called to mint a new NFT tokens"); nft::mint_tokens(ctx, tx_data) diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index 5731612888..eccddee2f0 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -7,10 +7,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let transfer = token::Transfer::try_from_slice(&data[..]) - .err_msg("failed to decode token::Transfer")?; + .wrap_err("failed to decode token::Transfer")?; debug_log!("apply_tx called with transfer: {:#?}", transfer); let token::Transfer { source, diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index c5ffc1ab6e..5d1765bb38 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -6,10 +6,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let unbond = transaction::pos::Unbond::try_from_slice(&data[..]) - .err_msg("failed to decode Unbond")?; + .wrap_err("failed to decode Unbond")?; ctx.unbond_tokens(unbond.source.as_ref(), &unbond.validator, unbond.amount) } diff --git a/wasm/wasm_source/src/tx_update_vp.rs b/wasm/wasm_source/src/tx_update_vp.rs index d0c41d3bd9..0bb819f026 100644 --- a/wasm/wasm_source/src/tx_update_vp.rs +++ b/wasm/wasm_source/src/tx_update_vp.rs @@ -7,10 +7,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let update_vp = transaction::UpdateVp::try_from_slice(&data[..]) - .err_msg("failed to decode UpdateVp")?; + .wrap_err("failed to decode UpdateVp")?; debug_log!("update VP for: {:#?}", update_vp.addr); diff --git a/wasm/wasm_source/src/tx_vote_proposal.rs b/wasm/wasm_source/src/tx_vote_proposal.rs index 614e4a9fa1..92c7af4c7f 100644 --- a/wasm/wasm_source/src/tx_vote_proposal.rs +++ b/wasm/wasm_source/src/tx_vote_proposal.rs @@ -5,11 +5,11 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let tx_data = transaction::governance::VoteProposalData::try_from_slice(&data[..]) - .err_msg("failed to decode VoteProposalData")?; + .wrap_err("failed to decode VoteProposalData")?; debug_log!("apply_tx called to vote a governance proposal"); diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index bcb64b4af0..3c841d88b0 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -6,10 +6,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let withdraw = transaction::pos::Withdraw::try_from_slice(&data[..]) - .err_msg("failed to decode Withdraw")?; + .wrap_err("failed to decode Withdraw")?; let slashed = ctx.withdraw_tokens(withdraw.source.as_ref(), &withdraw.validator)?; diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 2b6ef24242..4731ef60be 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -88,7 +88,7 @@ pub mod main { #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = match signed.data { Some(data) => { log(&format!("got data ({} bytes)", data.len())); @@ -134,7 +134,7 @@ pub mod main { #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let transfer = token::Transfer::try_from_slice(&signed.data.unwrap()[..]).unwrap(); log_string(format!("apply_tx called to mint tokens: {:#?}", transfer)); From 45dddbeaed67c8bbc2720c8b7109a4e7dc0fd971 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 13 Sep 2022 15:51:26 +0000 Subject: [PATCH 036/197] [ci skip] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 1c5fec452f..06f33fcda7 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.078d5be45d839fc1b6c034c4fdfe2055add709daa05868826682d3b6abf27ac4.wasm", - "tx_from_intent.wasm": "tx_from_intent.dcdfc49a02c76f277afac84e3511ad47c70b3bf54d1273a15c6dc75648316937.wasm", - "tx_ibc.wasm": "tx_ibc.3957053e0ea9caaa49128994711339aea6ede77a99f150688df3de870c8b17d4.wasm", - "tx_init_account.wasm": "tx_init_account.92e59887817a6d789dc8a85bb1eff3c86bd0a9bd1ddc8a9e0e5de5c9e9c2ddc6.wasm", - "tx_init_nft.wasm": "tx_init_nft.4125bdf149533cd201895cfaf6cdfbb9616182c187137029fe3c9214030f1877.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.8da848905d5a8ad1b15046d5e59e04f307bde73dcc8d2ab0b6d8d235a31a8b52.wasm", - "tx_init_validator.wasm": "tx_init_validator.364786e78253bd9ce72d089cc1539a882eb8ef6fd8c818616d003241b47ac010.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.b2c34b21a2f0b533e3dcd4b2ee64e45ca00ccad8b3f22c410474c7a67c3302e8.wasm", - "tx_transfer.wasm": "tx_transfer.6fac9a68bcd1a50d9cec64605f8637dfa897ce3be242edc344858cf4075fc100.wasm", - "tx_unbond.wasm": "tx_unbond.eeaf8ff32984275288b0a9a36c9579dace6f3ecfaf59255af769acf57a00df4a.wasm", - "tx_update_vp.wasm": "tx_update_vp.a304b3c70361cde5fda458ba5637d6825eb002198e73990a1c74351113b83d43.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.8f306e1d1bcc9ca7f47a39108554fc847d4ac6df7a8d87a6effdffeae6adc4c4.wasm", - "tx_withdraw.wasm": "tx_withdraw.c288861e842f0b1ac6fbb95b14b33c8819ac587bbd5b483f2cdd0ea184206c65.wasm", - "vp_nft.wasm": "vp_nft.379f0a9fdbc9611ba9afc8b03ea17eb1e7c63992be3c2ecd5dd506a0ec3809f3.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.9d28df0e4eea55c98ac05638cfc6211aaf8e1a4b9489f7057634c66d39835c36.wasm", - "vp_token.wasm": "vp_token.6506aea021bb6de9186e96fa0d9ea2ad35bcb66777d6ecf890a66cfe36a74f23.wasm", - "vp_user.wasm": "vp_user.77a0d0d406e300b2d5b9bc1c13bc50f233b6923d369db939ac82c8e10b64543c.wasm" + "tx_bond.wasm": "tx_bond.b3529e7dcdaf314353fa43b04eba22c44deb6143f8243933f66d20554565fa51.wasm", + "tx_from_intent.wasm": "tx_from_intent.6985cd22aa16334b008262beda3051d16f0522e019ba178c20b2cf0c92ef3cf5.wasm", + "tx_ibc.wasm": "tx_ibc.3ffa1662cb15d178be631adaeb060e96deaa44dfca8ee2978fc7d9ea09776977.wasm", + "tx_init_account.wasm": "tx_init_account.81c82e4f85575244dd833f8de566844de926f8869f1cce1dd86a1d69fe527a2f.wasm", + "tx_init_nft.wasm": "tx_init_nft.cde4c8c381369a61c6f0363c6748118d064202541c41c69c4e534f3ebbba2567.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.bfc77114ad453d9791d115fd97d681f4bbba956c77fcdb97c619637bb807a7c8.wasm", + "tx_init_validator.wasm": "tx_init_validator.7ecc353a6668788e01aac3a5799e9baa7f83979cdb8c82011225a53f1952672d.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.3bc71754956503b44f764dc27cd51796e0613435864dc3f10d089e3b99bda21e.wasm", + "tx_transfer.wasm": "tx_transfer.38b4c4ec9d949b72e51547ab55737c42db383f5e04385cffafccb36fa5a5dccb.wasm", + "tx_unbond.wasm": "tx_unbond.174499645c2aa9242f29050406a0c835d78e6c030b1f7978b5d5d1a602e88e99.wasm", + "tx_update_vp.wasm": "tx_update_vp.4ee18fa789b30215220f6edfd317314abb575baab34e9b0147afa65cbfec4543.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.17c3d54babf760367f63609f4e7aa6cf4500b80a5ff01c2917b54b4cfdf78df4.wasm", + "tx_withdraw.wasm": "tx_withdraw.010c8141fe8dfe470887d80f77db482ddc7081e318496180208490226060787e.wasm", + "vp_nft.wasm": "vp_nft.bfc7e3a5a33226ee19111cc0cc627da4da7db3d863ce1955d195873b36ba6374.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.b8d0069f0a287c57513b64daab378084d49c7e2cd52c44b0f5aa1a3ded262c88.wasm", + "vp_token.wasm": "vp_token.9f27451dba52d022ff02bbdf2421895e285e79ce2867dd1193a27fbfd2bff4fc.wasm", + "vp_user.wasm": "vp_user.eac794136d3c148b9997cc4e6fb458dcfc5e7c1f2edea49dad7c8b6d35b9c306.wasm" } \ No newline at end of file From b9406a224ddc314ee2e4e00e49b8500a30ef7eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 13 Sep 2022 18:01:44 +0200 Subject: [PATCH 037/197] fixup! Merge branch 'namada/tomas/sorted-prefix-iter' (#458) --- shared/src/types/key/secp256k1.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 99bcbb3f67..889b4de258 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -7,6 +7,7 @@ use std::io::{ErrorKind, Write}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::de::{Error, SeqAccess, Visitor}; @@ -113,7 +114,7 @@ impl Ord for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.serialize_compressed())) + write!(f, "{}", HEXLOWER.encode(&self.0.serialize_compressed())) } } @@ -121,7 +122,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_bytes()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -226,7 +229,7 @@ impl BorshSchema for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.serialize())) + write!(f, "{}", HEXLOWER.encode(&self.0.serialize())) } } @@ -234,7 +237,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_bytes()) + .map_err(ParseSecretKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParseSecretKeyError::InvalidEncoding) } From 16d9aa6c8ad6b3f1b8fc4be1ecba6f9b31f17f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 13 Sep 2022 18:42:46 +0200 Subject: [PATCH 038/197] changelog: add #465 --- .../unreleased/improvements/465-vp-tx-env-conrete-error.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/465-vp-tx-env-conrete-error.md diff --git a/.changelog/unreleased/improvements/465-vp-tx-env-conrete-error.md b/.changelog/unreleased/improvements/465-vp-tx-env-conrete-error.md new file mode 100644 index 0000000000..e40ff76a17 --- /dev/null +++ b/.changelog/unreleased/improvements/465-vp-tx-env-conrete-error.md @@ -0,0 +1,2 @@ +- Re-use `storage_api::Error` type that supports wrapping custom error in `VpEnv` and `TxEnv` traits. + ([#465](https://github.com/anoma/namada/pull/465)) From a0f193ad8fc21470646c45c049d563f40463bb2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 30 Aug 2022 15:16:48 +0200 Subject: [PATCH 039/197] rustdoc: resolve ambiguous link --- shared/src/ledger/storage_api/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/storage_api/error.rs b/shared/src/ledger/storage_api/error.rs index 8af95be723..f99539bc87 100644 --- a/shared/src/ledger/storage_api/error.rs +++ b/shared/src/ledger/storage_api/error.rs @@ -17,7 +17,7 @@ pub enum Error { /// Result of a storage API call. pub type Result = std::result::Result; -/// Result extension to easily wrap custom errors into [`Error`]. +/// Result extension to easily wrap custom errors into [`enum@Error`]. // This is separate from `ResultExt`, because the implementation requires // different bounds for `T`. pub trait ResultExt { From d7c30ac20f2d2c391eeb029f0a2092d0b2d4ed48 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 12 Aug 2022 16:08:07 -0400 Subject: [PATCH 040/197] create lazy data structures for storage access --- shared/src/ledger/storage_api/collections/mod.rs | 12 ++++++++++++ shared/src/ledger/storage_api/mod.rs | 1 + 2 files changed, 13 insertions(+) create mode 100644 shared/src/ledger/storage_api/collections/mod.rs diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs new file mode 100644 index 0000000000..982ebbd99a --- /dev/null +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -0,0 +1,12 @@ +//! Lazy data structures for storage access where elements are not all loaded +//! into memory. This serves to minimize gas costs, avoid unbounded iteration +//! in some cases, and ease the validation of storage changes in the VP. +//! +//! Rather than finding the diff of the state before and after, the VP will +//! just receive the storage sub-keys that have experienced changes. +//! +//! CONTINUE TO UPDATE THE ABOVE + +pub mod lazy_map; +pub mod lazy_set; +pub mod lazy_vec; \ No newline at end of file diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index 06ec8361f5..5e8c570cd3 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -2,6 +2,7 @@ //! and VPs (both native and WASM). mod error; +pub mod collections; use borsh::{BorshDeserialize, BorshSerialize}; pub use error::{CustomError, Error, OptionExt, Result, ResultExt}; From b07b84495cb01df90f2e1849bb38fa7a8b2523c6 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 12 Aug 2022 16:08:35 -0400 Subject: [PATCH 041/197] add lazy vector --- .../storage_api/collections/lazy_vec.rs | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 shared/src/ledger/storage_api/collections/lazy_vec.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs new file mode 100644 index 0000000000..fe97683c49 --- /dev/null +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -0,0 +1,86 @@ +//! Lazy vec + +use std::marker::PhantomData; + +use borsh::{BorshSerialize, BorshDeserialize}; +use crate::{types::{storage}, ledger::storage_api::{StorageWrite, StorageRead}}; +use super::super::Result; + +/// Subkey pointing to the length of the LazyVec +pub const LEN_SUBKEY: &str = "len"; +/// Subkey corresponding to the data elements of the LazyVec +pub const DATA_SUBKEY: &str = "data"; + +/// LazyVec ! fill in ! +pub struct LazyVec { + key: storage::Key, + phantom: PhantomData, +} + + +impl LazyVec where T: BorshSerialize + BorshDeserialize { + + /// new + pub fn new(key: storage::Key) -> Self { + Self { key, phantom: PhantomData} + } + + /// push + pub fn push(&self, val: T, storage_read: &impl StorageRead, storage_write: &mut impl StorageWrite) -> Result<()> { + let len = self.read_len(storage_read)?; + + let sub_index = len.unwrap_or(0); + let len = sub_index + 1; + + let data_key = self.get_data_key(sub_index); + + storage_write.write(&data_key, val)?; + storage_write.write(&self.get_len_key(), len)?; + + Ok(()) + } + + /// pop + pub fn pop(&self, storage_read: &impl StorageRead, storage_write: &mut impl StorageWrite) -> Result> { + let len = self.read_len(storage_read)?; + match len { + Some(0) | None => Ok(None), + Some(len) => { + let sub_index = len - 1; + let data_key = self.get_data_key(sub_index); + if len == 1 { + storage_write.delete(&self.get_len_key())?; + + } else { + storage_write.write(&self.get_len_key(), sub_index)?; + + } + let popped_val = storage_read.read(&data_key)?; + storage_write.delete(&data_key)?; + Ok(popped_val) + }, + } + + } + + /// get the length subkey + fn get_len_key(&self) -> storage::Key { + self.key.push(&LEN_SUBKEY.to_owned()).unwrap() + } + + /// read the length of the LazyVec + pub fn read_len(&self, storage_read: &impl StorageRead) -> Result> { + storage_read.read(&self.get_len_key()) + } + + /// get the data subkey + fn get_data_key(&self, sub_index: u64) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap().push(&sub_index.to_string()).unwrap() + } + + /// get the data held at a specific index within the data subkey + fn get(&self, sub_index: u64, storage_read: &impl StorageRead) -> Result> { + storage_read.read(&self.get_data_key(sub_index)) + } + +} \ No newline at end of file From 13173b3c3c102d945e2467d338c8ee989a19e6c7 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 12 Aug 2022 17:09:59 -0400 Subject: [PATCH 042/197] add lazy set (WIP), make LazyVec.get public --- .../storage_api/collections/lazy_set.rs | 69 +++++++++++++++++++ .../storage_api/collections/lazy_vec.rs | 2 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 shared/src/ledger/storage_api/collections/lazy_set.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs new file mode 100644 index 0000000000..143f234e3e --- /dev/null +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -0,0 +1,69 @@ +//! Lazy hash set + +use std::{marker::PhantomData, hash::Hash, hash::Hasher}; +use borsh::{BorshSerialize, BorshDeserialize}; +use std::collections::hash_map::DefaultHasher; + +use crate::{types::{storage}, ledger::storage_api::{StorageWrite, StorageRead}}; +use super::super::Result; + +/// Subkey corresponding to the data elements of the LazyVec +pub const DATA_SUBKEY: &str = "data"; + +/// lazy hash set +pub struct LazySet { + key: storage::Key, + phantom: PhantomData +} + +impl LazySet where T: BorshSerialize + BorshDeserialize + Hash { + + /// new + pub fn new(key: storage::Key) -> Self { + Self { key, phantom: PhantomData} + } + + /// insert + pub fn insert(&self, val: &T, storage_write: &mut impl StorageWrite) -> Result<()> { + + // Do we need to read to see if this val is already in the set? + + let data_key = self.get_data_key(val); + storage_write.write(&data_key, &val)?; + Ok(()) + } + + /// remove + pub fn remove(&self, val: &T, storage_write: &mut impl StorageWrite) -> Result<()> { + let data_key = self.get_data_key(val); + storage_write.delete(&data_key)?; + Ok(()) + } + + /// check if the hash set contains a value + pub fn contains(&self, val: &T, storage_read: &impl StorageRead) -> Result { + let digest: Option = storage_read.read(&self.get_data_key(val))?; + match digest { + Some(_) => Ok(true), + None => Ok(false), + } + } + + /// check if hash set is empty + pub fn is_empty(&self) { + todo!(); + } + + fn hash_val(&self, val: &T) -> u64 { + let mut hasher = DefaultHasher::new(); + val.hash(&mut hasher); + hasher.finish() + } + + /// get the data subkey + fn get_data_key(&self, val: &T) -> storage::Key { + let hash_str = self.hash_val(val).to_string(); + self.key.push(&DATA_SUBKEY.to_owned()).unwrap().push(&hash_str).unwrap() + } + +} \ No newline at end of file diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index fe97683c49..31bc7e98db 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -79,7 +79,7 @@ impl LazyVec where T: BorshSerialize + BorshDeserialize { } /// get the data held at a specific index within the data subkey - fn get(&self, sub_index: u64, storage_read: &impl StorageRead) -> Result> { + pub fn get(&self, sub_index: u64, storage_read: &impl StorageRead) -> Result> { storage_read.read(&self.get_data_key(sub_index)) } From 72e48e69fad7aca0c9a3f0e1292a3ae7fe3d1059 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 14 Aug 2022 03:15:02 -0400 Subject: [PATCH 043/197] lazy hash map first commit --- .../storage_api/collections/lazy_map.rs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 shared/src/ledger/storage_api/collections/lazy_map.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs new file mode 100644 index 0000000000..321ef6362d --- /dev/null +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -0,0 +1,68 @@ +//! Lazy map + +use borsh::{BorshSerialize, BorshDeserialize}; +use std::{marker::PhantomData, hash::Hash, hash::Hasher}; +use std::collections::hash_map::DefaultHasher; + +use crate::{types::{storage}, ledger::storage_api::{StorageWrite, StorageRead}}; +use super::super::Result; + + +/// Subkey corresponding to the data elements of the LazyMap +pub const DATA_SUBKEY: &str = "data"; + +/// LazyMap ! fill in ! +pub struct LazyMap { + key: storage::Key, + phantom_h: PhantomData, + phantom_t: PhantomData +} + +impl LazyMap where H: BorshDeserialize + BorshSerialize + Hash, +T: BorshDeserialize + BorshSerialize { + + /// insert + pub fn insert(&self, elem_key: &H, elem_val: T, storage_write: &mut impl StorageWrite) -> Result<()> { + + // TODO: Check to see if map element exists already ?? + + let data_key = self.get_data_key(elem_key); + storage_write.write(&data_key, (elem_key, elem_val))?; + + + Ok(()) + } + + /// remove + pub fn remove(&self, elem_key: &H, storage_write: &mut impl StorageWrite) -> Result<()> { + + let data_key = self.get_data_key(elem_key); + storage_write.delete(&data_key)?; + + Ok(()) + } + + /// get value + pub fn get_val(&self, elem_key: &H, storage_read: &mut impl StorageRead) -> Result> { + + // check if elem_key exists in the first place? + + let data_key = self.get_data_key(elem_key); + storage_read.read(&data_key) + + } + + /// hash + fn hash(&self, elem_key: &H) -> u64 { + let mut hasher = DefaultHasher::new(); + elem_key.hash(&mut hasher); + hasher.finish() + } + + /// get the data subkey + fn get_data_key(&self, elem_key: &H) -> storage::Key { + let hash_str = self.hash(elem_key).to_string(); + self.key.push(&DATA_SUBKEY.to_owned()).unwrap().push(&hash_str).unwrap() + } + +} \ No newline at end of file From d964880226ab41de852c6986a39265dd05b78de7 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 14 Aug 2022 04:38:32 -0400 Subject: [PATCH 044/197] add fn get_elem_key_by_hash to LazyMap --- .../storage_api/collections/lazy_map.rs | 76 ++++++++++++++----- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 321ef6362d..731cafd3b1 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -1,12 +1,14 @@ //! Lazy map -use borsh::{BorshSerialize, BorshDeserialize}; -use std::{marker::PhantomData, hash::Hash, hash::Hasher}; use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; -use crate::{types::{storage}, ledger::storage_api::{StorageWrite, StorageRead}}; -use super::super::Result; +use borsh::{BorshDeserialize, BorshSerialize}; +use super::super::Result; +use crate::ledger::storage_api::{StorageRead, StorageWrite}; +use crate::types::storage; /// Subkey corresponding to the data elements of the LazyMap pub const DATA_SUBKEY: &str = "data"; @@ -15,27 +17,35 @@ pub const DATA_SUBKEY: &str = "data"; pub struct LazyMap { key: storage::Key, phantom_h: PhantomData, - phantom_t: PhantomData + phantom_t: PhantomData, } -impl LazyMap where H: BorshDeserialize + BorshSerialize + Hash, -T: BorshDeserialize + BorshSerialize { - +impl LazyMap +where + H: BorshDeserialize + BorshSerialize + Hash, + T: BorshDeserialize + BorshSerialize, +{ /// insert - pub fn insert(&self, elem_key: &H, elem_val: T, storage_write: &mut impl StorageWrite) -> Result<()> { - + pub fn insert( + &self, + elem_key: &H, + elem_val: T, + storage_write: &mut impl StorageWrite, + ) -> Result<()> { // TODO: Check to see if map element exists already ?? let data_key = self.get_data_key(elem_key); storage_write.write(&data_key, (elem_key, elem_val))?; - Ok(()) } /// remove - pub fn remove(&self, elem_key: &H, storage_write: &mut impl StorageWrite) -> Result<()> { - + pub fn remove( + &self, + elem_key: &H, + storage_write: &mut impl StorageWrite, + ) -> Result<()> { let data_key = self.get_data_key(elem_key); storage_write.delete(&data_key)?; @@ -43,13 +53,38 @@ T: BorshDeserialize + BorshSerialize { } /// get value - pub fn get_val(&self, elem_key: &H, storage_read: &mut impl StorageRead) -> Result> { - + pub fn get( + &self, + elem_key: &H, + storage_read: &mut impl StorageRead, + ) -> Result> { // check if elem_key exists in the first place? let data_key = self.get_data_key(elem_key); - storage_read.read(&data_key) + let res: Option<(H, T)> = storage_read.read(&data_key)?; + match res { + Some(pair) => Ok(Some(pair.1)), + None => Ok(None), + } + } + /// get the element key by its hash + pub fn get_elem_key_by_hash( + &self, + elem_key_hash: &str, + storage_read: &mut impl StorageRead, + ) -> Result> { + let data_key = self + .key + .push(&DATA_SUBKEY.to_owned()) + .unwrap() + .push(&elem_key_hash.to_string()) + .unwrap(); + let res: Option<(H, T)> = storage_read.read(&data_key)?; + match res { + Some(pair) => Ok(Some(pair.0)), + None => Ok(None), + } } /// hash @@ -62,7 +97,10 @@ T: BorshDeserialize + BorshSerialize { /// get the data subkey fn get_data_key(&self, elem_key: &H) -> storage::Key { let hash_str = self.hash(elem_key).to_string(); - self.key.push(&DATA_SUBKEY.to_owned()).unwrap().push(&hash_str).unwrap() + self.key + .push(&DATA_SUBKEY.to_owned()) + .unwrap() + .push(&hash_str) + .unwrap() } - -} \ No newline at end of file +} From 6752132b06f2128a5e26360e04173ef53a0fc4a0 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 14 Aug 2022 04:42:40 -0400 Subject: [PATCH 045/197] fmt && clippy --- .../storage_api/collections/lazy_set.rs | 59 +++++++++++++------ .../storage_api/collections/lazy_vec.rs | 58 ++++++++++++------ .../src/ledger/storage_api/collections/mod.rs | 6 +- shared/src/ledger/storage_api/mod.rs | 2 +- 4 files changed, 85 insertions(+), 40 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 143f234e3e..187f5b5123 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -1,31 +1,42 @@ //! Lazy hash set -use std::{marker::PhantomData, hash::Hash, hash::Hasher}; -use borsh::{BorshSerialize, BorshDeserialize}; use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; + +use borsh::{BorshDeserialize, BorshSerialize}; -use crate::{types::{storage}, ledger::storage_api::{StorageWrite, StorageRead}}; use super::super::Result; +use crate::ledger::storage_api::{StorageRead, StorageWrite}; +use crate::types::storage; -/// Subkey corresponding to the data elements of the LazyVec +/// Subkey corresponding to the data elements of the LazySet pub const DATA_SUBKEY: &str = "data"; /// lazy hash set pub struct LazySet { key: storage::Key, - phantom: PhantomData + phantom: PhantomData, } -impl LazySet where T: BorshSerialize + BorshDeserialize + Hash { - +impl LazySet +where + T: BorshSerialize + BorshDeserialize + Hash, +{ /// new pub fn new(key: storage::Key) -> Self { - Self { key, phantom: PhantomData} - } + Self { + key, + phantom: PhantomData, + } + } /// insert - pub fn insert(&self, val: &T, storage_write: &mut impl StorageWrite) -> Result<()> { - + pub fn insert( + &self, + val: &T, + storage_write: &mut impl StorageWrite, + ) -> Result<()> { // Do we need to read to see if this val is already in the set? let data_key = self.get_data_key(val); @@ -34,14 +45,22 @@ impl LazySet where T: BorshSerialize + BorshDeserialize + Hash { } /// remove - pub fn remove(&self, val: &T, storage_write: &mut impl StorageWrite) -> Result<()> { + pub fn remove( + &self, + val: &T, + storage_write: &mut impl StorageWrite, + ) -> Result<()> { let data_key = self.get_data_key(val); storage_write.delete(&data_key)?; Ok(()) } /// check if the hash set contains a value - pub fn contains(&self, val: &T, storage_read: &impl StorageRead) -> Result { + pub fn contains( + &self, + val: &T, + storage_read: &impl StorageRead, + ) -> Result { let digest: Option = storage_read.read(&self.get_data_key(val))?; match digest { Some(_) => Ok(true), @@ -49,7 +68,8 @@ impl LazySet where T: BorshSerialize + BorshDeserialize + Hash { } } - /// check if hash set is empty + /// check if hash set is empty (if we want to do this we prob need a length + /// field) pub fn is_empty(&self) { todo!(); } @@ -60,10 +80,13 @@ impl LazySet where T: BorshSerialize + BorshDeserialize + Hash { hasher.finish() } - /// get the data subkey + /// get the data subkey fn get_data_key(&self, val: &T) -> storage::Key { let hash_str = self.hash_val(val).to_string(); - self.key.push(&DATA_SUBKEY.to_owned()).unwrap().push(&hash_str).unwrap() + self.key + .push(&DATA_SUBKEY.to_owned()) + .unwrap() + .push(&hash_str) + .unwrap() } - -} \ No newline at end of file +} diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index 31bc7e98db..4c08f906d9 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -2,9 +2,11 @@ use std::marker::PhantomData; -use borsh::{BorshSerialize, BorshDeserialize}; -use crate::{types::{storage}, ledger::storage_api::{StorageWrite, StorageRead}}; +use borsh::{BorshDeserialize, BorshSerialize}; + use super::super::Result; +use crate::ledger::storage_api::{StorageRead, StorageWrite}; +use crate::types::storage; /// Subkey pointing to the length of the LazyVec pub const LEN_SUBKEY: &str = "len"; @@ -17,16 +19,25 @@ pub struct LazyVec { phantom: PhantomData, } - -impl LazyVec where T: BorshSerialize + BorshDeserialize { - +impl LazyVec +where + T: BorshSerialize + BorshDeserialize, +{ /// new pub fn new(key: storage::Key) -> Self { - Self { key, phantom: PhantomData} + Self { + key, + phantom: PhantomData, + } } /// push - pub fn push(&self, val: T, storage_read: &impl StorageRead, storage_write: &mut impl StorageWrite) -> Result<()> { + pub fn push( + &self, + val: T, + storage_read: &impl StorageRead, + storage_write: &mut impl StorageWrite, + ) -> Result<()> { let len = self.read_len(storage_read)?; let sub_index = len.unwrap_or(0); @@ -36,12 +47,16 @@ impl LazyVec where T: BorshSerialize + BorshDeserialize { storage_write.write(&data_key, val)?; storage_write.write(&self.get_len_key(), len)?; - + Ok(()) } /// pop - pub fn pop(&self, storage_read: &impl StorageRead, storage_write: &mut impl StorageWrite) -> Result> { + pub fn pop( + &self, + storage_read: &impl StorageRead, + storage_write: &mut impl StorageWrite, + ) -> Result> { let len = self.read_len(storage_read)?; match len { Some(0) | None => Ok(None), @@ -50,17 +65,14 @@ impl LazyVec where T: BorshSerialize + BorshDeserialize { let data_key = self.get_data_key(sub_index); if len == 1 { storage_write.delete(&self.get_len_key())?; - } else { storage_write.write(&self.get_len_key(), sub_index)?; - } let popped_val = storage_read.read(&data_key)?; storage_write.delete(&data_key)?; Ok(popped_val) - }, + } } - } /// get the length subkey @@ -69,18 +81,28 @@ impl LazyVec where T: BorshSerialize + BorshDeserialize { } /// read the length of the LazyVec - pub fn read_len(&self, storage_read: &impl StorageRead) -> Result> { + pub fn read_len( + &self, + storage_read: &impl StorageRead, + ) -> Result> { storage_read.read(&self.get_len_key()) } /// get the data subkey fn get_data_key(&self, sub_index: u64) -> storage::Key { - self.key.push(&DATA_SUBKEY.to_owned()).unwrap().push(&sub_index.to_string()).unwrap() + self.key + .push(&DATA_SUBKEY.to_owned()) + .unwrap() + .push(&sub_index.to_string()) + .unwrap() } /// get the data held at a specific index within the data subkey - pub fn get(&self, sub_index: u64, storage_read: &impl StorageRead) -> Result> { + pub fn get( + &self, + sub_index: u64, + storage_read: &impl StorageRead, + ) -> Result> { storage_read.read(&self.get_data_key(sub_index)) } - -} \ No newline at end of file +} diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index 982ebbd99a..c3784c2525 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -1,12 +1,12 @@ //! Lazy data structures for storage access where elements are not all loaded //! into memory. This serves to minimize gas costs, avoid unbounded iteration //! in some cases, and ease the validation of storage changes in the VP. -//! +//! //! Rather than finding the diff of the state before and after, the VP will //! just receive the storage sub-keys that have experienced changes. -//! +//! //! CONTINUE TO UPDATE THE ABOVE pub mod lazy_map; pub mod lazy_set; -pub mod lazy_vec; \ No newline at end of file +pub mod lazy_vec; diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index 5e8c570cd3..8cb04434e2 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -1,8 +1,8 @@ //! The common storage read trait is implemented in the storage, client RPC, tx //! and VPs (both native and WASM). -mod error; pub mod collections; +mod error; use borsh::{BorshDeserialize, BorshSerialize}; pub use error::{CustomError, Error, OptionExt, Result, ResultExt}; From 68cd35a5475ea80e0720d711906632ac208bf07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 15 Aug 2022 14:09:19 +0200 Subject: [PATCH 046/197] refactored lazy collections, replaced hasher, added iter --- .../ledger/storage_api/collections/hasher.rs | 9 + .../storage_api/collections/lazy_map.rs | 202 ++++++++++++------ .../storage_api/collections/lazy_set.rs | 116 ++++++---- .../storage_api/collections/lazy_vec.rs | 147 +++++++------ .../src/ledger/storage_api/collections/mod.rs | 1 + 5 files changed, 300 insertions(+), 175 deletions(-) create mode 100644 shared/src/ledger/storage_api/collections/hasher.rs diff --git a/shared/src/ledger/storage_api/collections/hasher.rs b/shared/src/ledger/storage_api/collections/hasher.rs new file mode 100644 index 0000000000..0f864259f5 --- /dev/null +++ b/shared/src/ledger/storage_api/collections/hasher.rs @@ -0,0 +1,9 @@ +use borsh::BorshSerialize; + +/// Hash borsh encoded data into a storage sub-key. +/// This is a sha256 as an uppercase hexadecimal string. +pub fn hash_for_storage_key(data: impl BorshSerialize) -> String { + let bytes = data.try_to_vec().unwrap(); + let hash = crate::types::hash::Hash::sha256(bytes); + hash.to_string() +} diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 731cafd3b1..25b4cd4d5d 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -1,106 +1,170 @@ //! Lazy map -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; -use crate::ledger::storage_api::{StorageRead, StorageWrite}; +use super::hasher::hash_for_storage_key; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::storage; /// Subkey corresponding to the data elements of the LazyMap pub const DATA_SUBKEY: &str = "data"; /// LazyMap ! fill in ! -pub struct LazyMap { +pub struct LazyMap { key: storage::Key, - phantom_h: PhantomData, - phantom_t: PhantomData, + phantom_h: PhantomData, + phantom_t: PhantomData, } -impl LazyMap +#[derive(Debug, BorshSerialize, BorshDeserialize)] +struct KeyVal { + key: K, + val: V, +} + +impl LazyMap where - H: BorshDeserialize + BorshSerialize + Hash, - T: BorshDeserialize + BorshSerialize, + K: BorshDeserialize + BorshSerialize, + V: BorshDeserialize + BorshSerialize, { - /// insert - pub fn insert( + /// Create or use an existing map with the given storage `key`. + pub fn new(key: storage::Key) -> Self { + Self { + key, + phantom_h: PhantomData, + phantom_t: PhantomData, + } + } + + /// Inserts a key-value pair into the map. + /// + /// If the map did not have this key present, `None` is returned. + /// If the map did have this key present, the value is updated, and the old + /// value is returned. Unlike in `std::collection::HashMap`, the key is also + /// updated; this matters for types that can be `==` without being + /// identical. + pub fn insert( &self, - elem_key: &H, - elem_val: T, - storage_write: &mut impl StorageWrite, - ) -> Result<()> { - // TODO: Check to see if map element exists already ?? + storage: &mut S, + key: K, + val: V, + ) -> Result> + where + S: StorageWrite + StorageRead, + { + let previous = self.get(storage, &key)?; - let data_key = self.get_data_key(elem_key); - storage_write.write(&data_key, (elem_key, elem_val))?; + let data_key = self.get_data_key(&key); + Self::write_key_val(storage, &data_key, key, val)?; - Ok(()) + Ok(previous) } - /// remove - pub fn remove( - &self, - elem_key: &H, - storage_write: &mut impl StorageWrite, - ) -> Result<()> { - let data_key = self.get_data_key(elem_key); - storage_write.delete(&data_key)?; + /// Removes a key from the map, returning the value at the key if the key + /// was previously in the map. + pub fn remove(&self, storage: &mut S, key: &K) -> Result> + where + S: StorageWrite + StorageRead, + { + let value = self.get(storage, key)?; + + let data_key = self.get_data_key(key); + storage.delete(&data_key)?; - Ok(()) + Ok(value) } - /// get value + /// Returns the value corresponding to the key, if any. pub fn get( &self, - elem_key: &H, - storage_read: &mut impl StorageRead, - ) -> Result> { - // check if elem_key exists in the first place? - - let data_key = self.get_data_key(elem_key); - let res: Option<(H, T)> = storage_read.read(&data_key)?; - match res { - Some(pair) => Ok(Some(pair.1)), - None => Ok(None), - } + storage: &impl StorageRead, + key: &K, + ) -> Result> { + let res = self.get_key_val(storage, key)?; + Ok(res.map(|elem| elem.1)) } - /// get the element key by its hash - pub fn get_elem_key_by_hash( + /// Returns the key-value corresponding to the key, if any. + pub fn get_key_val( &self, - elem_key_hash: &str, - storage_read: &mut impl StorageRead, - ) -> Result> { - let data_key = self - .key - .push(&DATA_SUBKEY.to_owned()) - .unwrap() - .push(&elem_key_hash.to_string()) - .unwrap(); - let res: Option<(H, T)> = storage_read.read(&data_key)?; - match res { - Some(pair) => Ok(Some(pair.0)), - None => Ok(None), - } + storage: &impl StorageRead, + key: &K, + ) -> Result> { + let data_key = self.get_data_key(key); + Self::read_key_val(storage, &data_key) + } + + /// Returns the key-value corresponding to the given hash of a key, if any. + pub fn get_key_val_by_hash( + &self, + storage: &impl StorageRead, + key_hash: &str, + ) -> Result> { + let data_key = + self.get_data_prefix().push(&key_hash.to_string()).unwrap(); + Self::read_key_val(storage, &data_key) + } + + /// An iterator visiting all key-value elements. The iterator element type + /// is `Result<(K, V)>`, because iterator's call to `next` may fail with + /// e.g. out of gas or data decoding error. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// map. + pub fn iter<'a>( + &self, + storage: &'a impl StorageRead, + ) -> Result> + 'a> { + let iter = storage.iter_prefix(&self.get_data_prefix())?; + let iter = itertools::unfold(iter, |iter| { + match storage.iter_next(iter) { + Ok(Some((_key, value))) => { + match KeyVal::::try_from_slice(&value[..]) { + Ok(KeyVal { key, val }) => Some(Ok((key, val))), + Err(err) => Some(Err(storage_api::Error::new(err))), + } + } + Ok(None) => None, + Err(err) => { + // Propagate errors into Iterator's Item + Some(Err(err)) + } + } + }); + Ok(iter) + } + + /// Reads a key-value from storage + fn read_key_val( + storage: &impl StorageRead, + storage_key: &storage::Key, + ) -> Result> { + let res = storage.read(storage_key)?; + Ok(res.map(|KeyVal { key, val }| (key, val))) + } + + /// Write a key-value into storage + fn write_key_val( + storage: &mut impl StorageWrite, + storage_key: &storage::Key, + key: K, + val: V, + ) -> Result<()> { + storage.write(storage_key, KeyVal { key, val }) } - /// hash - fn hash(&self, elem_key: &H) -> u64 { - let mut hasher = DefaultHasher::new(); - elem_key.hash(&mut hasher); - hasher.finish() + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() } - /// get the data subkey - fn get_data_key(&self, elem_key: &H) -> storage::Key { - let hash_str = self.hash(elem_key).to_string(); - self.key - .push(&DATA_SUBKEY.to_owned()) - .unwrap() - .push(&hash_str) - .unwrap() + /// Get the sub-key of a given element + fn get_data_key(&self, key: &K) -> storage::Key { + let hash_str = hash_for_storage_key(key); + self.get_data_prefix().push(&hash_str).unwrap() } } diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 187f5b5123..862485b687 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -1,13 +1,12 @@ //! Lazy hash set -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; -use crate::ledger::storage_api::{StorageRead, StorageWrite}; +use super::hasher::hash_for_storage_key; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::storage; /// Subkey corresponding to the data elements of the LazySet @@ -21,9 +20,9 @@ pub struct LazySet { impl LazySet where - T: BorshSerialize + BorshDeserialize + Hash, + T: BorshSerialize + BorshDeserialize, { - /// new + /// Create or use an existing set with the given storage `key`. pub fn new(key: storage::Key) -> Self { Self { key, @@ -31,62 +30,87 @@ where } } - /// insert - pub fn insert( - &self, - val: &T, - storage_write: &mut impl StorageWrite, - ) -> Result<()> { - // Do we need to read to see if this val is already in the set? - - let data_key = self.get_data_key(val); - storage_write.write(&data_key, &val)?; - Ok(()) + /// Adds a value to the set. If the set did not have this value present, + /// `Ok(true)` is returned, `Ok(false)` otherwise. + pub fn insert(&self, storage: &mut S, val: &T) -> Result + where + S: StorageWrite + StorageRead, + { + if self.contains(storage, val)? { + Ok(false) + } else { + let data_key = self.get_data_key(val); + storage.write(&data_key, &val)?; + Ok(true) + } } - /// remove - pub fn remove( - &self, - val: &T, - storage_write: &mut impl StorageWrite, - ) -> Result<()> { + /// Removes a value from the set. Returns whether the value was present in + /// the set. + pub fn remove(&self, storage: &mut S, val: &T) -> Result + where + S: StorageWrite + StorageRead, + { let data_key = self.get_data_key(val); - storage_write.delete(&data_key)?; - Ok(()) + let value: Option = storage.read(&data_key)?; + storage.delete(&data_key)?; + Ok(value.is_some()) } - /// check if the hash set contains a value + /// Returns whether the set contains a value. pub fn contains( &self, + storage: &impl StorageRead, val: &T, - storage_read: &impl StorageRead, ) -> Result { - let digest: Option = storage_read.read(&self.get_data_key(val))?; - match digest { - Some(_) => Ok(true), - None => Ok(false), - } + let value: Option = storage.read(&self.get_data_key(val))?; + Ok(value.is_some()) } - /// check if hash set is empty (if we want to do this we prob need a length - /// field) - pub fn is_empty(&self) { - todo!(); + /// Returns whether the set contains no elements. + pub fn is_empty(&self, storage: &impl StorageRead) -> Result { + let mut iter = storage.iter_prefix(&self.get_data_prefix())?; + Ok(storage.iter_next(&mut iter)?.is_none()) + } + + /// An iterator visiting all elements. The iterator element type is + /// `Result`, because iterator's call to `next` may fail with e.g. out of + /// gas or data decoding error. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. + pub fn iter<'a>( + &self, + storage: &'a impl StorageRead, + ) -> Result> + 'a> { + let iter = storage.iter_prefix(&self.get_data_prefix())?; + let iter = itertools::unfold(iter, |iter| { + match storage.iter_next(iter) { + Ok(Some((_key, value))) => { + match T::try_from_slice(&value[..]) { + Ok(decoded_value) => Some(Ok(decoded_value)), + Err(err) => Some(Err(storage_api::Error::new(err))), + } + } + Ok(None) => None, + Err(err) => { + // Propagate errors into Iterator's Item + Some(Err(err)) + } + } + }); + Ok(iter) } - fn hash_val(&self, val: &T) -> u64 { - let mut hasher = DefaultHasher::new(); - val.hash(&mut hasher); - hasher.finish() + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() } - /// get the data subkey + /// Get the sub-key of a given element fn get_data_key(&self, val: &T) -> storage::Key { - let hash_str = self.hash_val(val).to_string(); - self.key - .push(&DATA_SUBKEY.to_owned()) - .unwrap() - .push(&hash_str) - .unwrap() + let hash_str = hash_for_storage_key(val); + self.get_data_prefix().push(&hash_str).unwrap() } } diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index 4c08f906d9..c55c39e516 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -5,7 +5,7 @@ use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; -use crate::ledger::storage_api::{StorageRead, StorageWrite}; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::storage; /// Subkey pointing to the length of the LazyVec @@ -23,7 +23,7 @@ impl LazyVec where T: BorshSerialize + BorshDeserialize, { - /// new + /// Create or use an existing vector with the given storage `key`. pub fn new(key: storage::Key) -> Self { Self { key, @@ -31,78 +31,105 @@ where } } - /// push - pub fn push( - &self, - val: T, - storage_read: &impl StorageRead, - storage_write: &mut impl StorageWrite, - ) -> Result<()> { - let len = self.read_len(storage_read)?; + /// Appends an element to the back of a collection. + pub fn push(&self, storage: &mut S, val: T) -> Result<()> + where + S: StorageWrite + StorageRead, + { + let len = self.len(storage)?; + let data_key = self.get_data_key(len); + storage.write(&data_key, val)?; + storage.write(&self.get_len_key(), len + 1) + } - let sub_index = len.unwrap_or(0); - let len = sub_index + 1; + /// Removes the last element from a vector and returns it, or `Ok(None)` if + /// it is empty. + + /// Note that an empty vector is completely removed from storage. + pub fn pop(&self, storage: &mut S) -> Result> + where + S: StorageWrite + StorageRead, + { + let len = self.len(storage)?; + if len == 0 { + Ok(None) + } else { + let index = len - 1; + let data_key = self.get_data_key(index); + if len == 1 { + storage.delete(&self.get_len_key())?; + } else { + storage.write(&self.get_len_key(), index)?; + } + let popped_val = storage.read(&data_key)?; + storage.delete(&data_key)?; + Ok(popped_val) + } + } - let data_key = self.get_data_key(sub_index); + /// Read an element at the index or `Ok(None)` if out of bounds. + pub fn get( + &self, + storage: &impl StorageRead, + index: u64, + ) -> Result> { + storage.read(&self.get_data_key(index)) + } - storage_write.write(&data_key, val)?; - storage_write.write(&self.get_len_key(), len)?; + /// Reads the number of elements in the vector. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &impl StorageRead) -> Result { + let len = storage.read(&self.get_len_key())?; + Ok(len.unwrap_or_default()) + } - Ok(()) + /// Returns `true` if the vector contains no elements. + pub fn is_empty(&self, storage: &impl StorageRead) -> Result { + Ok(self.len(storage)? == 0) } - /// pop - pub fn pop( + /// An iterator visiting all elements. The iterator element type is + /// `Result`, because iterator's call to `next` may fail with e.g. out of + /// gas or data decoding error. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. + pub fn iter<'a>( &self, - storage_read: &impl StorageRead, - storage_write: &mut impl StorageWrite, - ) -> Result> { - let len = self.read_len(storage_read)?; - match len { - Some(0) | None => Ok(None), - Some(len) => { - let sub_index = len - 1; - let data_key = self.get_data_key(sub_index); - if len == 1 { - storage_write.delete(&self.get_len_key())?; - } else { - storage_write.write(&self.get_len_key(), sub_index)?; + storage: &'a impl StorageRead, + ) -> Result> + 'a> { + let iter = storage.iter_prefix(&self.get_data_prefix())?; + let iter = itertools::unfold(iter, |iter| { + match storage.iter_next(iter) { + Ok(Some((_key, value))) => { + match T::try_from_slice(&value[..]) { + Ok(decoded_value) => Some(Ok(decoded_value)), + Err(err) => Some(Err(storage_api::Error::new(err))), + } + } + Ok(None) => None, + Err(err) => { + // Propagate errors into Iterator's Item + Some(Err(err)) } - let popped_val = storage_read.read(&data_key)?; - storage_write.delete(&data_key)?; - Ok(popped_val) } - } - } - - /// get the length subkey - fn get_len_key(&self) -> storage::Key { - self.key.push(&LEN_SUBKEY.to_owned()).unwrap() + }); + Ok(iter) } - /// read the length of the LazyVec - pub fn read_len( - &self, - storage_read: &impl StorageRead, - ) -> Result> { - storage_read.read(&self.get_len_key()) + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() } - /// get the data subkey - fn get_data_key(&self, sub_index: u64) -> storage::Key { - self.key - .push(&DATA_SUBKEY.to_owned()) - .unwrap() - .push(&sub_index.to_string()) - .unwrap() + /// Get the sub-key of vector's elements storage + fn get_data_key(&self, index: u64) -> storage::Key { + self.get_data_prefix().push(&index.to_string()).unwrap() } - /// get the data held at a specific index within the data subkey - pub fn get( - &self, - sub_index: u64, - storage_read: &impl StorageRead, - ) -> Result> { - storage_read.read(&self.get_data_key(sub_index)) + /// Get the sub-key of vector's length storage + fn get_len_key(&self) -> storage::Key { + self.key.push(&LEN_SUBKEY.to_owned()).unwrap() } } diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index c3784c2525..b0dc43779b 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -7,6 +7,7 @@ //! //! CONTINUE TO UPDATE THE ABOVE +mod hasher; pub mod lazy_map; pub mod lazy_set; pub mod lazy_vec; From bc5f615c437e2f4f894988552857fbdb61eb5bde Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 15 Aug 2022 16:38:10 -0400 Subject: [PATCH 047/197] add lazy map without hashing --- .../storage_api/collections/lazy_hashmap.rs | 171 ++++++++++++++++++ .../storage_api/collections/lazy_map.rs | 66 ++----- 2 files changed, 191 insertions(+), 46 deletions(-) create mode 100644 shared/src/ledger/storage_api/collections/lazy_hashmap.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs new file mode 100644 index 0000000000..405262b33d --- /dev/null +++ b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs @@ -0,0 +1,171 @@ +//! Lazy hash map + +use std::marker::PhantomData; + +use borsh::{BorshDeserialize, BorshSerialize}; + +use super::super::Result; +use super::hasher::hash_for_storage_key; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::types::storage; + +/// Subkey corresponding to the data elements of the LazyMap +pub const DATA_SUBKEY: &str = "data"; + +/// LazyHashmap ! fill in ! +pub struct LazyHashMap { + key: storage::Key, + phantom_k: PhantomData, + phantom_v: PhantomData, +} + +/// Struct to hold a key-value pair +#[derive(Debug, BorshSerialize, BorshDeserialize)] +struct KeyVal { + key: K, + val: V, +} + +impl LazyMap +where + K: BorshDeserialize + BorshSerialize, + V: BorshDeserialize + BorshSerialize, +{ + /// Create or use an existing map with the given storage `key`. + pub fn new(key: storage::Key) -> Self { + Self { + key, + phantom_k: PhantomData, + phantom_v: PhantomData, + } + } + + /// Inserts a key-value pair into the map. + /// + /// If the map did not have this key present, `None` is returned. + /// If the map did have this key present, the value is updated, and the old + /// value is returned. Unlike in `std::collection::HashMap`, the key is also + /// updated; this matters for types that can be `==` without being + /// identical. + pub fn insert( + &self, + storage: &mut S, + key: K, + val: V, + ) -> Result> + where + S: StorageWrite + StorageRead, + { + let previous = self.get(storage, &key)?; + + let data_key = self.get_data_key(&key); + Self::write_key_val(storage, &data_key, key, val)?; + + Ok(previous) + } + + /// Removes a key from the map, returning the value at the key if the key + /// was previously in the map. + pub fn remove(&self, storage: &mut S, key: &K) -> Result> + where + S: StorageWrite + StorageRead, + { + let value = self.get(storage, key)?; + + let data_key = self.get_data_key(key); + storage.delete(&data_key)?; + + Ok(value) + } + + /// Returns the value corresponding to the key, if any. + pub fn get( + &self, + storage: &impl StorageRead, + key: &K, + ) -> Result> { + let res = self.get_key_val(storage, key)?; + Ok(res.map(|elem| elem.1)) + } + + /// Returns the key-value corresponding to the key, if any. + pub fn get_key_val( + &self, + storage: &impl StorageRead, + key: &K, + ) -> Result> { + let data_key = self.get_data_key(key); + Self::read_key_val(storage, &data_key) + } + + /// Returns the key-value corresponding to the given hash of a key, if any. + pub fn get_key_val_by_hash( + &self, + storage: &impl StorageRead, + key_hash: &str, + ) -> Result> { + let data_key = + self.get_data_prefix().push(&key_hash.to_string()).unwrap(); + Self::read_key_val(storage, &data_key) + } + + /// An iterator visiting all key-value elements. The iterator element type + /// is `Result<(K, V)>`, because iterator's call to `next` may fail with + /// e.g. out of gas or data decoding error. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// map. + pub fn iter<'a>( + &self, + storage: &'a impl StorageRead, + ) -> Result> + 'a> { + let iter = storage.iter_prefix(&self.get_data_prefix())?; + let iter = itertools::unfold(iter, |iter| { + match storage.iter_next(iter) { + Ok(Some((_key, value))) => { + match KeyVal::::try_from_slice(&value[..]) { + Ok(KeyVal { key, val }) => Some(Ok((key, val))), + Err(err) => Some(Err(storage_api::Error::new(err))), + } + } + Ok(None) => None, + Err(err) => { + // Propagate errors into Iterator's Item + Some(Err(err)) + } + } + }); + Ok(iter) + } + + /// Reads a key-value from storage + fn read_key_val( + storage: &impl StorageRead, + storage_key: &storage::Key, + ) -> Result> { + let res = storage.read(storage_key)?; + Ok(res.map(|KeyVal { key, val }| (key, val))) + } + + /// Write a key-value into storage + fn write_key_val( + storage: &mut impl StorageWrite, + storage_key: &storage::Key, + key: K, + val: V, + ) -> Result<()> { + storage.write(storage_key, KeyVal { key, val }) + } + + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() + } + + /// Get the sub-key of a given element + fn get_data_key(&self, key: &K) -> storage::Key { + let hash_str = hash_for_storage_key(key); + self.get_data_prefix().push(&hash_str).unwrap() + } +} diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 25b4cd4d5d..01af3c5a42 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -1,11 +1,11 @@ -//! Lazy map +//! Lazy hash map +use std::fmt::Display; use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; -use super::hasher::hash_for_storage_key; use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::storage; @@ -15,32 +15,29 @@ pub const DATA_SUBKEY: &str = "data"; /// LazyMap ! fill in ! pub struct LazyMap { key: storage::Key, - phantom_h: PhantomData, - phantom_t: PhantomData, -} - -#[derive(Debug, BorshSerialize, BorshDeserialize)] -struct KeyVal { - key: K, - val: V, + phantom_k: PhantomData, + phantom_v: PhantomData, } impl LazyMap where - K: BorshDeserialize + BorshSerialize, + K: BorshDeserialize + BorshSerialize + Display, V: BorshDeserialize + BorshSerialize, { /// Create or use an existing map with the given storage `key`. pub fn new(key: storage::Key) -> Self { Self { key, - phantom_h: PhantomData, - phantom_t: PhantomData, + phantom_k: PhantomData, + phantom_v: PhantomData, } } /// Inserts a key-value pair into the map. /// + /// The full storage key identifies the key in the pair, while the value is + /// held within the storage key. + /// /// If the map did not have this key present, `None` is returned. /// If the map did have this key present, the value is updated, and the old /// value is returned. Unlike in `std::collection::HashMap`, the key is also @@ -58,7 +55,7 @@ where let previous = self.get(storage, &key)?; let data_key = self.get_data_key(&key); - Self::write_key_val(storage, &data_key, key, val)?; + Self::write_key_val(storage, &data_key, val)?; Ok(previous) } @@ -83,31 +80,10 @@ where storage: &impl StorageRead, key: &K, ) -> Result> { - let res = self.get_key_val(storage, key)?; - Ok(res.map(|elem| elem.1)) - } - - /// Returns the key-value corresponding to the key, if any. - pub fn get_key_val( - &self, - storage: &impl StorageRead, - key: &K, - ) -> Result> { let data_key = self.get_data_key(key); Self::read_key_val(storage, &data_key) } - /// Returns the key-value corresponding to the given hash of a key, if any. - pub fn get_key_val_by_hash( - &self, - storage: &impl StorageRead, - key_hash: &str, - ) -> Result> { - let data_key = - self.get_data_prefix().push(&key_hash.to_string()).unwrap(); - Self::read_key_val(storage, &data_key) - } - /// An iterator visiting all key-value elements. The iterator element type /// is `Result<(K, V)>`, because iterator's call to `next` may fail with /// e.g. out of gas or data decoding error. @@ -118,13 +94,13 @@ where pub fn iter<'a>( &self, storage: &'a impl StorageRead, - ) -> Result> + 'a> { + ) -> Result> + 'a> { let iter = storage.iter_prefix(&self.get_data_prefix())?; let iter = itertools::unfold(iter, |iter| { match storage.iter_next(iter) { Ok(Some((_key, value))) => { - match KeyVal::::try_from_slice(&value[..]) { - Ok(KeyVal { key, val }) => Some(Ok((key, val))), + match V::try_from_slice(&value[..]) { + Ok(decoded_value) => Some(Ok(decoded_value)), Err(err) => Some(Err(storage_api::Error::new(err))), } } @@ -138,23 +114,22 @@ where Ok(iter) } - /// Reads a key-value from storage + /// Reads a value from storage fn read_key_val( storage: &impl StorageRead, storage_key: &storage::Key, - ) -> Result> { + ) -> Result> { let res = storage.read(storage_key)?; - Ok(res.map(|KeyVal { key, val }| (key, val))) + Ok(res) } - /// Write a key-value into storage + /// Write a value into storage fn write_key_val( storage: &mut impl StorageWrite, storage_key: &storage::Key, - key: K, val: V, ) -> Result<()> { - storage.write(storage_key, KeyVal { key, val }) + storage.write(storage_key, val) } /// Get the prefix of set's elements storage @@ -164,7 +139,6 @@ where /// Get the sub-key of a given element fn get_data_key(&self, key: &K) -> storage::Key { - let hash_str = hash_for_storage_key(key); - self.get_data_prefix().push(&hash_str).unwrap() + self.get_data_prefix().push(&key.to_string()).unwrap() } } From 50e2f258838e4561d64b8421051950368eba394d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 12:51:19 +0200 Subject: [PATCH 048/197] Switch to use storage::KeySeg and add LazySet Also refactored the iterators implementation to take advantage of changes from #335. Note that this requires `'static` lifetime bound on the types of the collections' elements, which means we cannot use non-static references, but we wouldn't do that anyway. --- .../storage_api/collections/lazy_hashmap.rs | 50 ++++---- .../storage_api/collections/lazy_hashset.rs | 119 ++++++++++++++++++ .../storage_api/collections/lazy_map.rs | 58 +++++---- .../storage_api/collections/lazy_set.rs | 70 ++++++----- .../storage_api/collections/lazy_vec.rs | 35 +++--- .../src/ledger/storage_api/collections/mod.rs | 27 +++- 6 files changed, 252 insertions(+), 107 deletions(-) create mode 100644 shared/src/ledger/storage_api/collections/lazy_hashset.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs index 405262b33d..fbb76beb8b 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs @@ -1,4 +1,4 @@ -//! Lazy hash map +//! Lazy hash map. use std::marker::PhantomData; @@ -12,7 +12,22 @@ use crate::types::storage; /// Subkey corresponding to the data elements of the LazyMap pub const DATA_SUBKEY: &str = "data"; -/// LazyHashmap ! fill in ! +/// Lazy hash map. +/// +/// This can be used as an alternative to `std::collections::HashMap` and +/// `BTreeMap`. In the lazy map, the elements do not reside in memory but are +/// instead read and written to storage sub-keys of the storage `key` given to +/// construct the map. +/// +/// In the [`LazyHashMap`], the type of key `K` can be anything that +/// [`BorshSerialize`] and [`BorshDeserialize`] and a hex string of sha256 hash +/// over the borsh encoded keys are used as storage key segments. +/// +/// This is different from [`super::LazyMap`], which uses [`storage::KeySeg`] +/// trait. +/// +/// Additionally, [`LazyHashMap`] also writes the unhashed values into the +/// storage together with the values (using an internal `KeyVal` type). pub struct LazyHashMap { key: storage::Key, phantom_k: PhantomData, @@ -26,10 +41,10 @@ struct KeyVal { val: V, } -impl LazyMap +impl LazyHashMap where - K: BorshDeserialize + BorshSerialize, - V: BorshDeserialize + BorshSerialize, + K: BorshDeserialize + BorshSerialize + 'static, + V: BorshDeserialize + BorshSerialize + 'static, { /// Create or use an existing map with the given storage `key`. pub fn new(key: storage::Key) -> Self { @@ -85,7 +100,7 @@ where key: &K, ) -> Result> { let res = self.get_key_val(storage, key)?; - Ok(res.map(|elem| elem.1)) + Ok(res.map(|(_key, val)| val)) } /// Returns the key-value corresponding to the key, if any. @@ -120,23 +135,12 @@ where &self, storage: &'a impl StorageRead, ) -> Result> + 'a> { - let iter = storage.iter_prefix(&self.get_data_prefix())?; - let iter = itertools::unfold(iter, |iter| { - match storage.iter_next(iter) { - Ok(Some((_key, value))) => { - match KeyVal::::try_from_slice(&value[..]) { - Ok(KeyVal { key, val }) => Some(Ok((key, val))), - Err(err) => Some(Err(storage_api::Error::new(err))), - } - } - Ok(None) => None, - Err(err) => { - // Propagate errors into Iterator's Item - Some(Err(err)) - } - } - }); - Ok(iter) + let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; + Ok(iter.map(|key_val_res| { + let (_key, val) = key_val_res?; + let KeyVal { key, val } = val; + Ok((key, val)) + })) } /// Reads a key-value from storage diff --git a/shared/src/ledger/storage_api/collections/lazy_hashset.rs b/shared/src/ledger/storage_api/collections/lazy_hashset.rs new file mode 100644 index 0000000000..ae03ff1f0e --- /dev/null +++ b/shared/src/ledger/storage_api/collections/lazy_hashset.rs @@ -0,0 +1,119 @@ +//! Lazy hash set. + +use std::marker::PhantomData; + +use borsh::{BorshDeserialize, BorshSerialize}; + +use super::super::Result; +use super::hasher::hash_for_storage_key; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::types::storage; + +/// Subkey corresponding to the data elements of the LazySet +pub const DATA_SUBKEY: &str = "data"; + +/// Lazy hash set. +/// +/// This can be used as an alternative to `std::collections::HashSet` and +/// `BTreeSet`. In the lazy set, the elements do not reside in memory but are +/// instead read and written to storage sub-keys of the storage `key` given to +/// construct the set. +/// +/// In the [`LazyHashSet`], the type of value `T` can be anything that +/// [`BorshSerialize`] and [`BorshDeserialize`] and a hex string of sha256 hash +/// over the borsh encoded values are used as storage key segments. +/// +/// This is different from [`super::LazySet`], which uses [`storage::KeySeg`] +/// trait. +/// +/// Additionally, [`LazyHashSet`] also writes the unhashed values into the +/// storage. +pub struct LazyHashSet { + key: storage::Key, + phantom: PhantomData, +} + +impl LazyHashSet +where + T: BorshSerialize + BorshDeserialize + 'static, +{ + /// Create or use an existing set with the given storage `key`. + pub fn new(key: storage::Key) -> Self { + Self { + key, + phantom: PhantomData, + } + } + + /// Adds a value to the set. If the set did not have this value present, + /// `Ok(true)` is returned, `Ok(false)` otherwise. + pub fn insert(&self, storage: &mut S, val: &T) -> Result + where + S: StorageWrite + StorageRead, + { + if self.contains(storage, val)? { + Ok(false) + } else { + let data_key = self.get_data_key(val); + storage.write(&data_key, &val)?; + Ok(true) + } + } + + /// Removes a value from the set. Returns whether the value was present in + /// the set. + pub fn remove(&self, storage: &mut S, val: &T) -> Result + where + S: StorageWrite + StorageRead, + { + let data_key = self.get_data_key(val); + let value: Option = storage.read(&data_key)?; + storage.delete(&data_key)?; + Ok(value.is_some()) + } + + /// Returns whether the set contains a value. + pub fn contains( + &self, + storage: &impl StorageRead, + val: &T, + ) -> Result { + let value: Option = storage.read(&self.get_data_key(val))?; + Ok(value.is_some()) + } + + /// Returns whether the set contains no elements. + pub fn is_empty(&self, storage: &impl StorageRead) -> Result { + let mut iter = storage.iter_prefix(&self.get_data_prefix())?; + Ok(storage.iter_next(&mut iter)?.is_none()) + } + + /// An iterator visiting all elements. The iterator element type is + /// `Result`, because iterator's call to `next` may fail with e.g. out of + /// gas or data decoding error. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. + pub fn iter<'a>( + &self, + storage: &'a impl StorageRead, + ) -> Result> + 'a> { + let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; + Ok(iter.map(|key_val_res| { + let (_key, val) = key_val_res?; + Ok(val) + })) + } + + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() + } + + /// Get the sub-key of a given element + fn get_data_key(&self, val: &T) -> storage::Key { + let hash_str = hash_for_storage_key(val); + self.get_data_prefix().push(&hash_str).unwrap() + } +} diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 01af3c5a42..bf7b45324b 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -1,18 +1,30 @@ -//! Lazy hash map +//! Lazy map. -use std::fmt::Display; use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; -use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; -use crate::types::storage; +use super::ReadError; +use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; +use crate::types::storage::{self, KeySeg}; /// Subkey corresponding to the data elements of the LazyMap pub const DATA_SUBKEY: &str = "data"; -/// LazyMap ! fill in ! +/// Lazy map. +/// +/// This can be used as an alternative to `std::collections::HashMap` and +/// `BTreeMap`. In the lazy map, the elements do not reside in memory but are +/// instead read and written to storage sub-keys of the storage `key` used to +/// construct the map. +/// +/// In the [`LazyMap`], the type of key `K` can be anything that implements +/// [`storage::KeySeg`] and this trait is used to turn the keys into key +/// segments. +/// +/// This is different from [`super::LazyHashMap`], which hashes borsh encoded +/// key. pub struct LazyMap { key: storage::Key, phantom_k: PhantomData, @@ -21,8 +33,8 @@ pub struct LazyMap { impl LazyMap where - K: BorshDeserialize + BorshSerialize + Display, - V: BorshDeserialize + BorshSerialize, + K: storage::KeySeg, + V: BorshDeserialize + BorshSerialize + 'static, { /// Create or use an existing map with the given storage `key`. pub fn new(key: storage::Key) -> Self { @@ -94,24 +106,17 @@ where pub fn iter<'a>( &self, storage: &'a impl StorageRead, - ) -> Result> + 'a> { - let iter = storage.iter_prefix(&self.get_data_prefix())?; - let iter = itertools::unfold(iter, |iter| { - match storage.iter_next(iter) { - Ok(Some((_key, value))) => { - match V::try_from_slice(&value[..]) { - Ok(decoded_value) => Some(Ok(decoded_value)), - Err(err) => Some(Err(storage_api::Error::new(err))), - } - } - Ok(None) => None, - Err(err) => { - // Propagate errors into Iterator's Item - Some(Err(err)) - } - } - }); - Ok(iter) + ) -> Result> + 'a> { + let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; + Ok(iter.map(|key_val_res| { + let (key, val) = key_val_res?; + let last_key_seg = key + .last() + .ok_or(ReadError::UnexpectedlyEmptyStorageKey) + .into_storage_result()?; + let key = K::parse(last_key_seg.raw()).into_storage_result()?; + Ok((key, val)) + })) } /// Reads a value from storage @@ -139,6 +144,7 @@ where /// Get the sub-key of a given element fn get_data_key(&self, key: &K) -> storage::Key { - self.get_data_prefix().push(&key.to_string()).unwrap() + let key_str = key.to_db_key(); + self.get_data_prefix().push(&key_str).unwrap() } } diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 862485b687..8c1bbd871f 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -1,18 +1,28 @@ -//! Lazy hash set +//! Lazy set. use std::marker::PhantomData; -use borsh::{BorshDeserialize, BorshSerialize}; - use super::super::Result; -use super::hasher::hash_for_storage_key; -use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; -use crate::types::storage; +use super::ReadError; +use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; +use crate::types::storage::{self, KeySeg}; /// Subkey corresponding to the data elements of the LazySet pub const DATA_SUBKEY: &str = "data"; -/// lazy hash set +/// Lazy set. +/// +/// This can be used as an alternative to `std::collections::HashSet` and +/// `BTreeSet`. In the lazy set, the elements do not reside in memory but are +/// instead read and written to storage sub-keys of the storage `key` used to +/// construct the set. +/// +/// In the [`LazySet`], the type of value `T` can be anything that implements +/// [`storage::KeySeg`] and this trait is used to turn the values into key +/// segments. +/// +/// This is different from [`super::LazyHashSet`], which hashes borsh encoded +/// values. pub struct LazySet { key: storage::Key, phantom: PhantomData, @@ -20,7 +30,7 @@ pub struct LazySet { impl LazySet where - T: BorshSerialize + BorshDeserialize, + T: storage::KeySeg, { /// Create or use an existing set with the given storage `key`. pub fn new(key: storage::Key) -> Self { @@ -40,7 +50,9 @@ where Ok(false) } else { let data_key = self.get_data_key(val); - storage.write(&data_key, &val)?; + // The actual value is written into the key, so the value written to + // the storage is empty (unit) + storage.write(&data_key, ())?; Ok(true) } } @@ -52,7 +64,7 @@ where S: StorageWrite + StorageRead, { let data_key = self.get_data_key(val); - let value: Option = storage.read(&data_key)?; + let value: Option<()> = storage.read(&data_key)?; storage.delete(&data_key)?; Ok(value.is_some()) } @@ -63,14 +75,15 @@ where storage: &impl StorageRead, val: &T, ) -> Result { - let value: Option = storage.read(&self.get_data_key(val))?; + let value: Option<()> = storage.read(&self.get_data_key(val))?; Ok(value.is_some()) } /// Returns whether the set contains no elements. pub fn is_empty(&self, storage: &impl StorageRead) -> Result { - let mut iter = storage.iter_prefix(&self.get_data_prefix())?; - Ok(storage.iter_next(&mut iter)?.is_none()) + let mut iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + Ok(iter.next().is_none()) } /// An iterator visiting all elements. The iterator element type is @@ -84,23 +97,16 @@ where &self, storage: &'a impl StorageRead, ) -> Result> + 'a> { - let iter = storage.iter_prefix(&self.get_data_prefix())?; - let iter = itertools::unfold(iter, |iter| { - match storage.iter_next(iter) { - Ok(Some((_key, value))) => { - match T::try_from_slice(&value[..]) { - Ok(decoded_value) => Some(Ok(decoded_value)), - Err(err) => Some(Err(storage_api::Error::new(err))), - } - } - Ok(None) => None, - Err(err) => { - // Propagate errors into Iterator's Item - Some(Err(err)) - } - } - }); - Ok(iter) + let iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + Ok(iter.map(|key_val_res| { + let (key, _val) = key_val_res?; + let last_key_seg = key + .last() + .ok_or(ReadError::UnexpectedlyEmptyStorageKey) + .into_storage_result()?; + T::parse(last_key_seg.raw()).into_storage_result() + })) } /// Get the prefix of set's elements storage @@ -110,7 +116,7 @@ where /// Get the sub-key of a given element fn get_data_key(&self, val: &T) -> storage::Key { - let hash_str = hash_for_storage_key(val); - self.get_data_prefix().push(&hash_str).unwrap() + let key_str = val.to_db_key(); + self.get_data_prefix().push(&key_str).unwrap() } } diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index c55c39e516..f57797f35c 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -1,4 +1,4 @@ -//! Lazy vec +//! Lazy dynamically-sized vector. use std::marker::PhantomData; @@ -13,7 +13,12 @@ pub const LEN_SUBKEY: &str = "len"; /// Subkey corresponding to the data elements of the LazyVec pub const DATA_SUBKEY: &str = "data"; -/// LazyVec ! fill in ! +/// Lazy dynamically-sized vector. +/// +/// This can be used as an alternative to `std::collections::Vec`. In the lazy +/// vector, the elements do not reside in memory but are instead read and +/// written to storage sub-keys of the storage `key` used to construct the +/// vector. pub struct LazyVec { key: storage::Key, phantom: PhantomData, @@ -21,7 +26,7 @@ pub struct LazyVec { impl LazyVec where - T: BorshSerialize + BorshDeserialize, + T: BorshSerialize + BorshDeserialize + 'static, { /// Create or use an existing vector with the given storage `key`. pub fn new(key: storage::Key) -> Self { @@ -44,7 +49,7 @@ where /// Removes the last element from a vector and returns it, or `Ok(None)` if /// it is empty. - + /// /// Note that an empty vector is completely removed from storage. pub fn pop(&self, storage: &mut S) -> Result> where @@ -99,23 +104,11 @@ where &self, storage: &'a impl StorageRead, ) -> Result> + 'a> { - let iter = storage.iter_prefix(&self.get_data_prefix())?; - let iter = itertools::unfold(iter, |iter| { - match storage.iter_next(iter) { - Ok(Some((_key, value))) => { - match T::try_from_slice(&value[..]) { - Ok(decoded_value) => Some(Ok(decoded_value)), - Err(err) => Some(Err(storage_api::Error::new(err))), - } - } - Ok(None) => None, - Err(err) => { - // Propagate errors into Iterator's Item - Some(Err(err)) - } - } - }); - Ok(iter) + let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; + Ok(iter.map(|key_val_res| { + let (_key, val) = key_val_res?; + Ok(val) + })) } /// Get the prefix of set's elements storage diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index b0dc43779b..156615b9de 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -1,13 +1,30 @@ //! Lazy data structures for storage access where elements are not all loaded //! into memory. This serves to minimize gas costs, avoid unbounded iteration -//! in some cases, and ease the validation of storage changes in the VP. +//! in some cases, and ease the validation of storage changes in VPs. //! -//! Rather than finding the diff of the state before and after, the VP will -//! just receive the storage sub-keys that have experienced changes. -//! -//! CONTINUE TO UPDATE THE ABOVE +//! Rather than finding the diff of the state before and after (which requires +//! iteration over both of the states that also have to be decoded), VPs will +//! just receive the storage sub-keys that have experienced changes without +//! having to check any of the unchanged elements. + +use thiserror::Error; mod hasher; +pub mod lazy_hashmap; +pub mod lazy_hashset; pub mod lazy_map; pub mod lazy_set; pub mod lazy_vec; + +pub use lazy_hashmap::LazyHashMap; +pub use lazy_hashset::LazyHashSet; +pub use lazy_map::LazyMap; +pub use lazy_set::LazySet; +pub use lazy_vec::LazyVec; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum ReadError { + #[error("A storage key was unexpectedly empty")] + UnexpectedlyEmptyStorageKey, +} From 12ce117920cc0364b2aa602ba0682ad93ccfb303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 12:54:54 +0200 Subject: [PATCH 049/197] storage: add `Key::last` method, impl KeySeg for all ints --- shared/src/types/storage.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 1892334031..289f115c8f 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -278,6 +278,11 @@ impl Key { self.len() == 0 } + /// Returns the last segment of the key, or `None` if it is empty. + pub fn last(&self) -> Option<&DbKeySeg> { + self.segments.last() + } + /// Returns a key of the validity predicate of the given address /// Only this function can push "?" segment for validity predicate pub fn validity_predicate(addr: &Address) -> Self { @@ -321,8 +326,11 @@ impl Key { .split_off(2) .join(&KEY_SEGMENT_SEPARATOR.to_string()), ) - .map_err(|e| Error::Temporary { - error: format!("Cannot parse key segments {}: {}", db_key, e), + .map_err(|e| { + Error::ParseKeySeg(format!( + "Cannot parse key segments {}: {}", + db_key, e + )) })?, }; Ok(key) @@ -450,7 +458,12 @@ impl KeySeg for String { impl KeySeg for BlockHeight { fn parse(string: String) -> Result { - let h: u64 = KeySeg::parse(string)?; + let h = string.parse::().map_err(|e| { + Error::ParseKeySeg(format!( + "Unexpected height value {}, {}", + string, e + )) + })?; Ok(BlockHeight(h)) } From bb18f2b79562c2ec3e0fddf655c196e6a4e633a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 16:41:27 +0200 Subject: [PATCH 050/197] update lazy for explicit lifetime in StorageRead trait --- .../storage_api/collections/lazy_hashmap.rs | 46 ++++++++++--------- .../storage_api/collections/lazy_hashset.rs | 24 +++++----- .../storage_api/collections/lazy_map.rs | 28 +++++------ .../storage_api/collections/lazy_set.rs | 24 +++++----- .../storage_api/collections/lazy_vec.rs | 29 +++++++----- 5 files changed, 83 insertions(+), 68 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs index fbb76beb8b..d626b75451 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs @@ -69,7 +69,7 @@ where val: V, ) -> Result> where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let previous = self.get(storage, &key)?; @@ -83,7 +83,7 @@ where /// was previously in the map. pub fn remove(&self, storage: &mut S, key: &K) -> Result> where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let value = self.get(storage, key)?; @@ -94,31 +94,32 @@ where } /// Returns the value corresponding to the key, if any. - pub fn get( - &self, - storage: &impl StorageRead, - key: &K, - ) -> Result> { + pub fn get(&self, storage: &S, key: &K) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { let res = self.get_key_val(storage, key)?; Ok(res.map(|(_key, val)| val)) } /// Returns the key-value corresponding to the key, if any. - pub fn get_key_val( - &self, - storage: &impl StorageRead, - key: &K, - ) -> Result> { + pub fn get_key_val(&self, storage: &S, key: &K) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { let data_key = self.get_data_key(key); Self::read_key_val(storage, &data_key) } /// Returns the key-value corresponding to the given hash of a key, if any. - pub fn get_key_val_by_hash( + pub fn get_key_val_by_hash( &self, - storage: &impl StorageRead, + storage: &S, key_hash: &str, - ) -> Result> { + ) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { let data_key = self.get_data_prefix().push(&key_hash.to_string()).unwrap(); Self::read_key_val(storage, &data_key) @@ -131,10 +132,10 @@ where /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded sets to avoid gas usage increasing with the length of the /// map. - pub fn iter<'a>( + pub fn iter<'iter, S>( &self, - storage: &'a impl StorageRead, - ) -> Result> + 'a> { + storage: &'iter impl StorageRead<'iter>, + ) -> Result> + 'iter> { let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; Ok(iter.map(|key_val_res| { let (_key, val) = key_val_res?; @@ -144,10 +145,13 @@ where } /// Reads a key-value from storage - fn read_key_val( - storage: &impl StorageRead, + fn read_key_val( + storage: &S, storage_key: &storage::Key, - ) -> Result> { + ) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { let res = storage.read(storage_key)?; Ok(res.map(|KeyVal { key, val }| (key, val))) } diff --git a/shared/src/ledger/storage_api/collections/lazy_hashset.rs b/shared/src/ledger/storage_api/collections/lazy_hashset.rs index ae03ff1f0e..96a31ecef7 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashset.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashset.rs @@ -49,7 +49,7 @@ where /// `Ok(true)` is returned, `Ok(false)` otherwise. pub fn insert(&self, storage: &mut S, val: &T) -> Result where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { if self.contains(storage, val)? { Ok(false) @@ -64,7 +64,7 @@ where /// the set. pub fn remove(&self, storage: &mut S, val: &T) -> Result where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let data_key = self.get_data_key(val); let value: Option = storage.read(&data_key)?; @@ -73,17 +73,19 @@ where } /// Returns whether the set contains a value. - pub fn contains( - &self, - storage: &impl StorageRead, - val: &T, - ) -> Result { + pub fn contains(&self, storage: &S, val: &T) -> Result + where + S: for<'iter> StorageRead<'iter>, + { let value: Option = storage.read(&self.get_data_key(val))?; Ok(value.is_some()) } /// Returns whether the set contains no elements. - pub fn is_empty(&self, storage: &impl StorageRead) -> Result { + pub fn is_empty(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { let mut iter = storage.iter_prefix(&self.get_data_prefix())?; Ok(storage.iter_next(&mut iter)?.is_none()) } @@ -95,10 +97,10 @@ where /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded sets to avoid gas usage increasing with the length of the /// set. - pub fn iter<'a>( + pub fn iter<'iter>( &self, - storage: &'a impl StorageRead, - ) -> Result> + 'a> { + storage: &'iter impl StorageRead<'iter>, + ) -> Result> + 'iter> { let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; Ok(iter.map(|key_val_res| { let (_key, val) = key_val_res?; diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index bf7b45324b..6da5c97031 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -62,7 +62,7 @@ where val: V, ) -> Result> where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let previous = self.get(storage, &key)?; @@ -76,7 +76,7 @@ where /// was previously in the map. pub fn remove(&self, storage: &mut S, key: &K) -> Result> where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let value = self.get(storage, key)?; @@ -87,11 +87,10 @@ where } /// Returns the value corresponding to the key, if any. - pub fn get( - &self, - storage: &impl StorageRead, - key: &K, - ) -> Result> { + pub fn get(&self, storage: &S, key: &K) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { let data_key = self.get_data_key(key); Self::read_key_val(storage, &data_key) } @@ -103,10 +102,10 @@ where /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded sets to avoid gas usage increasing with the length of the /// map. - pub fn iter<'a>( + pub fn iter<'iter>( &self, - storage: &'a impl StorageRead, - ) -> Result> + 'a> { + storage: &'iter impl StorageRead<'iter>, + ) -> Result> + 'iter> { let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; Ok(iter.map(|key_val_res| { let (key, val) = key_val_res?; @@ -120,10 +119,13 @@ where } /// Reads a value from storage - fn read_key_val( - storage: &impl StorageRead, + fn read_key_val( + storage: &S, storage_key: &storage::Key, - ) -> Result> { + ) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { let res = storage.read(storage_key)?; Ok(res) } diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 8c1bbd871f..1e1f259c55 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -44,7 +44,7 @@ where /// `Ok(true)` is returned, `Ok(false)` otherwise. pub fn insert(&self, storage: &mut S, val: &T) -> Result where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { if self.contains(storage, val)? { Ok(false) @@ -61,7 +61,7 @@ where /// the set. pub fn remove(&self, storage: &mut S, val: &T) -> Result where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let data_key = self.get_data_key(val); let value: Option<()> = storage.read(&data_key)?; @@ -70,17 +70,19 @@ where } /// Returns whether the set contains a value. - pub fn contains( - &self, - storage: &impl StorageRead, - val: &T, - ) -> Result { + pub fn contains(&self, storage: &S, val: &T) -> Result + where + S: for<'iter> StorageRead<'iter>, + { let value: Option<()> = storage.read(&self.get_data_key(val))?; Ok(value.is_some()) } /// Returns whether the set contains no elements. - pub fn is_empty(&self, storage: &impl StorageRead) -> Result { + pub fn is_empty(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { let mut iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; Ok(iter.next().is_none()) @@ -93,10 +95,10 @@ where /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded sets to avoid gas usage increasing with the length of the /// set. - pub fn iter<'a>( + pub fn iter<'iter>( &self, - storage: &'a impl StorageRead, - ) -> Result> + 'a> { + storage: &'iter impl StorageRead<'iter>, + ) -> Result> + 'iter> { let iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; Ok(iter.map(|key_val_res| { diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index f57797f35c..e6a186c408 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -39,7 +39,7 @@ where /// Appends an element to the back of a collection. pub fn push(&self, storage: &mut S, val: T) -> Result<()> where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let len = self.len(storage)?; let data_key = self.get_data_key(len); @@ -53,7 +53,7 @@ where /// Note that an empty vector is completely removed from storage. pub fn pop(&self, storage: &mut S) -> Result> where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let len = self.len(storage)?; if len == 0 { @@ -73,23 +73,28 @@ where } /// Read an element at the index or `Ok(None)` if out of bounds. - pub fn get( - &self, - storage: &impl StorageRead, - index: u64, - ) -> Result> { + pub fn get(&self, storage: &S, index: u64) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { storage.read(&self.get_data_key(index)) } /// Reads the number of elements in the vector. #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &impl StorageRead) -> Result { + pub fn len(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { let len = storage.read(&self.get_len_key())?; Ok(len.unwrap_or_default()) } /// Returns `true` if the vector contains no elements. - pub fn is_empty(&self, storage: &impl StorageRead) -> Result { + pub fn is_empty(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { Ok(self.len(storage)? == 0) } @@ -100,10 +105,10 @@ where /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded sets to avoid gas usage increasing with the length of the /// set. - pub fn iter<'a>( + pub fn iter<'iter>( &self, - storage: &'a impl StorageRead, - ) -> Result> + 'a> { + storage: &'iter impl StorageRead<'iter>, + ) -> Result> + 'iter> { let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; Ok(iter.map(|key_val_res| { let (_key, val) = key_val_res?; From 22c2ea54591761e92a79b3f2b2c4de41d66d5eda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 15:01:16 +0200 Subject: [PATCH 051/197] cargo test test_lazy_vec_basics --- shared/src/ledger/storage/mod.rs | 40 +++++++++++++++++- .../storage_api/collections/lazy_vec.rs | 42 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 1c4e4efd94..3a1bfa697e 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -459,7 +459,7 @@ where // Note that this method is the same as `StorageWrite::delete`, // but with gas and storage bytes len diff accounting let mut deleted_bytes_len = 0; - if self.has_key(key)?.0 { + if Self::has_key(&self, key)?.0 { self.block.tree.delete(key)?; deleted_bytes_len = self.db.delete_subspace_val(self.last_height, key)?; @@ -808,6 +808,44 @@ where } } +impl StorageWrite for &mut Storage +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + fn write( + &mut self, + key: &crate::types::storage::Key, + val: T, + ) -> storage_api::Result<()> { + let val = val.try_to_vec().unwrap(); + self.write_bytes(key, val) + } + + fn write_bytes( + &mut self, + key: &crate::types::storage::Key, + val: impl AsRef<[u8]>, + ) -> storage_api::Result<()> { + let _ = self + .db + .write_subspace_val(self.block.height, key, val) + .into_storage_result()?; + Ok(()) + } + + fn delete( + &mut self, + key: &crate::types::storage::Key, + ) -> storage_api::Result<()> { + let _ = self + .db + .delete_subspace_val(self.block.height, key) + .into_storage_result()?; + Ok(()) + } +} + impl From for Error { fn from(error: MerkleTreeError) -> Self { Self::MerkleTreeError(error) diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index e6a186c408..73989bf942 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -131,3 +131,45 @@ where self.key.push(&LEN_SUBKEY.to_owned()).unwrap() } } + +#[cfg(test)] +mod test { + use super::*; + use crate::ledger::storage::testing::TestStorage; + + #[test] + fn test_lazy_vec_basics() -> storage_api::Result<()> { + let mut storage = TestStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_vec = LazyVec::::new(key); + + // The vec should be empty at first + assert!(lazy_vec.is_empty(&storage)?); + assert!(lazy_vec.len(&storage)? == 0); + assert!(lazy_vec.iter(&storage)?.next().is_none()); + assert!(lazy_vec.pop(&mut storage)?.is_none()); + assert!(lazy_vec.get(&storage, 0)?.is_none()); + assert!(lazy_vec.get(&storage, 1)?.is_none()); + + // Push a new value and check that it's added + lazy_vec.push(&mut storage, 15_u32)?; + assert!(!lazy_vec.is_empty(&storage)?); + assert!(lazy_vec.len(&storage)? == 1); + assert_eq!(lazy_vec.iter(&storage)?.next().unwrap()?, 15_u32); + assert_eq!(lazy_vec.get(&storage, 0)?.unwrap(), 15_u32); + assert!(lazy_vec.get(&storage, 1)?.is_none()); + + // Pop the last value and check that the vec is empty again + let popped = lazy_vec.pop(&mut storage)?.unwrap(); + assert_eq!(popped, 15_u32); + assert!(lazy_vec.is_empty(&storage)?); + assert!(lazy_vec.len(&storage)? == 0); + assert!(lazy_vec.iter(&storage)?.next().is_none()); + assert!(lazy_vec.pop(&mut storage)?.is_none()); + assert!(lazy_vec.get(&storage, 0)?.is_none()); + assert!(lazy_vec.get(&storage, 1)?.is_none()); + + Ok(()) + } +} From fff77348a1d18879786e44fdef1bb3d026f2d472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 17:47:36 +0200 Subject: [PATCH 052/197] storage_api/collections/lazy: add basic tests and missing methods --- .../storage_api/collections/lazy_hashmap.rs | 96 ++++++++++++++++++- .../storage_api/collections/lazy_hashset.rs | 67 ++++++++++++- .../storage_api/collections/lazy_map.rs | 92 +++++++++++++++++- .../storage_api/collections/lazy_set.rs | 72 +++++++++++++- 4 files changed, 314 insertions(+), 13 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs index d626b75451..f3330df8e3 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs @@ -6,7 +6,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; use super::hasher::hash_for_storage_key; -use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::types::storage; /// Subkey corresponding to the data elements of the LazyMap @@ -125,14 +125,51 @@ where Self::read_key_val(storage, &data_key) } + /// Returns whether the set contains a value. + pub fn contains(&self, storage: &S, key: &K) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + storage.has_key(&self.get_data_key(key)) + } + + /// Reads the number of elements in the map. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded maps to avoid gas usage increasing with the length of the + /// set. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + iter.count().try_into().into_storage_result() + } + + /// Returns whether the map contains no elements. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded maps to avoid gas usage increasing with the length of the + /// set. + pub fn is_empty(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let mut iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + Ok(iter.next().is_none()) + } + /// An iterator visiting all key-value elements. The iterator element type /// is `Result<(K, V)>`, because iterator's call to `next` may fail with /// e.g. out of gas or data decoding error. /// /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the + /// on unbounded maps to avoid gas usage increasing with the length of the /// map. - pub fn iter<'iter, S>( + pub fn iter<'iter>( &self, storage: &'iter impl StorageRead<'iter>, ) -> Result> + 'iter> { @@ -177,3 +214,56 @@ where self.get_data_prefix().push(&hash_str).unwrap() } } + +#[cfg(test)] +mod test { + use super::*; + use crate::ledger::storage::testing::TestStorage; + + #[test] + fn test_lazy_hash_map_basics() -> storage_api::Result<()> { + let mut storage = TestStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_map = LazyHashMap::::new(key); + + // The map should be empty at first + assert!(lazy_map.is_empty(&storage)?); + assert!(lazy_map.len(&storage)? == 0); + assert!(!lazy_map.contains(&storage, &0)?); + assert!(!lazy_map.contains(&storage, &1)?); + assert!(lazy_map.iter(&storage)?.next().is_none()); + assert!(lazy_map.get(&storage, &0)?.is_none()); + assert!(lazy_map.get(&storage, &1)?.is_none()); + assert!(lazy_map.remove(&mut storage, &0)?.is_none()); + assert!(lazy_map.remove(&mut storage, &1)?.is_none()); + + // Insert a new value and check that it's added + let (key, val) = (123, "Test".to_string()); + lazy_map.insert(&mut storage, key, val.clone())?; + assert!(!lazy_map.contains(&storage, &0)?); + assert!(lazy_map.contains(&storage, &key)?); + assert!(!lazy_map.is_empty(&storage)?); + assert!(lazy_map.len(&storage)? == 1); + assert_eq!( + lazy_map.iter(&storage)?.next().unwrap()?, + (key, val.clone()) + ); + assert!(lazy_map.get(&storage, &0)?.is_none()); + assert_eq!(lazy_map.get(&storage, &key)?.unwrap(), val); + + // Remove the last value and check that the map is empty again + let removed = lazy_map.remove(&mut storage, &key)?.unwrap(); + assert_eq!(removed, val); + assert!(lazy_map.is_empty(&storage)?); + assert!(lazy_map.len(&storage)? == 0); + assert!(!lazy_map.contains(&storage, &0)?); + assert!(!lazy_map.contains(&storage, &1)?); + assert!(lazy_map.get(&storage, &0)?.is_none()); + assert!(lazy_map.get(&storage, &key)?.is_none()); + assert!(lazy_map.iter(&storage)?.next().is_none()); + assert!(lazy_map.remove(&mut storage, &key)?.is_none()); + + Ok(()) + } +} diff --git a/shared/src/ledger/storage_api/collections/lazy_hashset.rs b/shared/src/ledger/storage_api/collections/lazy_hashset.rs index 96a31ecef7..c9bd01a34c 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashset.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashset.rs @@ -6,7 +6,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; use super::hasher::hash_for_storage_key; -use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::types::storage; /// Subkey corresponding to the data elements of the LazySet @@ -47,14 +47,14 @@ where /// Adds a value to the set. If the set did not have this value present, /// `Ok(true)` is returned, `Ok(false)` otherwise. - pub fn insert(&self, storage: &mut S, val: &T) -> Result + pub fn insert(&self, storage: &mut S, val: T) -> Result where S: StorageWrite + for<'iter> StorageRead<'iter>, { - if self.contains(storage, val)? { + if self.contains(storage, &val)? { Ok(false) } else { - let data_key = self.get_data_key(val); + let data_key = self.get_data_key(&val); storage.write(&data_key, &val)?; Ok(true) } @@ -81,6 +81,21 @@ where Ok(value.is_some()) } + /// Reads the number of elements in the set. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + iter.count().try_into().into_storage_result() + } + /// Returns whether the set contains no elements. pub fn is_empty(&self, storage: &S) -> Result where @@ -119,3 +134,47 @@ where self.get_data_prefix().push(&hash_str).unwrap() } } + +#[cfg(test)] +mod test { + use super::*; + use crate::ledger::storage::testing::TestStorage; + + #[test] + fn test_lazy_set_basics() -> storage_api::Result<()> { + let mut storage = TestStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_set = LazyHashSet::::new(key); + + // The set should be empty at first + assert!(lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 0); + assert!(!lazy_set.contains(&storage, &0)?); + assert!(lazy_set.is_empty(&storage)?); + assert!(lazy_set.iter(&storage)?.next().is_none()); + assert!(!lazy_set.remove(&mut storage, &0)?); + assert!(!lazy_set.remove(&mut storage, &1)?); + + // Insert a new value and check that it's added + let val = 1337; + lazy_set.insert(&mut storage, val)?; + assert!(!lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 1); + assert_eq!(lazy_set.iter(&storage)?.next().unwrap()?, val.clone()); + assert!(!lazy_set.contains(&storage, &0)?); + assert!(lazy_set.contains(&storage, &val)?); + + // Remove the last value and check that the set is empty again + let is_removed = lazy_set.remove(&mut storage, &val)?; + assert!(is_removed); + assert!(lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 0); + assert!(!lazy_set.contains(&storage, &0)?); + assert!(lazy_set.is_empty(&storage)?); + assert!(!lazy_set.remove(&mut storage, &0)?); + assert!(!lazy_set.remove(&mut storage, &1)?); + + Ok(()) + } +} diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 6da5c97031..a89cb02469 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -95,12 +95,49 @@ where Self::read_key_val(storage, &data_key) } + /// Returns whether the set contains a value. + pub fn contains(&self, storage: &S, key: &K) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + storage.has_key(&self.get_data_key(key)) + } + + /// Reads the number of elements in the map. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded maps to avoid gas usage increasing with the length of the + /// set. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + iter.count().try_into().into_storage_result() + } + + /// Returns whether the map contains no elements. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded maps to avoid gas usage increasing with the length of the + /// set. + pub fn is_empty(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let mut iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + Ok(iter.next().is_none()) + } + /// An iterator visiting all key-value elements. The iterator element type /// is `Result<(K, V)>`, because iterator's call to `next` may fail with /// e.g. out of gas or data decoding error. /// /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the + /// on unbounded maps to avoid gas usage increasing with the length of the /// map. pub fn iter<'iter>( &self, @@ -150,3 +187,56 @@ where self.get_data_prefix().push(&key_str).unwrap() } } + +#[cfg(test)] +mod test { + use super::*; + use crate::ledger::storage::testing::TestStorage; + + #[test] + fn test_lazy_map_basics() -> storage_api::Result<()> { + let mut storage = TestStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_map = LazyMap::::new(key); + + // The map should be empty at first + assert!(lazy_map.is_empty(&storage)?); + assert!(lazy_map.len(&storage)? == 0); + assert!(!lazy_map.contains(&storage, &0)?); + assert!(!lazy_map.contains(&storage, &1)?); + assert!(lazy_map.iter(&storage)?.next().is_none()); + assert!(lazy_map.get(&storage, &0)?.is_none()); + assert!(lazy_map.get(&storage, &1)?.is_none()); + assert!(lazy_map.remove(&mut storage, &0)?.is_none()); + assert!(lazy_map.remove(&mut storage, &1)?.is_none()); + + // Insert a new value and check that it's added + let (key, val) = (123, "Test".to_string()); + lazy_map.insert(&mut storage, key, val.clone())?; + assert!(!lazy_map.contains(&storage, &0)?); + assert!(lazy_map.contains(&storage, &key)?); + assert!(!lazy_map.is_empty(&storage)?); + assert!(lazy_map.len(&storage)? == 1); + assert_eq!( + lazy_map.iter(&storage)?.next().unwrap()?, + (key, val.clone()) + ); + assert!(lazy_map.get(&storage, &0)?.is_none()); + assert_eq!(lazy_map.get(&storage, &key)?.unwrap(), val); + + // Remove the last value and check that the map is empty again + let removed = lazy_map.remove(&mut storage, &key)?.unwrap(); + assert_eq!(removed, val); + assert!(lazy_map.is_empty(&storage)?); + assert!(lazy_map.len(&storage)? == 0); + assert!(!lazy_map.contains(&storage, &0)?); + assert!(!lazy_map.contains(&storage, &1)?); + assert!(lazy_map.get(&storage, &0)?.is_none()); + assert!(lazy_map.get(&storage, &key)?.is_none()); + assert!(lazy_map.iter(&storage)?.next().is_none()); + assert!(lazy_map.remove(&mut storage, &key)?.is_none()); + + Ok(()) + } +} diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 1e1f259c55..0bf533dea9 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -42,14 +42,14 @@ where /// Adds a value to the set. If the set did not have this value present, /// `Ok(true)` is returned, `Ok(false)` otherwise. - pub fn insert(&self, storage: &mut S, val: &T) -> Result + pub fn insert(&self, storage: &mut S, val: T) -> Result where S: StorageWrite + for<'iter> StorageRead<'iter>, { - if self.contains(storage, val)? { + if self.contains(storage, &val)? { Ok(false) } else { - let data_key = self.get_data_key(val); + let data_key = self.get_data_key(&val); // The actual value is written into the key, so the value written to // the storage is empty (unit) storage.write(&data_key, ())?; @@ -74,11 +74,29 @@ where where S: for<'iter> StorageRead<'iter>, { - let value: Option<()> = storage.read(&self.get_data_key(val))?; - Ok(value.is_some()) + storage.has_key(&self.get_data_key(val)) + } + + /// Reads the number of elements in the set. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + iter.count().try_into().into_storage_result() } /// Returns whether the set contains no elements. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. pub fn is_empty(&self, storage: &S) -> Result where S: for<'iter> StorageRead<'iter>, @@ -122,3 +140,47 @@ where self.get_data_prefix().push(&key_str).unwrap() } } + +#[cfg(test)] +mod test { + use super::*; + use crate::ledger::storage::testing::TestStorage; + + #[test] + fn test_lazy_set_basics() -> storage_api::Result<()> { + let mut storage = TestStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_set = LazySet::::new(key); + + // The set should be empty at first + assert!(lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 0); + assert!(!lazy_set.contains(&storage, &0)?); + assert!(lazy_set.is_empty(&storage)?); + assert!(lazy_set.iter(&storage)?.next().is_none()); + assert!(!lazy_set.remove(&mut storage, &0)?); + assert!(!lazy_set.remove(&mut storage, &1)?); + + // Insert a new value and check that it's added + let val = 1337; + lazy_set.insert(&mut storage, val)?; + assert!(!lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 1); + assert_eq!(lazy_set.iter(&storage)?.next().unwrap()?, val.clone()); + assert!(!lazy_set.contains(&storage, &0)?); + assert!(lazy_set.contains(&storage, &val)?); + + // Remove the last value and check that the set is empty again + let is_removed = lazy_set.remove(&mut storage, &val)?; + assert!(is_removed); + assert!(lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 0); + assert!(!lazy_set.contains(&storage, &0)?); + assert!(lazy_set.is_empty(&storage)?); + assert!(!lazy_set.remove(&mut storage, &0)?); + assert!(!lazy_set.remove(&mut storage, &1)?); + + Ok(()) + } +} From ca50fb950cefd968f8e7b77bd8dc617388b5def1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 17:52:44 +0200 Subject: [PATCH 053/197] fix clippy --- shared/src/ledger/storage/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 3a1bfa697e..bd969fa464 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -459,7 +459,7 @@ where // Note that this method is the same as `StorageWrite::delete`, // but with gas and storage bytes len diff accounting let mut deleted_bytes_len = 0; - if Self::has_key(&self, key)?.0 { + if self.has_key(key)?.0 { self.block.tree.delete(key)?; deleted_bytes_len = self.db.delete_subspace_val(self.last_height, key)?; From 5819b23ce380ebe1a4980c306d738b41d72c5576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 23 Aug 2022 17:09:18 +0200 Subject: [PATCH 054/197] add lazy_vec validation --- .../storage_api/collections/lazy_vec.rs | 308 +++++++++++++++++- shared/src/ledger/storage_api/mod.rs | 1 + .../src/ledger/storage_api/validation/mod.rs | 53 +++ shared/src/types/storage.rs | 28 ++ 4 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 shared/src/ledger/storage_api/validation/mod.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index 73989bf942..c9b836f774 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -1,11 +1,17 @@ //! Lazy dynamically-sized vector. +use std::collections::BTreeSet; use std::marker::PhantomData; +use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; +use derivative::Derivative; +use thiserror::Error; use super::super::Result; +use crate::ledger::storage_api::validation::{self, Data}; use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::ledger::vp_env::VpEnv; use crate::types::storage; /// Subkey pointing to the length of the LazyVec @@ -13,6 +19,9 @@ pub const LEN_SUBKEY: &str = "len"; /// Subkey corresponding to the data elements of the LazyVec pub const DATA_SUBKEY: &str = "data"; +/// Using `u64` for vector's indices +pub type Index = u64; + /// Lazy dynamically-sized vector. /// /// This can be used as an alternative to `std::collections::Vec`. In the lazy @@ -24,6 +33,69 @@ pub struct LazyVec { phantom: PhantomData, } +/// Possible sub-keys of a [`LazyVec`] +pub enum SubKey { + /// Length sub-key + Len, + /// Data sub-key, further sub-keyed by its index + Data(Index), +} + +/// Possible sub-keys of a [`LazyVec`], together with their [`validation::Data`] +/// that contains prior and posterior state. +#[derive(Debug)] +pub enum SubKeyWithData { + /// Length sub-key + Len(Data), + /// Data sub-key, further sub-keyed by its index + Data(Index, Data), +} + +/// Possible actions that can modify a [`LazyVec`]. This roughly corresponds to +/// the methods that have `StorageWrite` access. +pub enum Action { + /// Push a value `T` into a [`LazyVec`] + Push(T), + /// Pop a value `T` from a [`LazyVec`] + Pop(T), +} + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum ValidationError { + #[error("Incorrect difference in LazyVec's length")] + InvalidLenDiff, + #[error("An empty LazyVec must be deleted from storage")] + EmptyVecShouldBeDeleted, + #[error("Push at a wrong index. Got {got}, expected {expected}.")] + UnexpectedPushIndex { got: Index, expected: Index }, + #[error("Pop at a wrong index. Got {got}, expected {expected}.")] + UnexpectedPopIndex { got: Index, expected: Index }, + #[error( + "Update (combination of pop and push) at a wrong index. Got {got}, \ + expected {expected}." + )] + UnexpectedUpdateIndex { got: Index, expected: Index }, + #[error("An index has overflown its representation: {0}")] + IndexOverflow(>::Error), + #[error("Unexpected underflow in `{0} - {0}`")] + UnexpectedUnderflow(Index, Index), +} + +/// [`LazyVec`] validation result +pub type ValidationResult = std::result::Result; + +/// [`LazyVec`] validation builder from storage changes. The changes can be +/// accumulated with `LazyVec::validate()` and then turned into a list +/// of valid actions on the vector with `ValidationBuilder::build()`. +#[derive(Debug, Derivative)] +// https://mcarton.github.io/rust-derivative/latest/Default.html#custom-bound +#[derivative(Default(bound = ""))] +pub struct ValidationBuilder { + /// The accumulator of found changes under the vector + pub changes: Vec>, +} + impl LazyVec where T: BorshSerialize + BorshDeserialize + 'static, @@ -73,7 +145,7 @@ where } /// Read an element at the index or `Ok(None)` if out of bounds. - pub fn get(&self, storage: &S, index: u64) -> Result> + pub fn get(&self, storage: &S, index: Index) -> Result> where S: for<'iter> StorageRead<'iter>, { @@ -116,13 +188,64 @@ where })) } + /// Check if the given storage key is a LazyVec sub-key and if so return + /// which one + pub fn is_sub_key(&self, key: &storage::Key) -> Option { + if let Some((prefix, storage::DbKeySeg::StringSeg(last))) = + key.split_last() + { + if let Ok(index) = Index::from_str(last) { + if let Some((prefix, storage::DbKeySeg::StringSeg(snd_last))) = + prefix.split_last() + { + if snd_last == DATA_SUBKEY && prefix.eq_owned(&self.key) { + return Some(SubKey::Data(index)); + } + } + } else if last == LEN_SUBKEY && prefix.eq_owned(&self.key) { + return Some(SubKey::Len); + } + } + None + } + + /// Accumulate storage changes inside a [`ValidationBuilder`] + pub fn validate( + &self, + builder: &mut Option>, + env: &ENV, + key_changed: storage::Key, + ) -> std::result::Result<(), ENV::Error> + where + ENV: VpEnv, + { + if let Some(sub) = self.is_sub_key(&key_changed) { + let change = match sub { + SubKey::Len => { + let data = validation::read_data(env, &key_changed)?; + data.map(SubKeyWithData::Len) + } + SubKey::Data(index) => { + let data = validation::read_data(env, &key_changed)?; + data.map(|data| SubKeyWithData::Data(index, data)) + } + }; + if let Some(change) = change { + let builder = + builder.get_or_insert(ValidationBuilder::default()); + builder.changes.push(change) + } + } + Ok(()) + } + /// Get the prefix of set's elements storage fn get_data_prefix(&self) -> storage::Key { self.key.push(&DATA_SUBKEY.to_owned()).unwrap() } /// Get the sub-key of vector's elements storage - fn get_data_key(&self, index: u64) -> storage::Key { + fn get_data_key(&self, index: Index) -> storage::Key { self.get_data_prefix().push(&index.to_string()).unwrap() } @@ -132,6 +255,187 @@ where } } +impl ValidationBuilder { + /// Validate storage changes and if valid, build from them a list of + /// actions. + /// + /// The validation rules for a [`LazyVec`] are: + /// - A difference in the vector's length must correspond to the + /// difference in how many elements where pushed versus how many + /// elements were popped. + /// - An empty vector must be deleted from storage + /// - In addition, we check that indices of any changes are within an + /// expected range (i.e. the vectors indices should always be + /// monotonically increasing from zero) + pub fn build(self) -> ValidationResult>> { + let mut actions = vec![]; + + // We need to accumlate some values for what's changed + let mut post_gt_pre = false; + let mut len_diff: u64 = 0; + let mut len_pre: u64 = 0; + let mut added = BTreeSet::::default(); + let mut updated = BTreeSet::::default(); + let mut deleted = BTreeSet::::default(); + + for change in self.changes { + match change { + SubKeyWithData::Len(data) => match data { + Data::Add { post } => { + if post == 0 { + return Err( + ValidationError::EmptyVecShouldBeDeleted, + ); + } + post_gt_pre = true; + len_diff = post; + } + Data::Update { pre, post } => { + if post == 0 { + return Err( + ValidationError::EmptyVecShouldBeDeleted, + ); + } + if post > pre { + post_gt_pre = true; + len_diff = post - pre; + } else { + len_diff = pre - post; + } + len_pre = pre; + } + Data::Delete { pre } => { + len_diff = pre; + len_pre = pre; + } + }, + SubKeyWithData::Data(index, data) => match data { + Data::Add { post } => { + actions.push(Action::Push(post)); + added.insert(index); + } + Data::Update { pre, post } => { + actions.push(Action::Pop(pre)); + actions.push(Action::Push(post)); + updated.insert(index); + } + Data::Delete { pre } => { + actions.push(Action::Pop(pre)); + deleted.insert(index); + } + }, + } + } + let added_len: u64 = deleted + .len() + .try_into() + .map_err(ValidationError::IndexOverflow)?; + let deleted_len: u64 = deleted + .len() + .try_into() + .map_err(ValidationError::IndexOverflow)?; + + if len_diff != 0 + && !(if post_gt_pre { + deleted_len + len_diff == added_len + } else { + added_len + len_diff == deleted_len + }) + { + return Err(ValidationError::InvalidLenDiff); + } + + let mut last_added = Option::None; + // Iterate additions in increasing order of indices + for index in added { + if let Some(last_added) = last_added { + // Following additions should be at monotonically increasing + // indices + let expected = last_added + 1; + if expected != index { + return Err(ValidationError::UnexpectedPushIndex { + got: index, + expected, + }); + } + } else if index != len_pre { + // The first addition must be at the pre length value. + // If something is deleted and a new value is added + // in its place, it will go through `Data::Update` + // instead. + return Err(ValidationError::UnexpectedPushIndex { + got: index, + expected: len_pre, + }); + } + last_added = Some(index); + } + + let mut last_deleted = Option::None; + // Also iterate deletions in increasing order of indices + for index in deleted { + if let Some(last_added) = last_deleted { + // Following deletions should be at monotonically increasing + // indices + let expected = last_added + 1; + if expected != index { + return Err(ValidationError::UnexpectedPopIndex { + got: index, + expected, + }); + } + } + last_deleted = Some(index); + } + if let Some(index) = last_deleted { + if len_pre > 0 { + let expected = len_pre - 1; + if index != expected { + // The last deletion must be at the pre length value minus 1 + return Err(ValidationError::UnexpectedPopIndex { + got: index, + expected: len_pre, + }); + } + } + } + + // And finally iterate updates in increasing order of indices + let mut last_updated = Option::None; + for index in updated { + if let Some(last_updated) = last_updated { + // Following additions should be at monotonically increasing + // indices + let expected = last_updated + 1; + if expected != index { + return Err(ValidationError::UnexpectedUpdateIndex { + got: index, + expected, + }); + } + } + last_updated = Some(index); + } + if let Some(index) = last_updated { + let expected = len_pre.checked_sub(deleted_len).ok_or( + ValidationError::UnexpectedUnderflow(len_pre, deleted_len), + )?; + if index != expected { + // The last update must be at the pre length value minus + // deleted_len. + // If something is added and then deleted in a + // single tx, it will never be visible here. + return Err(ValidationError::UnexpectedUpdateIndex { + got: index, + expected: len_pre, + }); + } + } + + Ok(actions) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index 8cb04434e2..b806f35801 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -3,6 +3,7 @@ pub mod collections; mod error; +pub mod validation; use borsh::{BorshDeserialize, BorshSerialize}; pub use error::{CustomError, Error, OptionExt, Result, ResultExt}; diff --git a/shared/src/ledger/storage_api/validation/mod.rs b/shared/src/ledger/storage_api/validation/mod.rs new file mode 100644 index 0000000000..30c1e2566b --- /dev/null +++ b/shared/src/ledger/storage_api/validation/mod.rs @@ -0,0 +1,53 @@ +//! Storage change validation helpers + +use std::fmt::Debug; + +use borsh::BorshDeserialize; + +use crate::ledger::vp_env::VpEnv; +use crate::types::storage; + +/// Data update with prior and posterior state. +#[derive(Clone, Debug)] +pub enum Data { + /// Newly added value + Add { + /// Posterior state + post: T, + }, + /// Updated value prior and posterior state + Update { + /// Prior state + pre: T, + /// Posterior state + post: T, + }, + /// Deleted value + Delete { + /// Prior state + pre: T, + }, +} + +/// Read the prior and posterior state for the given key. +pub fn read_data( + env: &ENV, + key: &storage::Key, +) -> Result>, ENV::Error> +where + T: BorshDeserialize, + ENV: VpEnv, +{ + let pre = env.read_pre(key)?; + let post = env.read_post(key)?; + Ok(match (pre, post) { + (None, None) => { + // If the key was inserted and then deleted in the same tx, we don't + // need to validate it as it's not visible to any VPs + None + } + (None, Some(post)) => Some(Data::Add { post }), + (Some(pre), None) => Some(Data::Delete { pre }), + (Some(pre), Some(post)) => Some(Data::Update { pre, post }), + }) +} diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 289f115c8f..49abe7197d 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -210,6 +210,13 @@ pub struct Key { pub segments: Vec, } +/// A [`Key`] made of borrowed key segments [`DbKeySeg`]. +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct KeyRef<'a> { + /// Reference of key segments + pub segments: &'a [DbKeySeg], +} + impl From for Key { fn from(seg: DbKeySeg) -> Self { Self { @@ -283,6 +290,13 @@ impl Key { self.segments.last() } + /// Returns the prefix before the last segment and last segment of the key, + /// or `None` if it is empty. + pub fn split_last(&self) -> Option<(KeyRef<'_>, &DbKeySeg)> { + let (last, prefix) = self.segments.split_last()?; + Some((KeyRef { segments: prefix }, last)) + } + /// Returns a key of the validity predicate of the given address /// Only this function can push "?" segment for validity predicate pub fn validity_predicate(addr: &Address) -> Self { @@ -372,6 +386,20 @@ impl Display for Key { } } +impl KeyRef<'_> { + /// Check if [`KeyRef`] is equal to a [`Key`]. + pub fn eq_owned(&self, other: &Key) -> bool { + self.segments == other.segments + } + + /// Returns the prefix before the last segment and last segment of the key, + /// or `None` if it is empty. + pub fn split_last(&self) -> Option<(KeyRef<'_>, &DbKeySeg)> { + let (last, prefix) = self.segments.split_last()?; + Some((KeyRef { segments: prefix }, last)) + } +} + // TODO use std::convert::{TryFrom, Into}? /// Represents a segment in a path that may be used as a database key pub trait KeySeg { From a474c802b755ff7f133a2ca0829ba836ac70ef52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 29 Aug 2022 14:16:59 +0200 Subject: [PATCH 055/197] storage_api/collections/lazy: allow nested lazy collections in LazyMap --- .../storage_api/collections/lazy_hashmap.rs | 15 ++-- .../storage_api/collections/lazy_hashset.rs | 13 +-- .../storage_api/collections/lazy_map.rs | 72 +++++++++++----- .../storage_api/collections/lazy_set.rs | 14 ++-- .../storage_api/collections/lazy_vec.rs | 83 ++++++++++--------- .../src/ledger/storage_api/collections/mod.rs | 15 ++++ 6 files changed, 134 insertions(+), 78 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs index f3330df8e3..4098e6d8c9 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs @@ -6,6 +6,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; use super::hasher::hash_for_storage_key; +use super::LazyCollection; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::types::storage; @@ -41,20 +42,22 @@ struct KeyVal { val: V, } -impl LazyHashMap -where - K: BorshDeserialize + BorshSerialize + 'static, - V: BorshDeserialize + BorshSerialize + 'static, -{ +impl LazyCollection for LazyHashMap { /// Create or use an existing map with the given storage `key`. - pub fn new(key: storage::Key) -> Self { + fn new(key: storage::Key) -> Self { Self { key, phantom_k: PhantomData, phantom_v: PhantomData, } } +} +impl LazyHashMap +where + K: BorshDeserialize + BorshSerialize + 'static, + V: BorshDeserialize + BorshSerialize + 'static, +{ /// Inserts a key-value pair into the map. /// /// If the map did not have this key present, `None` is returned. diff --git a/shared/src/ledger/storage_api/collections/lazy_hashset.rs b/shared/src/ledger/storage_api/collections/lazy_hashset.rs index c9bd01a34c..a110072371 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashset.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashset.rs @@ -6,6 +6,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; use super::hasher::hash_for_storage_key; +use super::LazyCollection; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::types::storage; @@ -33,18 +34,20 @@ pub struct LazyHashSet { phantom: PhantomData, } -impl LazyHashSet -where - T: BorshSerialize + BorshDeserialize + 'static, -{ +impl LazyCollection for LazyHashSet { /// Create or use an existing set with the given storage `key`. - pub fn new(key: storage::Key) -> Self { + fn new(key: storage::Key) -> Self { Self { key, phantom: PhantomData, } } +} +impl LazyHashSet +where + T: BorshSerialize + BorshDeserialize + 'static, +{ /// Adds a value to the set. If the set did not have this value present, /// `Ok(true)` is returned, `Ok(false)` otherwise. pub fn insert(&self, storage: &mut S, val: T) -> Result diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index a89cb02469..ddec03363e 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -5,7 +5,7 @@ use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; -use super::ReadError; +use super::{LazyCollection, ReadError}; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::types::storage::{self, KeySeg}; @@ -31,20 +31,65 @@ pub struct LazyMap { phantom_v: PhantomData, } -impl LazyMap +impl LazyCollection for LazyMap where K: storage::KeySeg, - V: BorshDeserialize + BorshSerialize + 'static, { /// Create or use an existing map with the given storage `key`. - pub fn new(key: storage::Key) -> Self { + fn new(key: storage::Key) -> Self { Self { key, phantom_k: PhantomData, phantom_v: PhantomData, } } +} + +// Generic `LazyMap` methods that require no bounds on values `V` +impl LazyMap +where + K: storage::KeySeg, +{ + /// Returns whether the set contains a value. + pub fn contains(&self, storage: &S, key: &K) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + storage.has_key(&self.get_data_key(key)) + } + + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() + } + + /// Get the sub-key of a given element + fn get_data_key(&self, key: &K) -> storage::Key { + let key_str = key.to_db_key(); + self.get_data_prefix().push(&key_str).unwrap() + } +} + +// `LazyMap` methods with nested `LazyCollection`s `V` +impl LazyMap +where + K: storage::KeySeg, + V: LazyCollection, +{ + /// Get a nested collection at given key `key`. If there is no nested + /// collection at the given key, a new empty one will be provided. The + /// nested collection may be manipulated through its methods. + pub fn at(&self, key: &K) -> V { + V::new(self.get_data_key(key)) + } +} +// `LazyMap` methods with borsh encoded values `V` +impl LazyMap +where + K: storage::KeySeg, + V: BorshDeserialize + BorshSerialize + 'static, +{ /// Inserts a key-value pair into the map. /// /// The full storage key identifies the key in the pair, while the value is @@ -95,14 +140,6 @@ where Self::read_key_val(storage, &data_key) } - /// Returns whether the set contains a value. - pub fn contains(&self, storage: &S, key: &K) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - storage.has_key(&self.get_data_key(key)) - } - /// Reads the number of elements in the map. /// /// Note that this function shouldn't be used in transactions and VPs code @@ -175,17 +212,6 @@ where ) -> Result<()> { storage.write(storage_key, val) } - - /// Get the prefix of set's elements storage - fn get_data_prefix(&self) -> storage::Key { - self.key.push(&DATA_SUBKEY.to_owned()).unwrap() - } - - /// Get the sub-key of a given element - fn get_data_key(&self, key: &K) -> storage::Key { - let key_str = key.to_db_key(); - self.get_data_prefix().push(&key_str).unwrap() - } } #[cfg(test)] diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 0bf533dea9..8918b55efd 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use super::super::Result; -use super::ReadError; +use super::{LazyCollection, ReadError}; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::types::storage::{self, KeySeg}; @@ -28,18 +28,20 @@ pub struct LazySet { phantom: PhantomData, } -impl LazySet -where - T: storage::KeySeg, -{ +impl LazyCollection for LazySet { /// Create or use an existing set with the given storage `key`. - pub fn new(key: storage::Key) -> Self { + fn new(key: storage::Key) -> Self { Self { key, phantom: PhantomData, } } +} +impl LazySet +where + T: storage::KeySeg, +{ /// Adds a value to the set. If the set did not have this value present, /// `Ok(true)` is returned, `Ok(false)` otherwise. pub fn insert(&self, storage: &mut S, val: T) -> Result diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index c9b836f774..f87d4c126f 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -9,6 +9,7 @@ use derivative::Derivative; use thiserror::Error; use super::super::Result; +use super::LazyCollection; use crate::ledger::storage_api::validation::{self, Data}; use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::ledger::vp_env::VpEnv; @@ -96,18 +97,57 @@ pub struct ValidationBuilder { pub changes: Vec>, } -impl LazyVec -where - T: BorshSerialize + BorshDeserialize + 'static, -{ +impl LazyCollection for LazyVec { /// Create or use an existing vector with the given storage `key`. - pub fn new(key: storage::Key) -> Self { + fn new(key: storage::Key) -> Self { Self { key, phantom: PhantomData, } } +} + +// Generic `LazyVec` methods that require no bounds on values `T` +impl LazyVec { + /// Reads the number of elements in the vector. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let len = storage.read(&self.get_len_key())?; + Ok(len.unwrap_or_default()) + } + + /// Returns `true` if the vector contains no elements. + pub fn is_empty(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + Ok(self.len(storage)? == 0) + } + + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() + } + + /// Get the sub-key of vector's elements storage + fn get_data_key(&self, index: Index) -> storage::Key { + self.get_data_prefix().push(&index.to_string()).unwrap() + } + + /// Get the sub-key of vector's length storage + fn get_len_key(&self) -> storage::Key { + self.key.push(&LEN_SUBKEY.to_owned()).unwrap() + } +} +// `LazyVec` methods with borsh encoded values `T` +impl LazyVec +where + T: BorshSerialize + BorshDeserialize + 'static, +{ /// Appends an element to the back of a collection. pub fn push(&self, storage: &mut S, val: T) -> Result<()> where @@ -152,24 +192,6 @@ where storage.read(&self.get_data_key(index)) } - /// Reads the number of elements in the vector. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let len = storage.read(&self.get_len_key())?; - Ok(len.unwrap_or_default()) - } - - /// Returns `true` if the vector contains no elements. - pub fn is_empty(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - Ok(self.len(storage)? == 0) - } - /// An iterator visiting all elements. The iterator element type is /// `Result`, because iterator's call to `next` may fail with e.g. out of /// gas or data decoding error. @@ -238,21 +260,6 @@ where } Ok(()) } - - /// Get the prefix of set's elements storage - fn get_data_prefix(&self) -> storage::Key { - self.key.push(&DATA_SUBKEY.to_owned()).unwrap() - } - - /// Get the sub-key of vector's elements storage - fn get_data_key(&self, index: Index) -> storage::Key { - self.get_data_prefix().push(&index.to_string()).unwrap() - } - - /// Get the sub-key of vector's length storage - fn get_len_key(&self) -> storage::Key { - self.key.push(&LEN_SUBKEY.to_owned()).unwrap() - } } impl ValidationBuilder { diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index 156615b9de..01bcae439e 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -22,9 +22,24 @@ pub use lazy_map::LazyMap; pub use lazy_set::LazySet; pub use lazy_vec::LazyVec; +use crate::types::storage; + #[allow(missing_docs)] #[derive(Error, Debug)] pub enum ReadError { #[error("A storage key was unexpectedly empty")] UnexpectedlyEmptyStorageKey, } + +/// A lazy collection of storage values is a handler with some storage prefix +/// that is given to its `fn new()`. The values are typically nested under this +/// prefix and they can be changed individually (e.g. without reading in the +/// whole collection) and their changes directly indicated to the validity +/// predicates, which do not need to iterate the whole collection pre/post to +/// find diffs. +/// +/// An empty collection must be deleted from storage. +pub trait LazyCollection { + /// Create or use an existing vector with the given storage `key`. + fn new(key: storage::Key) -> Self; +} From 51036681d046e5b8100605d68cd8376b048dbbb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 1 Sep 2022 10:43:32 +0200 Subject: [PATCH 056/197] impl LazyCollection trait for all the collections + refactor --- .../storage_api/collections/lazy_hashmap.rs | 23 +++---- .../storage_api/collections/lazy_hashset.rs | 1 + .../storage_api/collections/lazy_map.rs | 61 ++++++++++++++---- .../storage_api/collections/lazy_set.rs | 23 +++---- .../storage_api/collections/lazy_vec.rs | 62 +++++++++++++++---- 5 files changed, 120 insertions(+), 50 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs index 4098e6d8c9..46bc55fa6e 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs @@ -29,6 +29,7 @@ pub const DATA_SUBKEY: &str = "data"; /// /// Additionally, [`LazyHashMap`] also writes the unhashed values into the /// storage together with the values (using an internal `KeyVal` type). +#[derive(Debug)] pub struct LazyHashMap { key: storage::Key, phantom_k: PhantomData, @@ -136,33 +137,29 @@ where storage.has_key(&self.get_data_key(key)) } - /// Reads the number of elements in the map. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded maps to avoid gas usage increasing with the length of the - /// set. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result + /// Returns whether the map contains no elements. + pub fn is_empty(&self, storage: &S) -> Result where S: for<'iter> StorageRead<'iter>, { - let iter = + let mut iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - iter.count().try_into().into_storage_result() + Ok(iter.next().is_none()) } - /// Returns whether the map contains no elements. + /// Reads the number of elements in the map. /// /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded maps to avoid gas usage increasing with the length of the /// set. - pub fn is_empty(&self, storage: &S) -> Result + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result where S: for<'iter> StorageRead<'iter>, { - let mut iter = + let iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - Ok(iter.next().is_none()) + iter.count().try_into().into_storage_result() } /// An iterator visiting all key-value elements. The iterator element type diff --git a/shared/src/ledger/storage_api/collections/lazy_hashset.rs b/shared/src/ledger/storage_api/collections/lazy_hashset.rs index a110072371..06a6ef2295 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashset.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashset.rs @@ -29,6 +29,7 @@ pub const DATA_SUBKEY: &str = "data"; /// /// Additionally, [`LazyHashSet`] also writes the unhashed values into the /// storage. +#[derive(Debug)] pub struct LazyHashSet { key: storage::Key, phantom: PhantomData, diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index ddec03363e..103508404e 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -6,6 +6,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; use super::{LazyCollection, ReadError}; +use crate::ledger::storage_api::validation::Data; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::types::storage::{self, KeySeg}; @@ -25,12 +26,50 @@ pub const DATA_SUBKEY: &str = "data"; /// /// This is different from [`super::LazyHashMap`], which hashes borsh encoded /// key. +#[derive(Debug)] pub struct LazyMap { key: storage::Key, phantom_k: PhantomData, phantom_v: PhantomData, } +/// Possible sub-keys of a [`LazyMap`] +#[derive(Debug)] +pub enum SubKey { + /// Data sub-key, further sub-keyed by its literal map key + Data(K), +} + +/// Possible sub-keys of a [`LazyMap`], together with their [`validation::Data`] +/// that contains prior and posterior state. +#[derive(Debug)] +pub enum SubKeyWithData { + /// Data sub-key, further sub-keyed by its literal map key + Data(K, Data), +} + +/// Possible actions that can modify a [`LazyMap`]. This roughly corresponds to +/// the methods that have `StorageWrite` access. +/// TODO: In a nested collection, `V` may be an action inside the nested +/// collection. +#[derive(Debug)] +pub enum Action { + /// Insert or update a value `V` at key `K` in a [`LazyMap`]. + Insert(K, V), + /// Remove a value `V` at key `K` from a [`LazyMap`]. + Remove(K, V), +} + +/// TODO: In a nested collection, `V` may be an action inside the nested +/// collection. +#[derive(Debug)] +pub enum Nested { + /// Insert or update a value `V` at key `K` in a [`LazyMap`]. + Insert(K, V), + /// Remove a value `V` at key `K` from a [`LazyMap`]. + Remove(K, V), +} + impl LazyCollection for LazyMap where K: storage::KeySeg, @@ -140,33 +179,29 @@ where Self::read_key_val(storage, &data_key) } - /// Reads the number of elements in the map. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded maps to avoid gas usage increasing with the length of the - /// set. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result + /// Returns whether the map contains no elements. + pub fn is_empty(&self, storage: &S) -> Result where S: for<'iter> StorageRead<'iter>, { - let iter = + let mut iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - iter.count().try_into().into_storage_result() + Ok(iter.next().is_none()) } - /// Returns whether the map contains no elements. + /// Reads the number of elements in the map. /// /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded maps to avoid gas usage increasing with the length of the /// set. - pub fn is_empty(&self, storage: &S) -> Result + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result where S: for<'iter> StorageRead<'iter>, { - let mut iter = + let iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - Ok(iter.next().is_none()) + iter.count().try_into().into_storage_result() } /// An iterator visiting all key-value elements. The iterator element type diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 8918b55efd..fc301b09f1 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -23,6 +23,7 @@ pub const DATA_SUBKEY: &str = "data"; /// /// This is different from [`super::LazyHashSet`], which hashes borsh encoded /// values. +#[derive(Debug)] pub struct LazySet { key: storage::Key, phantom: PhantomData, @@ -79,33 +80,29 @@ where storage.has_key(&self.get_data_key(val)) } - /// Reads the number of elements in the set. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the - /// set. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result + /// Returns whether the set contains no elements. + pub fn is_empty(&self, storage: &S) -> Result where S: for<'iter> StorageRead<'iter>, { - let iter = + let mut iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - iter.count().try_into().into_storage_result() + Ok(iter.next().is_none()) } - /// Returns whether the set contains no elements. + /// Reads the number of elements in the set. /// /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded sets to avoid gas usage increasing with the length of the /// set. - pub fn is_empty(&self, storage: &S) -> Result + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result where S: for<'iter> StorageRead<'iter>, { - let mut iter = + let iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - Ok(iter.next().is_none()) + iter.count().try_into().into_storage_result() } /// An iterator visiting all elements. The iterator element type is diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index f87d4c126f..6dd57ca556 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -11,7 +11,7 @@ use thiserror::Error; use super::super::Result; use super::LazyCollection; use crate::ledger::storage_api::validation::{self, Data}; -use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::ledger::vp_env::VpEnv; use crate::types::storage; @@ -29,12 +29,14 @@ pub type Index = u64; /// vector, the elements do not reside in memory but are instead read and /// written to storage sub-keys of the storage `key` used to construct the /// vector. +#[derive(Debug)] pub struct LazyVec { key: storage::Key, phantom: PhantomData, } /// Possible sub-keys of a [`LazyVec`] +#[derive(Debug)] pub enum SubKey { /// Length sub-key Len, @@ -54,11 +56,21 @@ pub enum SubKeyWithData { /// Possible actions that can modify a [`LazyVec`]. This roughly corresponds to /// the methods that have `StorageWrite` access. +#[derive(Debug)] pub enum Action { /// Push a value `T` into a [`LazyVec`] Push(T), /// Pop a value `T` from a [`LazyVec`] Pop(T), + /// Update a value `T` at index from pre to post state in a [`LazyVec`] + Update { + /// index at which the value is updated + index: Index, + /// value before the update + pre: T, + /// value after the update + post: T, + }, } #[allow(missing_docs)] @@ -83,6 +95,15 @@ pub enum ValidationError { UnexpectedUnderflow(Index, Index), } +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum UpdateError { + #[error( + "Invalid index into a LazyVec. Got {index}, but the length is {len}" + )] + InvalidIndex { index: Index, len: u64 }, +} + /// [`LazyVec`] validation result pub type ValidationResult = std::result::Result; @@ -184,6 +205,23 @@ where } } + /// Update an element at the given index. + /// + /// The index must be smaller than the length of the vector, otherwise this + /// will fail with `UpdateError::InvalidIndex`. + pub fn update(&self, storage: &mut S, index: Index, val: T) -> Result<()> + where + S: StorageWrite + for<'iter> StorageRead<'iter>, + { + let len = self.len(storage)?; + if index >= len { + return Err(UpdateError::InvalidIndex { index, len }) + .into_storage_result(); + } + let data_key = self.get_data_key(index); + storage.write(&data_key, val) + } + /// Read an element at the index or `Ok(None)` if out of bounds. pub fn get(&self, storage: &S, index: Index) -> Result> where @@ -231,24 +269,27 @@ where None } - /// Accumulate storage changes inside a [`ValidationBuilder`] - pub fn validate( + /// Accumulate storage changes inside a [`ValidationBuilder`]. This is + /// typically done by the validity predicate while looping through the + /// changed keys. If the resulting `builder` is not `None`, one must + /// call `fn build()` on it to get the validation result. + pub fn accumulate( &self, - builder: &mut Option>, env: &ENV, - key_changed: storage::Key, + builder: &mut Option>, + key_changed: &storage::Key, ) -> std::result::Result<(), ENV::Error> where ENV: VpEnv, { - if let Some(sub) = self.is_sub_key(&key_changed) { + if let Some(sub) = self.is_sub_key(key_changed) { let change = match sub { SubKey::Len => { - let data = validation::read_data(env, &key_changed)?; + let data = validation::read_data(env, key_changed)?; data.map(SubKeyWithData::Len) } SubKey::Data(index) => { - let data = validation::read_data(env, &key_changed)?; + let data = validation::read_data(env, key_changed)?; data.map(|data| SubKeyWithData::Data(index, data)) } }; @@ -277,7 +318,7 @@ impl ValidationBuilder { pub fn build(self) -> ValidationResult>> { let mut actions = vec![]; - // We need to accumlate some values for what's changed + // We need to accumulate some values for what's changed let mut post_gt_pre = false; let mut len_diff: u64 = 0; let mut len_pre: u64 = 0; @@ -322,8 +363,7 @@ impl ValidationBuilder { added.insert(index); } Data::Update { pre, post } => { - actions.push(Action::Pop(pre)); - actions.push(Action::Push(post)); + actions.push(Action::Update { index, pre, post }); updated.insert(index); } Data::Delete { pre } => { From 255b43be1e337e6838855187864bce59a611c313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 12 Sep 2022 18:18:26 +0200 Subject: [PATCH 057/197] storage/lazy_vec/validation: disallow unrecognized keys matching prefix --- .../storage_api/collections/lazy_vec.rs | 84 ++++++++++++++----- shared/src/types/storage.rs | 19 +++++ 2 files changed, 80 insertions(+), 23 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index 6dd57ca556..c13d40855d 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -13,7 +13,7 @@ use super::LazyCollection; use crate::ledger::storage_api::validation::{self, Data}; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::ledger::vp_env::VpEnv; -use crate::types::storage; +use crate::types::storage::{self, DbKeySeg}; /// Subkey pointing to the length of the LazyVec pub const LEN_SUBKEY: &str = "len"; @@ -76,6 +76,8 @@ pub enum Action { #[allow(missing_docs)] #[derive(Error, Debug)] pub enum ValidationError { + #[error("Storage error in reading key {0}")] + StorageError(storage::Key), #[error("Incorrect difference in LazyVec's length")] InvalidLenDiff, #[error("An empty LazyVec must be deleted from storage")] @@ -93,6 +95,8 @@ pub enum ValidationError { IndexOverflow(>::Error), #[error("Unexpected underflow in `{0} - {0}`")] UnexpectedUnderflow(Index, Index), + #[error("Invalid storage key {0}")] + InvalidSubKey(storage::Key), } #[allow(missing_docs)] @@ -155,6 +159,7 @@ impl LazyVec { /// Get the sub-key of vector's elements storage fn get_data_key(&self, index: Index) -> storage::Key { + // TODO rebase on ordered prefix iter (https://github.com/anoma/namada/pull/458) and remove `.to_string()` self.get_data_prefix().push(&index.to_string()).unwrap() } @@ -248,58 +253,91 @@ where })) } - /// Check if the given storage key is a LazyVec sub-key and if so return - /// which one - pub fn is_sub_key(&self, key: &storage::Key) -> Option { - if let Some((prefix, storage::DbKeySeg::StringSeg(last))) = - key.split_last() - { - if let Ok(index) = Index::from_str(last) { - if let Some((prefix, storage::DbKeySeg::StringSeg(snd_last))) = - prefix.split_last() - { - if snd_last == DATA_SUBKEY && prefix.eq_owned(&self.key) { - return Some(SubKey::Data(index)); - } + /// Check if the given storage key is a valid LazyVec sub-key and if so + /// return which one + pub fn is_valid_sub_key( + &self, + key: &storage::Key, + ) -> ValidationResult> { + let suffix = match key.split_prefix(&self.key) { + None => { + // not matching prefix, irrelevant + return Ok(None); + } + Some(None) => { + // no suffix, invalid + return Err(ValidationError::InvalidSubKey(key.clone())); + } + Some(Some(suffix)) => suffix, + }; + + // Match the suffix against expected sub-keys + match &suffix.segments[..] { + [DbKeySeg::StringSeg(sub)] if sub == LEN_SUBKEY => { + Ok(Some(SubKey::Len)) + } + [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + // TODO rebase on ordered prefix iter (https://github.com/anoma/namada/pull/458) and parse with `KeySeg::parse` + if let Ok(index) = Index::from_str(sub_b) { + Ok(Some(SubKey::Data(index))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) } - } else if last == LEN_SUBKEY && prefix.eq_owned(&self.key) { - return Some(SubKey::Len); } + _ => Err(ValidationError::InvalidSubKey(key.clone())), } - None } /// Accumulate storage changes inside a [`ValidationBuilder`]. This is /// typically done by the validity predicate while looping through the /// changed keys. If the resulting `builder` is not `None`, one must /// call `fn build()` on it to get the validation result. + /// This function will return `Ok(true)` if the storage key is a valid + /// sub-key of this collection, `Ok(false)` if the storage key doesn't match + /// the prefix of this collection, or fail with + /// [`ValidationError::InvalidSubKey`] if the prefix matches this + /// collection, but the key itself is not recognized. pub fn accumulate( &self, env: &ENV, builder: &mut Option>, key_changed: &storage::Key, - ) -> std::result::Result<(), ENV::Error> + ) -> ValidationResult where ENV: VpEnv, { - if let Some(sub) = self.is_sub_key(key_changed) { + if let Some(sub) = self.is_valid_sub_key(key_changed)? { let change = match sub { SubKey::Len => { - let data = validation::read_data(env, key_changed)?; + let data = validation::read_data(env, key_changed) + // TODO this has to propagate errors from VpEnv rather + // then replace them (e.g. it could be out-of-gas), but + // VpEnv::Error is generic, maybe we should make it + // concrete (only the VpEnv impls have extensible error + // types) + .map_err(|_| { + ValidationError::StorageError(key_changed.clone()) + })?; data.map(SubKeyWithData::Len) } SubKey::Data(index) => { - let data = validation::read_data(env, key_changed)?; + let data = validation::read_data(env, key_changed) + .map_err(|_| { + ValidationError::StorageError(key_changed.clone()) + })?; data.map(|data| SubKeyWithData::Data(index, data)) } }; if let Some(change) = change { let builder = builder.get_or_insert(ValidationBuilder::default()); - builder.changes.push(change) + builder.changes.push(change); + return Ok(true); } } - Ok(()) + Ok(false) } } diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 49abe7197d..b5d72b7b02 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -372,6 +372,25 @@ impl Key { }), } } + + /// Check if the key begins with the given prefix and returns: + /// - `Some(Some(suffix))` the suffix after the match with, if any, or + /// - `Some(None)` if the prefix is matched, but it has no suffix, or + /// - `None` if it doesn't match + pub fn split_prefix(&self, prefix: &Self) -> Option> { + if self.segments.len() < prefix.len() { + return None; + } else if self == prefix { + return Some(None); + } + let mut self_prefix = self.segments.clone(); + let rest = self_prefix.split_off(prefix.len()); + if self_prefix == prefix.segments { + Some(Some(Key { segments: rest })) + } else { + None + } + } } impl Display for Key { From f854d8285c7b5e80aa8131d9a14cbbb99b5fa0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 13 Sep 2022 18:56:32 +0200 Subject: [PATCH 058/197] storage/lazy_vec: add state machine test for lazy vec API --- .../storage_api/collections/lazy_hashmap.rs | 4 +- .../storage_api/collections/lazy_hashset.rs | 4 +- .../storage_api/collections/lazy_map.rs | 6 +- .../storage_api/collections/lazy_set.rs | 4 +- .../storage_api/collections/lazy_vec.rs | 257 +++++++++++++++++- .../src/ledger/storage_api/collections/mod.rs | 2 +- 6 files changed, 264 insertions(+), 13 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs index 46bc55fa6e..9a60fd18f0 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs @@ -45,7 +45,7 @@ struct KeyVal { impl LazyCollection for LazyHashMap { /// Create or use an existing map with the given storage `key`. - fn new(key: storage::Key) -> Self { + fn open(key: storage::Key) -> Self { Self { key, phantom_k: PhantomData, @@ -225,7 +225,7 @@ mod test { let mut storage = TestStorage::default(); let key = storage::Key::parse("test").unwrap(); - let lazy_map = LazyHashMap::::new(key); + let lazy_map = LazyHashMap::::open(key); // The map should be empty at first assert!(lazy_map.is_empty(&storage)?); diff --git a/shared/src/ledger/storage_api/collections/lazy_hashset.rs b/shared/src/ledger/storage_api/collections/lazy_hashset.rs index 06a6ef2295..63ac5c845c 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashset.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashset.rs @@ -37,7 +37,7 @@ pub struct LazyHashSet { impl LazyCollection for LazyHashSet { /// Create or use an existing set with the given storage `key`. - fn new(key: storage::Key) -> Self { + fn open(key: storage::Key) -> Self { Self { key, phantom: PhantomData, @@ -149,7 +149,7 @@ mod test { let mut storage = TestStorage::default(); let key = storage::Key::parse("test").unwrap(); - let lazy_set = LazyHashSet::::new(key); + let lazy_set = LazyHashSet::::open(key); // The set should be empty at first assert!(lazy_set.is_empty(&storage)?); diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 103508404e..a47ff0734a 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -75,7 +75,7 @@ where K: storage::KeySeg, { /// Create or use an existing map with the given storage `key`. - fn new(key: storage::Key) -> Self { + fn open(key: storage::Key) -> Self { Self { key, phantom_k: PhantomData, @@ -119,7 +119,7 @@ where /// collection at the given key, a new empty one will be provided. The /// nested collection may be manipulated through its methods. pub fn at(&self, key: &K) -> V { - V::new(self.get_data_key(key)) + V::open(self.get_data_key(key)) } } @@ -259,7 +259,7 @@ mod test { let mut storage = TestStorage::default(); let key = storage::Key::parse("test").unwrap(); - let lazy_map = LazyMap::::new(key); + let lazy_map = LazyMap::::open(key); // The map should be empty at first assert!(lazy_map.is_empty(&storage)?); diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index fc301b09f1..9635724543 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -31,7 +31,7 @@ pub struct LazySet { impl LazyCollection for LazySet { /// Create or use an existing set with the given storage `key`. - fn new(key: storage::Key) -> Self { + fn open(key: storage::Key) -> Self { Self { key, phantom: PhantomData, @@ -150,7 +150,7 @@ mod test { let mut storage = TestStorage::default(); let key = storage::Key::parse("test").unwrap(); - let lazy_set = LazySet::::new(key); + let lazy_set = LazySet::::open(key); // The set should be empty at first assert!(lazy_set.is_empty(&storage)?); diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index c13d40855d..f3a8c2bac8 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -29,7 +29,7 @@ pub type Index = u64; /// vector, the elements do not reside in memory but are instead read and /// written to storage sub-keys of the storage `key` used to construct the /// vector. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct LazyVec { key: storage::Key, phantom: PhantomData, @@ -124,7 +124,7 @@ pub struct ValidationBuilder { impl LazyCollection for LazyVec { /// Create or use an existing vector with the given storage `key`. - fn new(key: storage::Key) -> Self { + fn open(key: storage::Key) -> Self { Self { key, phantom: PhantomData, @@ -523,6 +523,12 @@ impl ValidationBuilder { #[cfg(test)] mod test { + use proptest::prelude::*; + use proptest::prop_state_machine; + use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; + use proptest::test_runner::Config; + use test_log::test; + use super::*; use crate::ledger::storage::testing::TestStorage; @@ -531,7 +537,7 @@ mod test { let mut storage = TestStorage::default(); let key = storage::Key::parse("test").unwrap(); - let lazy_vec = LazyVec::::new(key); + let lazy_vec = LazyVec::::open(key); // The vec should be empty at first assert!(lazy_vec.is_empty(&storage)?); @@ -561,4 +567,249 @@ mod test { Ok(()) } + + prop_state_machine! { + #![proptest_config(Config { + // Instead of the default 256, we only run 5 because otherwise it + // takes too long and it's preferable to crank up the number of + // transitions instead, to allow each case to run for more epochs as + // some issues only manifest once the model progresses further. + // Additionally, more cases will be explored every time this test is + // executed in the CI. + cases: 5, + .. Config::default() + })] + #[test] + /// A `StateMachineTest` implemented on `LazyVec` that manipulates + /// it with `Transition`s and checks its state against an in-memory + /// `std::collections::Vec`. + fn lazy_vec_api_state_machine_test(sequential 1..100 => ConcreteLazyVecState); + + } + + /// Some borsh-serializable type with arbitrary fields to be used inside + /// LazyVec state machine test + #[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + )] + struct TestVecItem { + x: u64, + y: bool, + } + + #[derive(Debug)] + struct ConcreteLazyVecState { + // The eager vec in `AbstractLazyVecState` is not visible in `impl + // StateMachineTest for ConcreteLazyVecState`, it's only used to drive + // transition generation, so we duplicate it here and apply the + // transitions on it the same way (with + // `fn apply_transition_on_eager_vec`) + eager_vec: Vec, + lazy_vec: LazyVec, + storage: TestStorage, + } + + #[derive(Clone, Debug)] + struct AbstractLazyVecState(Vec); + + /// Possible transitions that can modify a [`LazyVec`]. This roughly + /// corresponds to the methods that have `StorageWrite` access and is very + /// similar to [`Action`] + #[derive(Clone, Debug)] + pub enum Transition { + /// Push a value `T` into a [`LazyVec`] + Push(T), + /// Pop a value from a [`LazyVec`] + Pop, + /// Update a value `T` at index from pre to post state in a + /// [`LazyVec`] + Update { + /// index at which the value is updated + index: Index, + /// value to update the element to + value: T, + }, + } + + impl AbstractStateMachine for AbstractLazyVecState { + type State = Self; + type Transition = Transition; + + fn init_state() -> BoxedStrategy { + Just(Self(vec![])).boxed() + } + + fn transitions(state: &Self::State) -> BoxedStrategy { + if state.0.is_empty() { + prop_oneof![arb_test_vec_item().prop_map(Transition::Push)] + .boxed() + } else { + let indices: Vec = + (0_usize..state.0.len()).map(|ix| ix as Index).collect(); + let arb_index = proptest::sample::select(indices); + prop_oneof![ + Just(Transition::Pop), + arb_test_vec_item().prop_map(Transition::Push), + (arb_index, arb_test_vec_item()).prop_map( + |(index, value)| Transition::Update { index, value } + ) + ] + .boxed() + } + } + + fn apply_abstract( + mut state: Self::State, + transition: &Self::Transition, + ) -> Self::State { + apply_transition_on_eager_vec(&mut state.0, transition); + state + } + + fn preconditions( + state: &Self::State, + transition: &Self::Transition, + ) -> bool { + if state.0.is_empty() { + !matches!( + transition, + Transition::Pop | Transition::Update { .. } + ) + } else if let Transition::Update { index, .. } = transition { + *index < (state.0.len() - 1) as Index + } else { + true + } + } + } + + impl StateMachineTest for ConcreteLazyVecState { + type Abstract = AbstractLazyVecState; + type ConcreteState = Self; + + fn init_test( + _initial_state: ::State, + ) -> Self::ConcreteState { + Self { + eager_vec: vec![], + lazy_vec: LazyVec::open( + storage::Key::parse("key_path/arbitrary").unwrap(), + ), + storage: TestStorage::default(), + } + } + + fn apply_concrete( + mut state: Self::ConcreteState, + transition: ::Transition, + ) -> Self::ConcreteState { + // Transition application on lazy vec and post-conditions: + match dbg!(&transition) { + Transition::Push(value) => { + let old_len = state.lazy_vec.len(&state.storage).unwrap(); + + state + .lazy_vec + .push(&mut state.storage, value.clone()) + .unwrap(); + + // Post-conditions: + let new_len = state.lazy_vec.len(&state.storage).unwrap(); + let stored_value = state + .lazy_vec + .get(&state.storage, new_len - 1) + .unwrap() + .unwrap(); + assert_eq!( + &stored_value, value, + "the new item must be added to the back" + ); + assert_eq!(old_len + 1, new_len, "length must increment"); + } + Transition::Pop => { + let old_len = state.lazy_vec.len(&state.storage).unwrap(); + + let popped = state + .lazy_vec + .pop(&mut state.storage) + .unwrap() + .unwrap(); + + // Post-conditions: + let new_len = state.lazy_vec.len(&state.storage).unwrap(); + assert_eq!(old_len, new_len + 1, "length must decrement"); + assert_eq!( + &popped, + state.eager_vec.last().unwrap(), + "popped element matches the last element in eager vec \ + before it's updated" + ); + } + Transition::Update { index, value } => { + state + .lazy_vec + .update(&mut state.storage, *index, value.clone()) + .unwrap(); + } + } + + // Apply transition in the eager vec for comparison + apply_transition_on_eager_vec(&mut state.eager_vec, &transition); + + // Global post-conditions: + + // All items in eager vec must be present in lazy vec + for (ix, expected_item) in state.eager_vec.iter().enumerate() { + let got = state + .lazy_vec + .get(&state.storage, ix as Index) + .unwrap() + .expect("The expected item must be present in lazy vec"); + assert_eq!(expected_item, &got, "at index {ix}"); + } + + // All items in lazy vec must be present in eager vec + for (ix, expected_item) in + state.lazy_vec.iter(&state.storage).unwrap().enumerate() + { + let expected_item = expected_item.unwrap(); + let got = state + .eager_vec + .get(ix) + .expect("The expected item must be present in eager vec"); + assert_eq!(&expected_item, got, "at index {ix}"); + } + + state + } + } + + /// Generate an arbitrary `TestVecItem` + fn arb_test_vec_item() -> impl Strategy { + (any::(), any::()).prop_map(|(x, y)| TestVecItem { x, y }) + } + + /// Apply `Transition` on an eager `Vec`. + fn apply_transition_on_eager_vec( + vec: &mut Vec, + transition: &Transition, + ) { + match transition { + Transition::Push(value) => vec.push(value.clone()), + Transition::Pop => { + let _popped = vec.pop(); + } + Transition::Update { index, value } => { + let entry = vec.get_mut(*index as usize).unwrap(); + *entry = value.clone(); + } + } + } } diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index 01bcae439e..b3e1b4af0c 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -41,5 +41,5 @@ pub enum ReadError { /// An empty collection must be deleted from storage. pub trait LazyCollection { /// Create or use an existing vector with the given storage `key`. - fn new(key: storage::Key) -> Self; + fn open(key: storage::Key) -> Self; } From 8c4e2ca33466a0a54ae751ecf693ca042eb29345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 14 Sep 2022 10:46:45 +0200 Subject: [PATCH 059/197] update after rebase on #458, #465 handle TODOs to fix LazyVec's iter order to fix the API SM test --- .../storage_api/collections/lazy_vec.rs | 35 +++++++------------ .../src/ledger/storage_api/validation/mod.rs | 5 +-- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index f3a8c2bac8..0c91b0dbbc 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -2,7 +2,6 @@ use std::collections::BTreeSet; use std::marker::PhantomData; -use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; @@ -159,8 +158,7 @@ impl LazyVec { /// Get the sub-key of vector's elements storage fn get_data_key(&self, index: Index) -> storage::Key { - // TODO rebase on ordered prefix iter (https://github.com/anoma/namada/pull/458) and remove `.to_string()` - self.get_data_prefix().push(&index.to_string()).unwrap() + self.get_data_prefix().push(&index).unwrap() } /// Get the sub-key of vector's length storage @@ -258,7 +256,7 @@ where pub fn is_valid_sub_key( &self, key: &storage::Key, - ) -> ValidationResult> { + ) -> storage_api::Result> { let suffix = match key.split_prefix(&self.key) { None => { // not matching prefix, irrelevant @@ -266,7 +264,8 @@ where } Some(None) => { // no suffix, invalid - return Err(ValidationError::InvalidSubKey(key.clone())); + return Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(); } Some(Some(suffix)) => suffix, }; @@ -279,14 +278,15 @@ where [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] if sub_a == DATA_SUBKEY => { - // TODO rebase on ordered prefix iter (https://github.com/anoma/namada/pull/458) and parse with `KeySeg::parse` - if let Ok(index) = Index::from_str(sub_b) { + if let Ok(index) = storage::KeySeg::parse(sub_b.clone()) { Ok(Some(SubKey::Data(index))) } else { Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() } } - _ => Err(ValidationError::InvalidSubKey(key.clone())), + _ => Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(), } } @@ -304,29 +304,18 @@ where env: &ENV, builder: &mut Option>, key_changed: &storage::Key, - ) -> ValidationResult + ) -> storage_api::Result where - ENV: VpEnv, + ENV: for<'a> VpEnv<'a>, { if let Some(sub) = self.is_valid_sub_key(key_changed)? { let change = match sub { SubKey::Len => { - let data = validation::read_data(env, key_changed) - // TODO this has to propagate errors from VpEnv rather - // then replace them (e.g. it could be out-of-gas), but - // VpEnv::Error is generic, maybe we should make it - // concrete (only the VpEnv impls have extensible error - // types) - .map_err(|_| { - ValidationError::StorageError(key_changed.clone()) - })?; + let data = validation::read_data(env, key_changed)?; data.map(SubKeyWithData::Len) } SubKey::Data(index) => { - let data = validation::read_data(env, key_changed) - .map_err(|_| { - ValidationError::StorageError(key_changed.clone()) - })?; + let data = validation::read_data(env, key_changed)?; data.map(|data| SubKeyWithData::Data(index, data)) } }; diff --git a/shared/src/ledger/storage_api/validation/mod.rs b/shared/src/ledger/storage_api/validation/mod.rs index 30c1e2566b..ca0e779a75 100644 --- a/shared/src/ledger/storage_api/validation/mod.rs +++ b/shared/src/ledger/storage_api/validation/mod.rs @@ -4,6 +4,7 @@ use std::fmt::Debug; use borsh::BorshDeserialize; +use crate::ledger::storage_api; use crate::ledger::vp_env::VpEnv; use crate::types::storage; @@ -33,10 +34,10 @@ pub enum Data { pub fn read_data( env: &ENV, key: &storage::Key, -) -> Result>, ENV::Error> +) -> Result>, storage_api::Error> where T: BorshDeserialize, - ENV: VpEnv, + ENV: for<'a> VpEnv<'a>, { let pre = env.read_pre(key)?; let post = env.read_post(key)?; From 35b5f4013719d224895235e105d0e336c64a05fd Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 14 Sep 2022 14:38:29 +0200 Subject: [PATCH 060/197] quick bug and documentation fix --- shared/src/ledger/storage_api/collections/lazy_vec.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index 0c91b0dbbc..c086297ea7 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -336,7 +336,7 @@ impl ValidationBuilder { /// /// The validation rules for a [`LazyVec`] are: /// - A difference in the vector's length must correspond to the - /// difference in how many elements where pushed versus how many + /// difference in how many elements were pushed versus how many /// elements were popped. /// - An empty vector must be deleted from storage /// - In addition, we check that indices of any changes are within an @@ -400,7 +400,7 @@ impl ValidationBuilder { }, } } - let added_len: u64 = deleted + let added_len: u64 = added .len() .try_into() .map_err(ValidationError::IndexOverflow)?; From 5396df98e460a30471c6785661b6d3b333f97cb1 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 14 Sep 2022 16:47:22 +0200 Subject: [PATCH 061/197] post-conditions for Transition::Update + some comments --- .../storage_api/collections/lazy_vec.rs | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index c086297ea7..79967cb9c3 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -336,8 +336,8 @@ impl ValidationBuilder { /// /// The validation rules for a [`LazyVec`] are: /// - A difference in the vector's length must correspond to the - /// difference in how many elements were pushed versus how many - /// elements were popped. + /// difference in how many elements were pushed versus how many elements + /// were popped. /// - An empty vector must be deleted from storage /// - In addition, we check that indices of any changes are within an /// expected range (i.e. the vectors indices should always be @@ -635,6 +635,7 @@ mod test { Just(Self(vec![])).boxed() } + // Apply a random transition to the state fn transitions(state: &Self::State) -> BoxedStrategy { if state.0.is_empty() { prop_oneof![arb_test_vec_item().prop_map(Transition::Push)] @@ -667,11 +668,14 @@ mod test { transition: &Self::Transition, ) -> bool { if state.0.is_empty() { + // Ensure that the pop or update transitions are not applied to + // an empty state !matches!( transition, Transition::Pop | Transition::Update { .. } ) } else if let Transition::Update { index, .. } = transition { + // Ensure that the update index is a valid one *index < (state.0.len() - 1) as Index } else { true @@ -742,10 +746,37 @@ mod test { ); } Transition::Update { index, value } => { + let old_len = state.lazy_vec.len(&state.storage).unwrap(); + let old_val = state + .lazy_vec + .get(&state.storage, *index) + .unwrap() + .unwrap(); + state .lazy_vec .update(&mut state.storage, *index, value.clone()) .unwrap(); + + // Post-conditions: + let new_len = state.lazy_vec.len(&state.storage).unwrap(); + let new_val = state + .lazy_vec + .get(&state.storage, *index) + .unwrap() + .unwrap(); + assert_eq!(old_len, new_len, "length must not change"); + assert_eq!( + &old_val, + state.eager_vec.get(*index as usize).unwrap(), + "old value must match the value at the same index in \ + the eager vec before it's updated" + ); + assert_eq!( + &new_val, value, + "new value must match that which was passed into the \ + Transition::Update" + ); } } From ad69a3102c38d4e24dec21818ba6ac98a803fae8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 09:59:00 +0100 Subject: [PATCH 062/197] 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 073a7081c3d89a062c48fc15e50e186a859f3fe6 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sat, 20 Aug 2022 17:33:47 +0200 Subject: [PATCH 063/197] multitoken transfer and query --- apps/src/lib/cli.rs | 15 +++ apps/src/lib/client/rpc.rs | 128 ++++++++++++++++++---- apps/src/lib/client/tx.rs | 15 ++- shared/src/types/intent.rs | 4 + shared/src/types/token.rs | 68 ++++++++++++ tests/src/e2e/ledger_tests.rs | 1 + vm_env/src/governance.rs | 1 + vm_env/src/ibc.rs | 2 +- vm_env/src/proof_of_stake.rs | 2 +- vm_env/src/token.rs | 108 ++++++++++++++---- wasm/wasm_source/src/tx_from_intent.rs | 3 +- wasm/wasm_source/src/tx_transfer.rs | 3 +- wasm/wasm_source/src/vp_testnet_faucet.rs | 8 +- wasm/wasm_source/src/vp_user.rs | 20 +++- wasm_for_tests/wasm_source/src/lib.rs | 1 + 15 files changed, 323 insertions(+), 56 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index f546860cfd..6eb6454bc4 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1465,6 +1465,7 @@ pub mod args { const SOURCE: Arg = arg("source"); const SOURCE_OPT: ArgOpt = SOURCE.opt(); const STORAGE_KEY: Arg = arg("storage-key"); + const SUB_PREFIX: ArgOpt = arg_opt("sub-prefix"); const TARGET: Arg = arg("target"); const TO_STDOUT: ArgFlag = flag("stdout"); const TOKEN_OPT: ArgOpt = TOKEN.opt(); @@ -1610,6 +1611,8 @@ pub mod args { pub target: WalletAddress, /// Transferred token address pub token: WalletAddress, + /// Transferred token address + pub sub_prefix: Option, /// Transferred token amount pub amount: token::Amount, } @@ -1620,12 +1623,14 @@ pub mod args { let source = SOURCE.parse(matches); let target = TARGET.parse(matches); let token = TOKEN.parse(matches); + let sub_prefix = SUB_PREFIX.parse(matches); let amount = AMOUNT.parse(matches); Self { tx, source, target, token, + sub_prefix, amount, } } @@ -1638,6 +1643,7 @@ pub mod args { )) .arg(TARGET.def().about("The target account address.")) .arg(TOKEN.def().about("The transfer token.")) + .arg(SUB_PREFIX.def().about("The token's sub prefix.")) .arg(AMOUNT.def().about("The amount to transfer in decimal.")) } } @@ -2185,6 +2191,8 @@ pub mod args { pub owner: Option, /// Address of a token pub token: Option, + /// Sub prefix of an account + pub sub_prefix: Option, } impl Args for QueryBalance { @@ -2192,10 +2200,12 @@ pub mod args { let query = Query::parse(matches); let owner = OWNER.parse(matches); let token = TOKEN_OPT.parse(matches); + let sub_prefix = SUB_PREFIX.parse(matches); Self { query, owner, token, + sub_prefix, } } @@ -2211,6 +2221,11 @@ pub mod args { .def() .about("The token's address whose balance to query."), ) + .arg( + SUB_PREFIX.def().about( + "The token's sub prefix whose balance to query.", + ), + ) } } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 34652ac825..7d7a859138 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -27,7 +27,7 @@ use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalVote, TallyResult, }; use namada::types::key::*; -use namada::types::storage::{Epoch, PrefixValue}; +use namada::types::storage::{Epoch, Key, KeySeg, PrefixValue}; use namada::types::token::{balance_key, Amount}; use namada::types::{address, storage, token}; use tendermint::abci::Code; @@ -101,15 +101,29 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { (Some(token), Some(owner)) => { let token = ctx.get(&token); let owner = ctx.get(&owner); - let key = token::balance_key(&token, &owner); + let key = match &args.sub_prefix { + Some(sub_prefix) => { + let sub_prefix = Key::parse(sub_prefix).unwrap(); + let prefix = + token::multitoken_balance_prefix(&token, &sub_prefix); + token::multitoken_balance_key(&prefix, &owner) + } + None => token::balance_key(&token, &owner), + }; let currency_code = tokens .get(&token) .map(|c| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); match query_storage_value::(&client, &key).await { - Some(balance) => { - println!("{}: {}", currency_code, balance); - } + Some(balance) => match &args.sub_prefix { + Some(sub_prefix) => { + println!( + "{} with {}: {}", + currency_code, sub_prefix, balance + ); + } + None => println!("{}: {}", currency_code, balance), + }, None => { println!("No {} balance found for {}", currency_code, owner) } @@ -119,12 +133,44 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { let owner = ctx.get(&owner); let mut found_any = false; for (token, currency_code) in tokens { - let key = token::balance_key(&token, &owner); - if let Some(balance) = - query_storage_value::(&client, &key).await - { - println!("{}: {}", currency_code, balance); - found_any = true; + let prefix = token.to_db_key().into(); + let balances = query_storage_prefix::( + client.clone(), + prefix, + ) + .await; + if let Some(balances) = balances { + let stdout = io::stdout(); + let mut w = stdout.lock(); + for (key, balance) in balances { + match token::is_any_multitoken_balance_key(&key) { + Some((sub_prefix, o)) if *o == owner => { + writeln!( + w, + "{} with {}: {}", + currency_code, sub_prefix, balance + ) + .unwrap(); + found_any = true; + } + Some(_) => {} + None => { + if let Some(o) = + token::is_any_token_balance_key(&key) + { + if *o == owner { + writeln!( + w, + "{}: {}", + currency_code, balance + ) + .unwrap(); + found_any = true; + } + } + } + } + } } } if !found_any { @@ -133,9 +179,9 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { } (Some(token), None) => { let token = ctx.get(&token); - let key = token::balance_prefix(&token); + let prefix = token.to_db_key().into(); let balances = - query_storage_prefix::(client, key).await; + query_storage_prefix::(client, prefix).await; match balances { Some(balances) => { let currency_code = tokens @@ -144,12 +190,30 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { .unwrap_or_else(|| Cow::Owned(token.to_string())); let stdout = io::stdout(); let mut w = stdout.lock(); - writeln!(w, "Token {}:", currency_code).unwrap(); + writeln!(w, "Token {}", currency_code).unwrap(); for (key, balance) in balances { - let owner = - token::is_any_token_balance_key(&key).unwrap(); - writeln!(w, " {}, owned by {}", balance, owner) - .unwrap(); + match token::is_any_multitoken_balance_key(&key) { + Some((sub_prefix, owner)) => { + writeln!( + w, + " with {}: {}, owned by {}", + sub_prefix, balance, owner + ) + .unwrap(); + } + None => { + if let Some(owner) = + token::is_any_token_balance_key(&key) + { + writeln!( + w, + ": {}, owned by {}", + balance, owner + ) + .unwrap(); + } + } + } } } None => { @@ -167,12 +231,30 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { .await; match balances { Some(balances) => { - writeln!(w, "Token {}:", currency_code).unwrap(); + writeln!(w, "Token {}", currency_code).unwrap(); for (key, balance) in balances { - let owner = - token::is_any_token_balance_key(&key).unwrap(); - writeln!(w, " {}, owned by {}", balance, owner) - .unwrap(); + match token::is_any_multitoken_balance_key(&key) { + Some((sub_prefix, owner)) => { + writeln!( + w, + " with {}: {}, owned by {}", + sub_prefix, balance, owner + ) + .unwrap(); + } + None => { + if let Some(owner) = + token::is_any_token_balance_key(&key) + { + writeln!( + w, + ": {}, owned by {}", + balance, owner + ) + .unwrap() + } + } + } } } None => { diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 1d41ebbc77..a1b089fab9 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -16,7 +16,7 @@ use namada::types::governance::{ }; use namada::types::key::*; use namada::types::nft::{self, Nft, NftToken}; -use namada::types::storage::Epoch; +use namada::types::storage::{Epoch, Key}; use namada::types::token::Amount; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, @@ -415,7 +415,17 @@ pub async fn submit_transfer(ctx: Context, args: args::TxTransfer) { } } // Check source balance - let balance_key = token::balance_key(&token, &source); + let (sub_prefix, balance_key) = match args.sub_prefix { + Some(sub_prefix) => { + let sub_prefix = Key::parse(sub_prefix).unwrap(); + let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); + ( + Some(sub_prefix), + token::multitoken_balance_key(&prefix, &source), + ) + } + None => (None, token::balance_key(&token, &source)), + }; let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); match rpc::query_storage_value::(&client, &balance_key).await { @@ -447,6 +457,7 @@ pub async fn submit_transfer(ctx: Context, args: args::TxTransfer) { source, target, token, + sub_prefix, amount: args.amount, }; tracing::debug!("Transfer data {:?}", transfer); diff --git a/shared/src/types/intent.rs b/shared/src/types/intent.rs index c3effbb4fc..3acb43f0c0 100644 --- a/shared/src/types/intent.rs +++ b/shared/src/types/intent.rs @@ -316,12 +316,14 @@ mod tests { source: bertha_addr.clone(), target: albert_addr.clone(), token: Address::from_str(BTC).unwrap(), + sub_prefix: None, amount: token::Amount::from(100), }, token::Transfer { source: albert_addr, target: bertha_addr, token: Address::from_str(XAN).unwrap(), + sub_prefix: None, amount: token::Amount::from(1), }, ] @@ -424,12 +426,14 @@ mod tests { source: bertha_addr.clone(), target: albert_addr.clone(), token: Address::from_str(BTC).unwrap(), + sub_prefix: None, amount: token::Amount::from(100), }, token::Transfer { source: albert_addr, target: bertha_addr, token: Address::from_str(XAN).unwrap(), + sub_prefix: None, amount: token::Amount::from(1), }, ] diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index f3e4cd4ed2..4d03138e60 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -250,6 +250,23 @@ pub fn balance_prefix(token_addr: &Address) -> Key { .expect("Cannot obtain a storage key") } +/// Obtain a storage key prefix for multitoken balances. +pub fn multitoken_balance_prefix( + token_addr: &Address, + sub_prefix: &Key, +) -> Key { + Key::from(token_addr.to_db_key()).join(sub_prefix) +} + +/// Obtain a storage key for user's multitoken balance. +pub fn multitoken_balance_key(prefix: &Key, owner: &Address) -> Key { + prefix + .push(&BALANCE_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") + .push(&owner.to_db_key()) + .expect("Cannot obtain a storage key") +} + /// Check if the given storage key is balance key for the given token. If it is, /// returns the owner. pub fn is_balance_key<'a>( @@ -297,6 +314,54 @@ pub fn is_non_owner_balance_key(key: &Key) -> Option<&Address> { } } +/// Check if the given storage key is multitoken balance key for the given +/// token. If it is, returns the sub prefix and the owner. +pub fn is_multitoken_balance_key<'a>( + token_addr: &Address, + key: &'a Key, +) -> Option<(Key, &'a Address)> { + match key.segments.first() { + Some(DbKeySeg::AddressSeg(addr)) if addr == token_addr => { + multitoken_balance_owner(key) + } + _ => None, + } +} + +/// Check if the given storage key is multitoken balance key for unspecified +/// token. If it is, returns the sub prefix and the owner. +pub fn is_any_multitoken_balance_key(key: &Key) -> Option<(Key, &Address)> { + match key.segments.first() { + Some(DbKeySeg::AddressSeg(_)) => multitoken_balance_owner(key), + _ => None, + } +} + +fn multitoken_balance_owner(key: &Key) -> Option<(Key, &Address)> { + let len = key.segments.len(); + if len < 4 { + // the key of a multitoken should have 1 or more segments other than + // token, balance, owner + return None; + } + match key.get_at(len - 2) { + Some(DbKeySeg::StringSeg(balance)) + if balance == BALANCE_STORAGE_KEY => + { + match key.segments.last() { + Some(DbKeySeg::AddressSeg(owner)) => { + let sub_prefix = Key { + segments: key.segments[1..(len - 2)].to_vec(), + }; + Some((sub_prefix, owner)) + } + _ => None, + } + } + _ => None, + } +} + /// A simple bilateral token transfer #[derive( Debug, @@ -318,6 +383,8 @@ pub struct Transfer { pub target: Address, /// Token's address pub token: Address, + /// Source token's sub prefix + pub sub_prefix: Option, /// The amount of tokens pub amount: Amount, } @@ -354,6 +421,7 @@ impl TryFrom for Transfer { source, target, token, + sub_prefix: None, amount, }) } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 22eb4f4ac5..1f19433ca1 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -425,6 +425,7 @@ fn invalid_transactions() -> Result<()> { source: find_address(&test, DAEWON)?, target: find_address(&test, ALBERT)?, token: find_address(&test, XAN)?, + sub_prefix: None, amount: token::Amount::whole(1), }; let data = transfer diff --git a/vm_env/src/governance.rs b/vm_env/src/governance.rs index db4ea7916f..cfe16e2acb 100644 --- a/vm_env/src/governance.rs +++ b/vm_env/src/governance.rs @@ -63,6 +63,7 @@ pub mod tx { &data.author, &governance_address, &m1t(), + None, min_proposal_funds, ); } diff --git a/vm_env/src/ibc.rs b/vm_env/src/ibc.rs index febaa78560..c7abb82ec5 100644 --- a/vm_env/src/ibc.rs +++ b/vm_env/src/ibc.rs @@ -37,7 +37,7 @@ impl IbcActions for Ibc { token: &Address, amount: Amount, ) { - transfer(src, dest, token, amount) + transfer(src, dest, token, None, amount) } fn get_height(&self) -> BlockHeight { diff --git a/vm_env/src/proof_of_stake.rs b/vm_env/src/proof_of_stake.rs index 8e4bba4223..afabd85ed7 100644 --- a/vm_env/src/proof_of_stake.rs +++ b/vm_env/src/proof_of_stake.rs @@ -256,6 +256,6 @@ impl namada_proof_of_stake::PosActions for PoS { src: &Self::Address, dest: &Self::Address, ) { - crate::token::tx::transfer(src, dest, token, amount) + crate::token::tx::transfer(src, dest, token, None, amount) } } diff --git a/vm_env/src/token.rs b/vm_env/src/token.rs index 8a7367afb9..784b838707 100644 --- a/vm_env/src/token.rs +++ b/vm_env/src/token.rs @@ -20,7 +20,12 @@ pub mod vp { ) -> bool { let mut change: Change = 0; let all_checked = keys_changed.iter().all(|key| { - match token::is_balance_key(token, key) { + let owner: Option<&Address> = + match token::is_multitoken_balance_key(token, key) { + Some((_, o)) => Some(o), + None => token::is_balance_key(token, key), + }; + match owner { None => { // Unknown changes to this address space are disallowed, but // unknown changes anywhere else are permitted @@ -73,38 +78,101 @@ pub mod tx { src: &Address, dest: &Address, token: &Address, + sub_prefix: Option, amount: Amount, ) { - let src_key = token::balance_key(token, src); - let dest_key = token::balance_key(token, dest); + let src_key = match &sub_prefix { + Some(sub_prefix) => { + let prefix = + token::multitoken_balance_prefix(token, sub_prefix); + token::multitoken_balance_key(&prefix, src) + } + None => token::balance_key(token, src), + }; + let dest_key = match &sub_prefix { + Some(sub_prefix) => { + let prefix = + token::multitoken_balance_prefix(token, sub_prefix); + token::multitoken_balance_key(&prefix, dest) + } + None => token::balance_key(token, dest), + }; let src_bal: Option = tx::read(&src_key.to_string()); - let mut src_bal = src_bal.unwrap_or_else(|| match src { - Address::Internal(InternalAddress::IbcMint) => Amount::max(), - _ => { + match src_bal { + None => { tx::log_string(format!("src {} has no balance", src)); unreachable!() } - }); - src_bal.spend(&amount); - let mut dest_bal: Amount = - tx::read(&dest_key.to_string()).unwrap_or_default(); - dest_bal.receive(&amount); - match src { - Address::Internal(InternalAddress::IbcMint) => { - tx::write_temp(&src_key.to_string(), src_bal) + Some(mut src_bal) => { + src_bal.spend(&amount); + let mut dest_bal: Amount = + tx::read(&dest_key.to_string()).unwrap_or_default(); + dest_bal.receive(&amount); + tx::write(&src_key.to_string(), src_bal); + tx::write(&dest_key.to_string(), dest_bal); } - Address::Internal(InternalAddress::IbcBurn) => { + } + } + + /// A token transfer with storage keys that can be used in a transaction. + pub fn transfer_with_keys(src_key: &Key, dest_key: &Key, amount: Amount) { + let src_owner = is_any_multitoken_balance_key(src_key).map(|(_, o)| o); + let src_bal: Option = match src_owner { + Some(Address::Internal(InternalAddress::IbcMint)) => { + Some(Amount::max()) + } + Some(Address::Internal(InternalAddress::IbcBurn)) => { tx::log_string("invalid transfer from the burn address"); unreachable!() } - _ => tx::write(&src_key.to_string(), src_bal), - } - match dest { - Address::Internal(InternalAddress::IbcMint) => { + Some(_) => tx::read(&src_key.to_string()), + None => { + // the key is not a multitoken key + match is_any_token_balance_key(src_key) { + Some(_) => tx::read(src_key.to_string()), + None => { + tx::log_string(format!( + "invalid balance key: {}", + src_key + )); + unreachable!() + } + } + } + }; + let mut src_bal = src_bal.unwrap_or_else(|| { + tx::log_string(format!("src {} has no balance", src_key)); + unreachable!() + }); + src_bal.spend(&amount); + let dest_owner = + is_any_multitoken_balance_key(dest_key).map(|(_, o)| o); + let mut dest_bal: Amount = match dest_owner { + Some(Address::Internal(InternalAddress::IbcMint)) => { tx::log_string("invalid transfer to the mint address"); unreachable!() } - Address::Internal(InternalAddress::IbcBurn) => { + Some(_) => tx::read(dest_key.to_string()).unwrap_or_default(), + None => match is_any_token_balance_key(dest_key) { + Some(_) => tx::read(dest_key.to_string()).unwrap_or_default(), + None => { + tx::log_string(format!( + "invalid balance key: {}", + dest_key + )); + unreachable!() + } + }, + }; + dest_bal.receive(&amount); + match src_owner { + Some(Address::Internal(InternalAddress::IbcMint)) => { + tx::write_temp(&src_key.to_string(), src_bal) + } + _ => tx::write(&src_key.to_string(), src_bal), + } + match dest_owner { + Some(Address::Internal(InternalAddress::IbcBurn)) => { tx::write_temp(&dest_key.to_string(), dest_bal) } _ => tx::write(&dest_key.to_string(), dest_bal), diff --git a/wasm/wasm_source/src/tx_from_intent.rs b/wasm/wasm_source/src/tx_from_intent.rs index e39963fae7..f86c412353 100644 --- a/wasm/wasm_source/src/tx_from_intent.rs +++ b/wasm/wasm_source/src/tx_from_intent.rs @@ -20,10 +20,11 @@ fn apply_tx(tx_data: Vec) { source, target, token, + sub_prefix, amount, } in tx_data.matches.transfers { - token::transfer(&source, &target, &token, amount); + token::transfer(&source, &target, &token, sub_prefix, amount); } tx_data diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index f0ab0ad2d0..059a3296e1 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -14,7 +14,8 @@ fn apply_tx(tx_data: Vec) { source, target, token, + sub_prefix, amount, } = transfer; - token::transfer(&source, &target, &token, amount) + token::transfer(&source, &target, &token, sub_prefix, amount) } diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 553288926e..a2f9a6267b 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -136,7 +136,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(&source, address, &token, amount); + tx_host_env::token::transfer( + &source, address, &token, None, amount, + ); }); let vp_env = vp_host_env::take(); @@ -251,7 +253,7 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer(address, &target, &token, None, amount); }); let vp_env = vp_host_env::take(); @@ -284,7 +286,7 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer(address, &target, &token, None, amount); }); let vp_env = vp_host_env::take(); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index a222a344ef..e1815cc0b1 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -34,6 +34,10 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { if let Some(address) = token::is_any_token_balance_key(key) { Self::Token(address) + } else if let Some((_, address)) = + token::is_any_multitoken_balance_key(key) + { + Self::Token(address) } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if let Some(address) = intent::is_invalid_intent_key(key) { @@ -412,7 +416,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(&source, address, &token, amount); + tx_host_env::token::transfer( + &source, address, &token, None, amount, + ); }); let vp_env = vp_host_env::take(); @@ -445,7 +451,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer( + address, &target, &token, None, amount, + ); }); let vp_env = vp_host_env::take(); @@ -482,7 +490,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer( + address, &target, &token, None, amount, + ); }); let mut vp_env = vp_host_env::take(); @@ -520,7 +530,9 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { tx_host_env::insert_verifier(address); // Apply transfer in a transaction - tx_host_env::token::transfer(&source, &target, &token, amount); + tx_host_env::token::transfer( + &source, &target, &token, None, amount, + ); }); let vp_env = vp_host_env::take(); diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 674fb2a2d9..1062d1ce43 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -140,6 +140,7 @@ pub mod main { source: _, target, token, + sub_prefix: _, amount, } = transfer; let target_key = token::balance_key(&token, &target); From beb6f8e69c180588d8db8bc648d61a568c06e988 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 20 Aug 2022 16:33:02 +0000 Subject: [PATCH 064/197] [ci skip] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++-------------- wasm_for_tests/wasm_source/Cargo.lock | 24 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 898f6e9763..1cf95608b2 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.16097490afa7378c79e6216751b20796cde3a9026c34255c3f1e5ec5a4c9482e.wasm", - "tx_from_intent.wasm": "tx_from_intent.f8d1937b17a3abaf7ea595526c870b3d57ddef8e0c1bc96f8e0a448864b186c7.wasm", - "tx_ibc.wasm": "tx_ibc.378b10551c0b22c2c892d24e2676ee5160d654e2e53a50e7925e0f2c6321497b.wasm", - "tx_init_account.wasm": "tx_init_account.adab66c2b4d635e9c42133936aafb143363f91dddff2a60f94df504ffec951a6.wasm", - "tx_init_nft.wasm": "tx_init_nft.d1065ebd80ba6ea97f29bc2268becf9ba3ba2952641992464f3e9e868df17447.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.184131576a579f9ece96460d1eb20e5970fcd149b0527c8e56b711e5c535aa5f.wasm", - "tx_init_validator.wasm": "tx_init_validator.2990747d24d467b56e19724c5d13df826a3aab83f7e1bf26558dbdf44e260f8a.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.33db14dea4a03ff7508ca44f3ae956d83c0abceb3dae5be844668e54ac22b273.wasm", - "tx_transfer.wasm": "tx_transfer.a601d62296f56f6b4dabb0a2ad082478d195e667c7469f363bdfd5fe41349bd8.wasm", - "tx_unbond.wasm": "tx_unbond.014cbf5b0aa3ac592c0a6940dd502ec8569a3af4d12782e3a5931c15dc13042f.wasm", - "tx_update_vp.wasm": "tx_update_vp.83d4caeb5a9ca3009cd899810493a6b87b4c07fa9ed36f297db99dc881fb9a1c.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.bcb5280be9dfeed0a7650ba5e4a3cebc2c19b76780fd74dcb345be3da766b64a.wasm", - "tx_withdraw.wasm": "tx_withdraw.8fc0a3439ee9ae66047c520519877bc1f540e0cb02abfa31afa8cce8cd069b6f.wasm", - "vp_nft.wasm": "vp_nft.2c820c728d241b82bf0ed3c552ee9e7c046bceaa4f7b6f12d3236a1a3d7c1589.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.6e762f3fda8aa7a252e2b29a4a312db91ded062d6c18b8b489883733c89dc227.wasm", - "vp_token.wasm": "vp_token.c45cc3848f12fc47713702dc206d1312ad740a6bbee7f141556714f6f89d4985.wasm", - "vp_user.wasm": "vp_user.d6cd2f4b5bc26f96df6aa300fddf4d25e1656920d59896209bd54ae8d407ecde.wasm" + "tx_bond.wasm": "tx_bond.23637103c6b0bb5604e51a522d125c102ce8faa958a73e31fe9ec495a87b5d5e.wasm", + "tx_from_intent.wasm": "tx_from_intent.b4d18f001d7f50cebf13c613dc76cd2157784d2ca38cdac7bae089ee76750aae.wasm", + "tx_ibc.wasm": "tx_ibc.459621fe0df4389118360ecfb333e58682d6efa465fb2743051b3b034de36f2f.wasm", + "tx_init_account.wasm": "tx_init_account.bb9a3c0d941e815a5363b5edcaf12e7a77aeefc7d041826c80f746a71daea964.wasm", + "tx_init_nft.wasm": "tx_init_nft.44e5c200985c93c442b122ecd334d7d6f33babfafa17b8263ebdbcc76f19b417.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.19e24f2b040d737f69c3e838e9a0d1ff33d87709cd1378c80561b32af1bc5718.wasm", + "tx_init_validator.wasm": "tx_init_validator.d62938cc345f4ae9f082ed5a92f7ad36992ff294800dfec63fed8a40207886e2.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.867743e04fba226e0794c9f70c039918e6cecc9aa17e89a9609d4782d9feca92.wasm", + "tx_transfer.wasm": "tx_transfer.119cfb69971a648c1363d6d466590bcc4c45b992a3f65ca51b47b8f4a8637f8b.wasm", + "tx_unbond.wasm": "tx_unbond.94884377c96b8045c1c965badc321275401e2ab9edc09f66e766a40e924e06c7.wasm", + "tx_update_vp.wasm": "tx_update_vp.cb21644aed96971122eb1a52f3db96da377cd19a7f028951f755bb76c0701558.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.854851a5af0c67b53c3165ccbc2355fd96a1cea29a48437c1c3e1ab4b4660dac.wasm", + "tx_withdraw.wasm": "tx_withdraw.eb07fbbde79b5c2355a4f73a30355aba1e64e803eff29cfc8161fbda946b2de5.wasm", + "vp_nft.wasm": "vp_nft.1613c09ad9ecac50584160bc8f4eb1b1acc2d96c5b51e91ac69ad17a439c01d6.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.598942468d0cb42d987be7b93f2efaeba98d837441c5a6971217c0dff6161064.wasm", + "vp_token.wasm": "vp_token.4988b829c7825aa101ac47903d3e363ab38b6887df95eec739e1726e3b29234f.wasm", + "vp_user.wasm": "vp_user.bf58f7316e142118328e4c63486e53003812b142b9b5927c83681cd0de0f74d8.wasm" } \ No newline at end of file diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 9df5195b2f..876455fcaa 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1357,7 +1357,7 @@ dependencies = [ [[package]] name = "libsecp256k1" version = "0.7.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", @@ -1373,7 +1373,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1383,7 +1383,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1527,7 +1527,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1584,7 +1584,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -1593,7 +1593,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1611,7 +1611,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1619,7 +1619,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -1629,7 +1629,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1637,7 +1637,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "getrandom", From fa3d72d26525f7e621471cb68a1ff75b7ebedd31 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sat, 20 Aug 2022 18:41:52 +0200 Subject: [PATCH 065/197] remove an error message --- apps/src/lib/client/rpc.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 7d7a859138..d39aeaddb9 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1414,14 +1414,7 @@ where Ok(values) => { let decode = |PrefixValue { key, value }: PrefixValue| { match T::try_from_slice(&value[..]) { - Err(err) => { - eprintln!( - "Skipping a value for key {}. Error in \ - decoding: {}", - key, err - ); - None - } + Err(_) => None, Ok(value) => Some((key, value)), } }; From c503031ce7f86317ec80f81957976f19c06d5a14 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sun, 21 Aug 2022 09:39:48 +0200 Subject: [PATCH 066/197] 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 067/197] change the balance format --- apps/src/lib/client/rpc.rs | 168 ++++++++++++++----------------------- shared/src/types/token.rs | 23 +++-- 2 files changed, 71 insertions(+), 120 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d39aeaddb9..7bbc0feaa8 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -131,8 +131,7 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { } (None, Some(owner)) => { let owner = ctx.get(&owner); - let mut found_any = false; - for (token, currency_code) in tokens { + for (token, _) in tokens { let prefix = token.to_db_key().into(); let balances = query_storage_prefix::( client.clone(), @@ -140,132 +139,87 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { ) .await; if let Some(balances) = balances { - let stdout = io::stdout(); - let mut w = stdout.lock(); - for (key, balance) in balances { - match token::is_any_multitoken_balance_key(&key) { - Some((sub_prefix, o)) if *o == owner => { - writeln!( - w, - "{} with {}: {}", - currency_code, sub_prefix, balance - ) - .unwrap(); - found_any = true; - } - Some(_) => {} - None => { - if let Some(o) = - token::is_any_token_balance_key(&key) - { - if *o == owner { - writeln!( - w, - "{}: {}", - currency_code, balance - ) - .unwrap(); - found_any = true; - } - } - } - } - } + print_balances(balances, &token, Some(&owner)); } } - if !found_any { - println!("No balance found for {}", owner); - } } (Some(token), None) => { let token = ctx.get(&token); let prefix = token.to_db_key().into(); let balances = query_storage_prefix::(client, prefix).await; - match balances { - Some(balances) => { - let currency_code = tokens - .get(&token) - .map(|c| Cow::Borrowed(*c)) - .unwrap_or_else(|| Cow::Owned(token.to_string())); - let stdout = io::stdout(); - let mut w = stdout.lock(); - writeln!(w, "Token {}", currency_code).unwrap(); - for (key, balance) in balances { - match token::is_any_multitoken_balance_key(&key) { - Some((sub_prefix, owner)) => { - writeln!( - w, - " with {}: {}, owned by {}", - sub_prefix, balance, owner - ) - .unwrap(); - } - None => { - if let Some(owner) = - token::is_any_token_balance_key(&key) - { - writeln!( - w, - ": {}, owned by {}", - balance, owner - ) - .unwrap(); - } - } - } - } - } - None => { - println!("No balances for token {}", token.encode()) - } + if let Some(balances) = balances { + print_balances(balances, &token, None); } } (None, None) => { - let stdout = io::stdout(); - let mut w = stdout.lock(); - for (token, currency_code) in tokens { + for (token, _) in tokens { let key = token::balance_prefix(&token); let balances = query_storage_prefix::(client.clone(), key) .await; - match balances { - Some(balances) => { - writeln!(w, "Token {}", currency_code).unwrap(); - for (key, balance) in balances { - match token::is_any_multitoken_balance_key(&key) { - Some((sub_prefix, owner)) => { - writeln!( - w, - " with {}: {}, owned by {}", - sub_prefix, balance, owner - ) - .unwrap(); - } - None => { - if let Some(owner) = - token::is_any_token_balance_key(&key) - { - writeln!( - w, - ": {}, owned by {}", - balance, owner - ) - .unwrap() - } - } - } - } - } - None => { - println!("No balances for token {}", token.encode()) - } + if let Some(balances) = balances { + print_balances(balances, &token, None); } } } } } +fn print_balances( + balances: impl Iterator, + token: &Address, + target: Option<&Address>, +) { + let stdout = io::stdout(); + let mut w = stdout.lock(); + + // Token + let tokens = address::tokens(); + let currency_code = tokens + .get(token) + .map(|c| Cow::Borrowed(*c)) + .unwrap_or_else(|| Cow::Owned(token.to_string())); + writeln!(w, "Token {}", currency_code).unwrap(); + + let print_num = balances + .filter_map( + |(key, balance)| match token::is_any_multitoken_balance_key(&key) { + Some((sub_prefix, owner)) => Some(( + owner.clone(), + format!( + "with {}: {}, owned by {}", + sub_prefix, balance, owner + ), + )), + None => token::is_any_token_balance_key(&key).map(|owner| { + ( + owner.clone(), + format!(": {}, owned by {}", balance, owner), + ) + }), + }, + ) + .filter_map(|(o, s)| match target { + Some(t) if o == *t => Some(s), + Some(_) => None, + None => Some(s), + }) + .map(|s| { + writeln!(w, "{}", s).unwrap(); + }) + .count(); + + if print_num == 0 { + match target { + Some(t) => writeln!(w, "No balances owned by {}", t).unwrap(), + None => { + writeln!(w, "No balances for token {}", currency_code).unwrap() + } + } + } +} + /// Query Proposals pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { async fn print_proposal( diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index 4d03138e60..a4a123da12 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -344,19 +344,16 @@ fn multitoken_balance_owner(key: &Key) -> Option<(Key, &Address)> { // token, balance, owner return None; } - match key.get_at(len - 2) { - Some(DbKeySeg::StringSeg(balance)) - if balance == BALANCE_STORAGE_KEY => - { - match key.segments.last() { - Some(DbKeySeg::AddressSeg(owner)) => { - let sub_prefix = Key { - segments: key.segments[1..(len - 2)].to_vec(), - }; - Some((sub_prefix, owner)) - } - _ => None, - } + match &key.segments[..] { + [ + .., + DbKeySeg::StringSeg(balance), + DbKeySeg::AddressSeg(owner), + ] if balance == BALANCE_STORAGE_KEY => { + let sub_prefix = Key { + segments: key.segments[1..(len - 2)].to_vec(), + }; + Some((sub_prefix, owner)) } _ => None, } From a360b0282a1c6e150108a4e1e14cd16f9de99b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 16 Sep 2022 18:44:51 +0200 Subject: [PATCH 068/197] changelog: add #359 --- .changelog/unreleased/features/132-multitoken-transfer.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/132-multitoken-transfer.md diff --git a/.changelog/unreleased/features/132-multitoken-transfer.md b/.changelog/unreleased/features/132-multitoken-transfer.md new file mode 100644 index 0000000000..2b913d7d19 --- /dev/null +++ b/.changelog/unreleased/features/132-multitoken-transfer.md @@ -0,0 +1,2 @@ +- Added multitoken transfer and query for bridges + ([#132](https://github.com/anoma/namada/issues/132)) \ No newline at end of file From 42231dc0eedb8e4a996cc9a832c7c8c02baad834 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 16 Sep 2022 17:16:56 +0000 Subject: [PATCH 069/197] [ci] wasm checksums update --- wasm/checksums.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 1cf95608b2..9c1ce45fe8 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -14,6 +14,6 @@ "tx_withdraw.wasm": "tx_withdraw.eb07fbbde79b5c2355a4f73a30355aba1e64e803eff29cfc8161fbda946b2de5.wasm", "vp_nft.wasm": "vp_nft.1613c09ad9ecac50584160bc8f4eb1b1acc2d96c5b51e91ac69ad17a439c01d6.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.598942468d0cb42d987be7b93f2efaeba98d837441c5a6971217c0dff6161064.wasm", - "vp_token.wasm": "vp_token.4988b829c7825aa101ac47903d3e363ab38b6887df95eec739e1726e3b29234f.wasm", - "vp_user.wasm": "vp_user.bf58f7316e142118328e4c63486e53003812b142b9b5927c83681cd0de0f74d8.wasm" + "vp_token.wasm": "vp_token.e3df9b76b573bf1a4d722073f7573822a7504e9ccba65c641ca54067943c009f.wasm", + "vp_user.wasm": "vp_user.42678c7c959fca64e6003b9b84dbe66cfd22797e7fd22047d352b3e61f58b17c.wasm" } \ No newline at end of file From 6a2b42f734c3136bbcdb90f8ac20a36b971b14f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 19 Sep 2022 10:53:03 +0200 Subject: [PATCH 070/197] docs/user-guide: fix broken testnet link --- documentation/docs/src/user-guide/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/src/user-guide/getting-started.md b/documentation/docs/src/user-guide/getting-started.md index 6f2ce00e30..45154615f4 100644 --- a/documentation/docs/src/user-guide/getting-started.md +++ b/documentation/docs/src/user-guide/getting-started.md @@ -23,7 +23,7 @@ After you installed Namada, you will need to join a live network (e.g. testnet) namada client utils join-network --chain-id= ``` -To join a testnet, head over to the [testnets](../testnets) section for details on how to do this. +To join a testnet, head over to the [testnets](../testnets/README.md) section for details on how to do this. ## Start your node From 48068f033ce5762bdef196f8f163869ed21907d5 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 19 Sep 2022 14:29:44 +0200 Subject: [PATCH 071/197] [feat] added `pls load test` command --- .github/workflows/automation.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml index 7c5e77ea6a..2bcd22fa6a 100644 --- a/.github/workflows/automation.yml +++ b/.github/workflows/automation.yml @@ -17,6 +17,7 @@ jobs: tasks: if: ${{ github.event.issue.pull_request }} runs-on: ${{ matrix.os }} + timeout-minutes: ${{ matrix.make.timeout }} strategy: fail-fast: false matrix: @@ -25,12 +26,24 @@ jobs: - name: Update wasm comment: pls update wasm command: update-wasm.py + logs: 'false' + timeout: 15 - name: Publish wasm comment: pls publish wasm command: publish-wasm.py + logs: 'false' + timeout: 15 - name: Spawn devnet comment: pls spawn devnet command: spawn-devnet.py + logs: 'false' + timeout: 25 + - name: Load tester + comment: pls load test + command: load-test.py + logs: 'true' + logs_path: /tmp/namada-load-tester/logs/ + timeout: 360 steps: - name: Configure AWS Credentials @@ -56,7 +69,7 @@ jobs: run: | git config --global user.name 'github-actions[bot]' git config --global user.email 'github-actions[bot]@users.noreply.github.com' - pip3 install ghapi boto3 toml >/dev/null 2>&1 + pip3 install ghapi boto3 toml requests py-markdown-table >/dev/null 2>&1 aws s3 cp s3://$CHAIN_BUCKET/scripts/${{ matrix.make.command }} .github/workflows/scripts/ python3 .github/workflows/scripts/${{ matrix.make.command }} env: @@ -65,6 +78,13 @@ jobs: GITHUB_READ_ORG_TOKEN: ${{ secrets.GT_READ_ORG }} GITHUB_DISPATCH_TOKEN: ${{ secrets.GT_DISPATCH }} BINARIES_COMMIT_SHA: ${{ steps.comment-branch.outputs.head_sha }} + - name: Upload load tester logs + if: ${{ matrix.make.logs == 'true' && steps.check.outputs.triggered == 'true' }} + uses: actions/upload-artifact@v3 + with: + name: logs-load-tester-${{ github.sha }} + path: ${{ matrix.make.logs_path }} + retention-days: 5 - name: Comment not found if: steps.check.outputs.triggered != 'true' run: echo "Comment $COMMENT not found" From 8b3b1406456d433141ee47fb4739cf1b96bbd5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 20 Sep 2022 19:01:00 +0200 Subject: [PATCH 072/197] fixup! Sync with 'main' --- Cargo.lock | 12 +++++------ Cargo.toml | 4 ++++ apps/src/lib/node/ledger/tendermint_node.rs | 6 ++---- shared/src/ledger/storage_api/error.rs | 2 +- wasm_for_tests/wasm_source/Cargo.lock | 24 ++++++++++----------- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 02fd1365bd..ac8e31770d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,8 +311,7 @@ dependencies = [ [[package]] name = "async-io" version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7" +source = "git+https://github.com/heliaxdev/async-io.git?rev=9285dad39c9a37ecd0dbd498c5ce5b0e65b02489#9285dad39c9a37ecd0dbd498c5ce5b0e65b02489" dependencies = [ "autocfg 1.1.0", "concurrent-queue", @@ -322,6 +321,7 @@ dependencies = [ "once_cell", "parking", "polling", + "rustversion", "slab", "socket2 0.4.7", "waker-fn", @@ -340,8 +340,7 @@ dependencies = [ [[package]] name = "async-process" version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02111fd8655a613c25069ea89fc8d9bb89331fa77486eb3bc059ee757cfa481c" +source = "git+https://github.com/heliaxdev/async-process.git?rev=e42c527e87d937da9e01aaeb563c0b948580dc89#e42c527e87d937da9e01aaeb563c0b948580dc89" dependencies = [ "async-io", "autocfg 1.1.0", @@ -351,6 +350,7 @@ dependencies = [ "futures-lite", "libc", "once_cell", + "rustversion", "signal-hook", "winapi 0.3.9", ] @@ -4697,13 +4697,13 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "polling" version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011" +source = "git+https://github.com/heliaxdev/polling.git?rev=02a655775282879459a3460e2646b60c005bca2c#02a655775282879459a3460e2646b60c005bca2c" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", "libc", "log 0.4.17", + "rustversion", "wepoll-ffi", "winapi 0.3.9", ] diff --git a/Cargo.toml b/Cargo.toml index 2bb0d8ad10..3ce019636e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,10 @@ borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4 borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} +# The following 3 crates patch a work-around for https://github.com/smol-rs/polling/issues/38 breaking namada tooling build with nightly 2022-05-20 +polling = {git = "https://github.com/heliaxdev/polling.git", rev = "02a655775282879459a3460e2646b60c005bca2c"} +async-io = {git = "https://github.com/heliaxdev/async-io.git", rev = "9285dad39c9a37ecd0dbd498c5ce5b0e65b02489"} +async-process = {git = "https://github.com/heliaxdev/async-process.git", rev = "e42c527e87d937da9e01aaeb563c0b948580dc89"} # borsh = {path = "../borsh-rs/borsh"} # borsh-derive = {path = "../borsh-rs/borsh-derive"} # borsh-derive-internal = {path = "../borsh-rs/borsh-derive-internal"} diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index ffdf3b5b9c..181f72cb4b 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -185,11 +185,9 @@ pub fn reset(tendermint_dir: impl AsRef) -> Result<()> { /// Convert a common signing scheme validator key into JSON for /// Tendermint fn validator_key_to_json( - address: &Address, sk: &common::SecretKey, ) -> std::result::Result { - let address = address.raw_hash().unwrap(); - + let raw_hash = tm_consensus_key_raw_hash(&sk.ref_to()); let (id_str, pk_arr, kp_arr) = match sk { common::SecretKey::Ed25519(_) => { let sk_ed: ed25519::SecretKey = sk.try_to_sk().unwrap(); @@ -211,7 +209,7 @@ fn validator_key_to_json( }; Ok(json!({ - "address": address, + "address": raw_hash, "pub_key": { "type": format!("tendermint/PubKey{}",id_str), "value": base64::encode(pk_arr), diff --git a/shared/src/ledger/storage_api/error.rs b/shared/src/ledger/storage_api/error.rs index d01fbfd287..c5e602c464 100644 --- a/shared/src/ledger/storage_api/error.rs +++ b/shared/src/ledger/storage_api/error.rs @@ -15,7 +15,7 @@ pub enum Error { /// Result of a storage API call. pub type Result = std::result::Result; -/// Result extension to easily wrap custom errors into [`Error`]. +/// Result extension to easily wrap custom errors into [`enum@Error`]. // This is separate from `ResultExt`, because the implementation requires // different bounds for `T`. pub trait ResultExt { diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 98263054ce..cd7a6a1e67 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1357,7 +1357,7 @@ dependencies = [ [[package]] name = "libsecp256k1" version = "0.7.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", @@ -1373,7 +1373,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1383,7 +1383,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1527,7 +1527,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1584,7 +1584,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "derivative", @@ -1594,7 +1594,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1613,7 +1613,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "namada", @@ -1625,7 +1625,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "namada", @@ -1633,7 +1633,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "namada", @@ -1645,7 +1645,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "getrandom", From dd13e3300f215f25a5a59951e2631af054919df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 21 Sep 2022 09:48:12 +0200 Subject: [PATCH 073/197] rustdoc: fix more broken links picked from #324 merged earlier --- tests/src/native_vp/pos.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 0cf9382673..528b8e9492 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -23,7 +23,7 @@ //! ## Pos Parameters //! //! Arbitrary valid PoS parameters are provided from its module via -//! [`namada_vm_env::proof_of_stake::parameters::testing::arb_pos_params`]. +//! [`proof_of_stake::parameters::testing::arb_pos_params`]. //! //! ## Valid transitions //! From 1ef0ef0d1e9abcac1b5460ae54127232363ca283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 21 Sep 2022 10:32:52 +0200 Subject: [PATCH 074/197] fixup! Sync with 'main' --- shared/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- wasm/checksums.json | 34 ++++++++++---------- wasm_for_tests/wasm_source/Cargo.lock | 45 ++++++++++++++++++++++----- 4 files changed, 57 insertions(+), 26 deletions(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6094550024..f183a385c5 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -57,7 +57,7 @@ ark-serialize = "0.3" arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/heliaxdev/sparse-merkle-tree", rev = "b03d8df11a6a0cfbd47a1a33cda5b73353bb715d", default-features = false, features = ["std", "borsh"]} bech32 = "0.8.0" borsh = "0.9.0" -chrono = "0.4.19" +chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} # Using unreleased commit on top of version 0.5.0 that adds Sync to the CLruCache clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} derivative = "2.2.0" diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 4aed0b5e85..79c1fab152 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -15,7 +15,7 @@ wasm-runtime = ["namada/wasm-runtime"] namada = {path = "../shared", features = ["testing", "ibc-mocks"]} namada_vp_prelude = {path = "../vp_prelude"} namada_tx_prelude = {path = "../tx_prelude"} -chrono = "0.4.19" +chrono = "0.4.22" concat-idents = "1.1.2" prost = "0.9.0" serde_json = {version = "1.0.65"} diff --git a/wasm/checksums.json b/wasm/checksums.json index e2d1296d87..e2f2d87b50 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.e57051db9c794067b0203363e135d0e0fda3be4ee6af1d283df0e921029e1efd.wasm", - "tx_from_intent.wasm": "tx_from_intent.479d4a2caf6b8c6b569db2fb8ec741a1304b58c167338fd6e1099b08d41bb046.wasm", - "tx_ibc.wasm": "tx_ibc.99da4fe30ebe90427103fadf999936c3d60b1d6ea25c68ff0d3b9a7d479173fc.wasm", - "tx_init_account.wasm": "tx_init_account.983c1a26ef1337b843ab243662490f56cc9e4a35bd6854959a678e57e4bcfa8b.wasm", - "tx_init_nft.wasm": "tx_init_nft.12330bcfbc19bab368b643504b5a40473ea19fc936407e4e79b7259a1b58951d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.6397eb2a01f5574ef745a38ea1f8fcbfaa86f68015cccc21ff455f63be215f43.wasm", - "tx_init_validator.wasm": "tx_init_validator.f557a23db47bb5b306cf89f259eb46702492b4df49cc6418dc405df78b72332d.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.766dbf72a8441689c9b03f3564c59c73747729c9231f5ab30d37f7146cb4bb99.wasm", - "tx_transfer.wasm": "tx_transfer.f75183c2dc5bbab3af9044327bc22b1c38f633ffe959e6170d82943cf4924a94.wasm", - "tx_unbond.wasm": "tx_unbond.41556156ddb543a121b70b8517d7c9ab821c1591923b15ec16cf9d7a01e6396d.wasm", - "tx_update_vp.wasm": "tx_update_vp.f2632c6b949a5383284278bfd059aa538d7b26e521a7aebc823989d78d7354ad.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.7646435ae6f38cc6efcf50eff0763139a632808fcd1ff7951c784b5631f54836.wasm", - "tx_withdraw.wasm": "tx_withdraw.a893bc7fc00c3d670d53c25235d50df98aaaacc2877c500adafa182a658a477f.wasm", - "vp_nft.wasm": "vp_nft.8172eefc935351b9b04bf094270198ee76e9557df230d00e3ff859f0d5e67375.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.229b3e44ff0458b5ee5f58b962f90b46568c09faee20f8c0ea279ed3c774c056.wasm", - "vp_token.wasm": "vp_token.6838583ee06be081a0643754acab19aadb865a9167386b301c69991756437235.wasm", - "vp_user.wasm": "vp_user.a6d7344a8a78360b50a86e0a1401ab509d1cb6110180c0e2b1ce9089b18eb54a.wasm" + "tx_bond.wasm": "tx_bond.fd431211cffe6678a4aab29c220d95f34bf44f9dfe5b7deeadb48f389c494003.wasm", + "tx_from_intent.wasm": "tx_from_intent.4f1c180362177f07a37e24e836dc15fcb1c98c0ffb3613312ff0647f3a6cc881.wasm", + "tx_ibc.wasm": "tx_ibc.0006a82894609d9f9bf474f77a62dca4dd58f8a7ceac6ccebd396391418eb75f.wasm", + "tx_init_account.wasm": "tx_init_account.44bd4e179a6a81ac725caabab958d366b33be6c31f8ba884116ef7cc1398eb7f.wasm", + "tx_init_nft.wasm": "tx_init_nft.878b9dded41ec4d89b81e4b4fa89c5d24359e20eb49d1936d47884cd155ecb39.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.8387f70ea09f674af60e9134af351f698ff8a183a9a1568c34ec7a6074292e22.wasm", + "tx_init_validator.wasm": "tx_init_validator.b090e9ba8ea94214d7099d43d2af7d46a1587e6d9ce73183211f33b454830880.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.adbcd0a341d04538279c55c083cb4ac4c8630bfd3d3cb90e721af5a26212ad57.wasm", + "tx_transfer.wasm": "tx_transfer.ff667b75188859a0c8803beb2d9b3dd3c2d2753191003f4ff1d5fe7fca56f3e9.wasm", + "tx_unbond.wasm": "tx_unbond.a52c41c58da4e54f10a423a0f6519dfca15c96757ea72a8168e945900329d34f.wasm", + "tx_update_vp.wasm": "tx_update_vp.18fbdba6e4a7d158daaf8e9c7aab4fd122a95bafc50e486a903ad03c287c6b88.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.5600006579e70de5ae488c47721cd6366f52550da59eac9a6452fc9056ac5065.wasm", + "tx_withdraw.wasm": "tx_withdraw.044f4ca72eebbef1d3f7ec95f6c557e279eb4c3e4c994eac6fbc5e76b7afe1be.wasm", + "vp_nft.wasm": "vp_nft.6b40c4d28bf92db475cb3d3634cde01c2a0aa4fa7b7764091244dc2cb402efa0.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.3dc90813260f06bb64ba3bf8f0c39c0a45aaa1e0466edd0f8e03a16647e202ec.wasm", + "vp_token.wasm": "vp_token.155172a21c3b958ffb9c10899c4411fc2fb12c6cb882b4f8b6aaddeee0331d48.wasm", + "vp_user.wasm": "vp_user.b764c772d0d5083c0450aa69bcc672046ffbb4c0ec1b3977e90d391249fd8c08.wasm" } \ No newline at end of file diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index cd7a6a1e67..d69927cade 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -37,6 +37,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.59" @@ -383,14 +392,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time 0.1.44", + "wasm-bindgen", "winapi", ] @@ -421,6 +432,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.2" @@ -1184,6 +1201,20 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "iana-time-zone" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237a0714f28b1ee39ccec0770ccb544eb02c9ef2c82bb096230eefcffa6468b0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "once_cell", + "wasm-bindgen", + "winapi", +] + [[package]] name = "ibc" version = "0.12.0" @@ -1340,9 +1371,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.121" +version = "0.2.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" [[package]] name = "libloading" @@ -1747,9 +1778,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.10.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" From d5d2ec671bbbaac9ac33ca5ee54b6f8134c727ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 21 Sep 2022 10:35:27 +0200 Subject: [PATCH 075/197] fixup! rustdoc: fix more broken links --- tests/src/native_vp/pos.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 528b8e9492..ec6f75a15f 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -23,7 +23,7 @@ //! ## Pos Parameters //! //! Arbitrary valid PoS parameters are provided from its module via -//! [`proof_of_stake::parameters::testing::arb_pos_params`]. +//! [`namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params`]. //! //! ## Valid transitions //! From 9eb27059642db42d524b67bf95b9a9bb72115726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 21 Sep 2022 16:25:22 +0200 Subject: [PATCH 076/197] fixup! Merge branch 'yuji/multitoken' (#359) --- wasm_for_tests/tx_memory_limit.wasm | Bin 135517 -> 135517 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 226159 -> 241603 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 213719 -> 213875 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 152558 -> 157244 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 218648 -> 230399 bytes wasm_for_tests/vp_always_false.wasm | Bin 160766 -> 160758 bytes wasm_for_tests/vp_always_true.wasm | Bin 160766 -> 161094 bytes wasm_for_tests/vp_eval.wasm | Bin 160961 -> 161715 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 162919 -> 163755 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 170873 -> 176873 bytes 10 files changed, 0 insertions(+), 0 deletions(-) diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index c31821bdf45cc9da95e876de939171236563b048..ff162b561b3d58cc4013c777fd8a13b2ec3351b0 100755 GIT binary patch delta 620 zcmZ`#OK1~O6n%FxX;NOalQh4kiJ3_g9TROPY12>AN?%19l%gmSms&w3f`U>I6)7mC zu1vWo2wJ2?(?xJIil0_UX%H!h1{60IDs-nCK@jT3+Xk0z&b#-Wd)_(kuFl$5XYHl3 z)NGqBho;|N(Zf4D9Z~2_wz_YMUYDwr>7AES+ z3}un24;Mv+YDLGf!Be1{wZS#Xsf)!;-bRD92Kjtw1$3McAsU$xU#yl+{uzUYf%79l e>(**;Fdx98ah$^T=P&(dJ2#i|FP8G~;N4$18>AHg delta 607 zcmZ{g-%FEW6vyB5?%fZ0X1>$?a&ErQbX#k)?cH*w`w@GJau`7*VRxZH(SicI36l^C zt*ev+T|{}Ys1->!Z&66aMkB(oGK#JwgSx6eqBBRA-F%*Np7ZN_&Su57S+OlvmmUS= zHtW*cOVNP8E7%8n(;cc~cVaNpsi*dIy0x9rU9osJmmkWE42%5-_7zjcY0#}E7yFGh za2Zc*%mhhXWntnLt18KZN)cZ%FW$4qA!&THUk2zg4)3^bF@8Dg03NKm*T9R@o)g6P zo*CjH?>u;ob?-_W_>7spaT&VtL+Ax?TnJA9H@=7OEAC#2i9a`LlG-CNZsx#c4l2jn zv3>~Q>(~*ZJ$?-Ojg#>oCJ3WQYQ)LpqTfjcAh`H)i>jNBS-dfe#+5lR?>{ zH@aouhSP~-dEMn>U6c@&2{gC)f;xaV^G~%1b&y3gD`^yZuuOUs^0l&9AzO{$RACY# zMx(GLJ0jv`Gj65CMl1NNxCRlN69GzD7T?UK0rnLWx{eP={pRiSV4>v0S^YQ(9w|-# P=R#NOrRVh$+#_IRYwwjbX*Wrwb1j1GNOK9tn&cFeJ+|}_QY_~l?zvpA=eG5N z;@%aSG)6tvWS6Ei(4r9w7ATr)b8Ll1s#LAVa!Ijj)gG2AE1@8Cv zA7eh&el=+W9M8uF_L_5!F~|6i|M-vpdyE-fb?x`YQ5413opoipZ{NOn-<3)6H@Y(3 z7unP3%8W8@VpZVJ-YXk=z>{R}-YDEq6j6oyY%l+Xmw4K=x_Y(cuXLI2_5JMK8}Ci7 z%F;KzWzShxz2&Xnd({=Yf8dI%-}>EGT)TVM_q^r1qY;&K%aUDhy6TP5YCSu<_Uzj4 zyL$H(-}!yt^<8h;6^*HIye$0wUEj0&O;I;}U;GnsnvKM9lEiT{P8wYBUmVAstes>H zaFE2!tigjQUNw<4v!s!!IBtz6DvRPQ?v1K6jxs&>|EUh;{FmATE|a9yYT8X(eIn`@+Tw5s`nbYJY|=Z{96eUUr--cQ)?J^F3EJZpaM zo4)t0yM8cWVkL<&A0Fx#Eg9zv-%LuK3QYu6a4ifGMDYdLL z&AD&)8l61ixf^pC*PY);+rH82?~5YWn;VR5h!WbrsH>mEH7@Vcs!L~Gvd6`9gV=pJ z-oc~DMO)}Pa_zZ6q8_3_>h|s&H1#xg$!>S+k9;DU9keOwXL*!oDrn~kKVvblEPg*O z-ggIiRqv(AwEw5^&J7wobJy)0WB`|I#DkWLcM4EIjaAaVh*4*K>wzDq9sy?O`a!IB zVndZncjnoyElHNEK}N)jj5ij3qJGN;({BP|-cr>j<b|26i!pBZM2?J+N>^7n& zwUZfeF40>WtMvfTG4Ny+JV2xQWCV0A$lCOzW@GRk^K!eBr~SmRm8TSR3^9hnPCnv# zvm2t$hi6|}&EP3s?OmoGOvU~gu|W;@G?`nGL+#(*+I9InkMA7xTy&}E;!e$4-eVqm zF7CAkFX+Z&t2-{v)4bPvf82*0^WK)^aR#YDJr=ulc}l(XqVB3Zb(6F1qbDDTUKT~K zh$8NLv+l#(Z;Ya?cHf?Lchv7cP~I42w?rJP@@(e)^YinE(ydH#X1EKbp_ermMr|6u z|ML$(#Z)lrbaDR$V-ubH1qG;UYM`zWsJdqn1nAeecs4v;!$c0!3!{S#kDfdohf78s zCi3=p*2P9FGs)KE7TR#f{!ZN<&?O~@Gn!4@p^V0EQx}Ol*x>pQ&)s9$EPoHgv%QgO ze5)B#vN3%u+X}H{uB|G^j33ZXHPF?JFbwqM&_Ks}0*pr00ZotcdoUJ1=~FNTko__J z9*CL6PCpe=5_hYw(d{Jr{2=vL)gF%rO)4g=6c|K1V26Uo;z0_I1}uHUT`DZKE{r~s z=7@&`zU$T*jW8=`1MHcY`?FV^d-by9+yi}jWF~+6OuBz-dMF)B7ff9ortYZ|<3-&DJ^Ecd=v|G9Pr?{FLJ3RdCHx2vz`>H@oZ30JS2or-^;%T*m(L16&#(O58iSY+7S^_6kHd6SHw^^yB)S+8W)=uRGR2{LMsL5zDMIZd%37 zBiuB?jeZ_#P-8xu9gG$=PNuViiQ?u2HzT#`$Kj#wp+>bY~#JLUA-<+ zT4E(}Cuo({H>Sr^4+xB6uqtnDOaoxYxo_p|^8O_E&3vT1p9j_~pD6DSaG&O*#l4z- zq_Gudq^pM-eRcW>ZHzJtMIR4w505SHk8+>o&GP;}>$6eb-@|>o2IF1axAIj*d0>92 zamf3ah!Pri4|hesTfgS%#7#Uffq_~ ziw%b#gh8e51r*`5$0$mf*Ce+tu!VuNA85C%2Xh!q@C@QEpTQyqBP9)%glkOV zJ)mC9tbqNXpNjI`1Kg)IRxrPKq~R@{D~V!$Gj1F;%s=&#>m=nE<&5Ie8rP=BV?BfJKPDtNDuP|FP>$(T9-wcL>IxZh9JnffuaX z-J8t%nS?_m?tbn@)wbO|XfT)%u=|I(4}#b3ALTx9H@p8M-p-2Aqk$xc12l)5uZRyB z3BZi7Dee}usM~kJWbWchOa<*Jai5|E>D*rDIY9e@`<`TWV-ke_L7oZE_tl==#xvph zXcAbP$H5_R@Mr2NnrR;x#NUz}@;6flzBxYZFSi|dW%M(;Of~|@)^wgLcQP!e$7@}j zu;tV)!8p#n30M)ID6@RCbf> z!gtFq1Zhj#g=@WC_~bv;?ZTxP^Li;*7j=zK0DLI~#XN%aGMlPY044KGOEkM`OLT{D z1FHFZ*bFN(5gil46Kuu;7mjJ;3C36PMA*Frh5&Yhb{RS>fdHK~egm>=i<0T67HkES z=P}1(Z|co6idNqAl8xR^;>bY>D3*yk!97-UdH)Fabr$!S96uEcbH}(}z)$9(k2f2s zFwa}lOX+1BjHT>ya~VvnCOixG%YI@TGEx!VId7t0Pe5SLkiF1 zRn!a~%j+exS0h}tC7IW%k&s_YIcsg8aboHiix`aR<{P-FcL(L4UGyoS)vHtk>+qb= zAxOxrD~xV1n4hyH(W0@g&NXD>&a&oTy14nTE1D0DchMtqsMG}Vv3S<}IA7-yv)bH4 z5fAbfHrBZ=yQGUQ*p^34;9!B8vVsYd__xk3-a2k?H8H~0hPTXjXK!WUE$M*$cx+ti zQPKVLX{bv>nj2wtG9ao?+cq;i9E^G;FQ43!!00`;1{54cYiLu@>!4x&C=qVn zfa?!NyedpPxMhsAXQFDUnI37y=zhnJiwVS*eA9-tV7ED3d$r}z0zm4B_ zwKvI2g<&R7d4w|goI7F1&f<>Nc_#GGGm;0oW@W#FSBwjeI2%k#Lpl;3=oKS+Sm zbYjMESLc}bxip~>SxyZ%CF&iTI95ObSYbFpW97sNFud~CHXh^A9Pj-bx~YDy?5_^b zx%rO4i}mb{gKqD&R!1&*0Nh9ks@VBlL$g!-Fr6FtdGVGc=XHQ#A{lGCjl1(bCyQp# z7v@i#cf;$%d>e3!DMZI5RCpBhv;-XVwk3HTPw?e>r2M9kB2);43V6^gDu+;b*Sn6= z4=Ciiz#W3mJ^r}|sAXu&fp-jyHPpNsr1_#Tb0nAZ@$@av-dJ}`ElB`_DO_M4>FR%D zx@~LvhM*lWN27j@-cZhwmh{jBjWR(CXU3Vz1im*6Mued$a5>^Rd*M*UiQ$LFtWAV@ zu!-qr>jcJ|@fm5t`dw$s^2JB;)iRS=r9|PjBt4a8nquTbvPQ4;`s?!XeD%fIJ|2$u z)~ZI-KTAJt9&}j3`C0hF@Zo{|akND#(7GC%)$HpEDirVk7EO=6C|C8k7COktGziwW z_*&NQ!f_b`6x2Zju)ap}afUliNBwR-(oe+(kQc%klp;0?vdkwBn^h!04g(4$2H}># zF^2C}55>(@wF3|GK|L4`08%+WD_|0~iS+HdN%(Cy>U2b@V#UY_V{4Op@ATIIZ1FvY zCc?eu1_i8Mn8)0Ing1qk*pKQ!Ai&55K)V$(_R1k zK;*XiIdo4$sqSvH>)yHYXqYxs3D-V~kts^C7oB~R#&RPx)GWG}`3_=JQO1cIf#R-f zUk*+ZhyLT%I7M{ax?WFmZDT4sLQxe>3hygSA}Q1!bU?r6nI47oK?mej(;zg6*1OR< zFVd_MQ=u`2iSbj>2~*J-nhK(%nyo=ky^d)r6wQK=>2kKOCTvC;nvsTPBp+AJ@gp;f zrvmS6(2HJF=tX#)n9qs+Xg&yf(WqY6K=RR`7rC##4rXy7^om-*O2%afSjo0j$y3rW z%;mf#xsNNZ&2x5KbNCojZ8O;$;TJWGzqk_^n4f>9RHnYQg_n)#x7t3`8<-KgQIvyY zSv(*_GXuhH`Orue-~wTb1%^~4I9b7bXn+OgDfOuoq(iaCy#^&ZEuC-Cot-28B?3UE z0{=^yG_5eLp}7Hq!ps-14)O>wWa?lSvskiEZcy1H=GVYHIi%UcXt3xUHiVo>Qi~7s zJC)qlfVCz0DE08IJ#Pb?SzU$3TN&WY@X&P1tY2h++jO$*A(b(>zeG1%Vk-t zs#_MeRD)%a9+A1G2&b7cdGYF9O)Wl|GUtR>7nw2$hPT_xhgFw;`^C$L>}9Ro$>mmV zBn1=2@}h1xZ%8JJOA%73xD%`4ISYv_a3+IUNf3qk6%X_#qZ76Yfa|tb} z0xnmPmo5)+`3?RO??!HEA~--0xow{sVm=NLC$**ZJ&Xj&`*0Zj=Fp^m&%=MOUqrYf z>3Q@m3ZqLDlrD3#sRIL(EnZwcNp=v+LEH)+W^CNSk?nxV&K^z0}6s%1^1S}(@<2fJ~R;Vo~B{Al0;8 zKJuc&{MBM(a(hm}{FFH5flW~vJ%>}Ee@a0ime1AZ3>`|WDBQQ1aA>Y=qh={IX)x(P znGa(f%DiFj3oB@x53AH5_JNnla;gKoLW5vY%-X{(lJeXmJ2Tv4B5)5Au@mARA=0#n zd%*Mn;#QDnnn_Ku!Kn2*=4cl~kq~nv@?z8lYh*cVUsX`5_$w+y^OzzEsPVx{rWE*NO5u)Z%(!418qOe9R+AOq%CrEexq{+; z(`)ToB82=_cXHgLm^b7%sdGsBI4|k6HraPjwejF3X!{Uaf%gv{Y)F=eoG8C$f}LT} za;Qj(f*i)sl*}%B?5T{9Xa~PtW=5br?-Xipb*({ELojdUbF`0-v!hsKo*T}4%FDw|!q$=oDX%<|eC~QfN>I#*WzpVJ) z3%YIT3To6eh8Q2@zJHfZ&qVmWGx9?*-hK9DN{%Qgl2A!+of)cPFu;v-JU!AA?Ubqp zS2XC=jTs2#qHc3tvR7?2UF)@_zZwBPj<_E7SGSO6^|ZQ4r#WLW0{G8A z)s`oP^=agR-gjhcVo8(4UYTc97QzN+oWq?i)gM;uLXL$c#JBht|CgruIA7wWOKdwIgOdI85UrNz!44awGsM}%(`0^? zQR9Q$6rpQXI1r1WDGPW@^sQ7TJUbk#-mC}pQ1z4^fWW$aqeyk04@aoW{_*Qn=^`lD zRfL()ZW+L>fbmi4Xspw5C-FeKm%7_A;d<-k-YH^-A)H90z)~3ai?+@Dc`iP$9%YiPW1xSF2o4v>scIb2h@ za=iPLtps-x->oyP))5f^u5%G{dD9u-|GjPyeNHK~Ff1=b=_!aNqc<&mJW z${?2G)yzxqwn!)*#2yj;%Q#eY2VQc0+??9^)x>2ZskY7MV+UUtl0YGH@n&Hrks=tgrZCK;N&7zB69N~ z=_Tw#+mguj47&yBv03-fyFL-QWBlpe8V`stVLnJZU>8}congD#h=?e~n~*nltamrf zD`Mq(d!{2Sy?qz6N*k`Xn_l3d+874|Bj2dam0Fpfziub44s9KiOrz_iJMM#Qffrs^ zkOS!SL~Ze=E4SbYSHnY~CJO(_Y6z0_JW^M^ACGN7fW_F>ipG`8xQljK}O}6Tx0qrDx|H8<{3g~(<8R}rMoe-O|UUb zPT|aW=O9tkHg&C?{ie)u*V>MVlRamCoCZ5X`uJhoNouD%`)w7a(*lw)5i{|>z3@93 ze)I4<6@E8|-);7r&W8W!B_X6v2W^F4+Bn^L@XEOkPw)z<2V#;*(I5J)UT6yeQg zM`0At8jT2$SM333HB+C)v zKrv*nycdbkL|$Yfh~AL!sEHyinU5%1F%uP3Hgj{vfLPq;v0dS#XDIG4D~YT2Y%dqM z8Mz+D1rERMXym@pO5DR*bSHSA75(4SuyxgsGMTmwm`s{w5NSP;K?FcrTbgl|ppqK# z<1HQ}_}(I=p8&6x9za7(_5<_r8CmS?ZCpCWz+QP{rA1hx@C*yD=%DMuujQ&`{fPcU|iD-BB6 z6e=em6mvAN?v(i&m!>;S8mjX+81H@xgpkh1*tu715uJyOS-8ox|5r?6Z>Q)Zak=en zEf;@3lDi|?6hW8}XT-{Yo@U*b$&=aa{!qi2fQ!1(?du_Sr7cISlRnRRnC-EZJh4N| zEOO$piMu|&+-5&^Up#OWp6A=6%<=AA?|-33?4#v+-1-&i&|G};aqnfu4#GtyfQ=nK z+Uy1)q}{fL$m%cf$5YX{jBIi@z_lezo@wperRzOz{!8pfrfAQuT?31@S?$SFsEvBB zwNKk?PN6p8H!%!q_fDbqs3Zb)Gv3pBCX9&7$=$p3 zAPkFp4QtHpyQJ4;2U~6Q8Zvbkp`csVWqB9v!U?F$gU~MbYInS>3;!zW!v3hsgHV@y z)nz04c7s6!LS!2-`>tiDcCX9-@($oJNVbcQp)q{sSAO*meu(;vrB)|V9agC?-m$QW zv}~eH6Tf=&r~m0cE#HI{uh0Z~k-Oz%zxAbMp&(#~Vd(4B+8HE%JOW0eiQLh*{n7P5 zY&EQe=Y!U@N(w!iUp63%Cvv zX|fL| z1~Kf-e-641&H@z5nyv%OMzmMDj(S<75}4-5a#^GqjEzX~eH9mhd4K$}BJiHc+ku{W7Y3$4ZX^UrM2F6#I0hCR}eDcO? z0ix7%yJx6pWYr3DZzQ(#SHdx5bHWhvwZ$Oux{wz0JWG8Ah9XC<>fT-2yW<#x92QtOLaS!3I;E5>IQnShR z#LV>V(QMJyFy;Hy>=oPUj;bMTor?J_(&5$n#x!R~R9KAFYPhsq4KkG$ErkVZAodY3 znd9a$yq}Rc6FB_-S@rlON6lXz@c>vdvH0aby+x zNS3Xw-gnqK3Ja^-0#^1ZNSkRoGKg@VFm(zTDFII_jY&s~(Sl=@kyk%xTV^&C3HPO$ zHXGArv)NMY;~wib%nJ5PdAK{-&%@M|dF!$SB85EAN>KMI8R&y=;ST^=2lSG2oyA(mg@A$I0k6eUo*%D$?qK z*eVwAu%L8ashpqHdt~U!A#$%mMi)yfp~AUVA$EVgTm^A8Of~oSsz4Wu zT6j=9S5)JqYK;0C`#C(Q=);%Z%98?u+pTn+oNt>Va}{oi8enf70O6p+{EPEgxAM6S zOyADTJAlD4={^;#_Bsx1&QZ=7Ez)X_jCRJv2ogm*6Mja;4-%PBW^9g0TC=m z;aDWnGEEM{2NJQ720yToCO@!|7C-C-Z1V$;80pJzxOkO@wOOrF{7dGnEB#BN_P@-= zhxFoM7Y7>(HPrTiqcA4-5`Jc1ENd+*b6_-2{c)kiC);KHQBedAAZCJeA8`b+FPWO7 ztVrsav$Yvq!hNz#`rUMz!!)`vg{^!XPZ5pclJdxgfXt^{QA1EKvHYP1aZQiWgx6zU zV;XGB05j_&ry!Jtn#WRXP*O-nQC7$`H|&Q+!s7UU+p|eOQP!+hY-pDDL?G}F*izXj zHs1=3wlzPrE1D@_L|2 zIwXZF&MzUURs(YbFP`tKX`hr1>CZ8f%3LDGt=*J<=TmNT-xi(8bNRRKoXUB&!j*Tm zVX47`!&X;g2!7v*k;NkCK(R85-(sN=ifG#gSi(j`eNckQ0c93=JO^X20XInqZS7d! zPnfbMv`AKwPKj_YBl}6te_!eg#;X=r*1#Vj3U$)BC@WTgxEUAe&OujuBDKR=)oqC~a}ey@pp|!^VEOEe zrJJ|qs^_2pwrX}+zKW1`=K3v1l{vOmkBBXd(kjj2o_!5yv~hF z=Yn@E!X^v=TH0@U`GdkYCmf7w1K_AH$+Lc&*61q?9)_rlGEkx;G&+KSO@m-ybPgXh zBkF=)CLya09`2?ja~phnc<{D7^7dS9@aj)U9ogf@-39~2xTy~M38ArOZ18XwYaLN( z!$uLPM!c}L5Jiv_SV0u2kJ{77nM0xoKgcW$5<5WehC!BS?g~3nsA<56iKI({p&WK3 zN)grrrPSI9Gvz5IrLI)C9>z1%iH&-^?hwzrj58Y+J!J{+cwz1q6FfFI<` zX2`F-@j( zQg~>6sW4MvHe6T=k03D89o-hQ1^V%YR;Xig#YL3(iIarb#BL zY$3reCCKm`MzAVx_q%=+Fm)S82Mn&#x^3sH6hhGsbBjPDG^AS7LQ$Nv^^@J5UNMBx z&vGD6TfB4lUn*swH5iw207blp7E-~E1mQdnOB){dssf8QMOBbp=C06;CKBRgGplA& z)U5Odt8co?Ay`UgRCQ!GY9yE5N9vF>`n?&3Q6(Sl;5X*Fv-=`IZg2a5hYNsW{z>2I~ zu}+nbK(K}+@Q{HHP54Mr8ERjl@>PxL`GXn+J6L zMEdSU{Sp3e?AQpSN8d4mjN|o`uyp7rkD9xR7Y3_^k{*XVus-DFGB{aF$R*P4?QDiz zsw)&^_KcNeZUFaSV9DBA$hK3qCwZm^$d~; z3=g@wDPmXwMJkkFe-QSgXFM%y%r@m z$BLUCtK2;w2;f z0)GsL3UnlE@e+knP||RCY$Z-nc|vXiB3ObIh1lKuHxE#>nXApS?tX51U&nCB`VXdf z+IuDUmW-G;4K|eMe2h8Pu}JvVa()_af(IOzL?dRaw{f97LYOOkj8DX0LBql&LRT z;1u)(SD(r*(6MDxNe|tbpGliIshQqlf^FKGYAg*KwJuUh$q+BBWHkC zoP3fkJ84O%YW4n&^-XOW-iC(}^@PJAG;BLYX`7^(My~P;Xq={t+*q7HT6rQRaulth zZHx{W+SVu*GkY%0i6GZy(GXw)mZ|=TDBGYXqd@KFNeNkKa5G(&khP$;B_K1QPi}38 zZyBKD3mz!f+*@=jTSm4@YJL%>EgyqA^hnN3d6IEk$)q1MJzI98)(qnp0ohkYwn>vU zBFW7CC)jDZvD+Q9MC5C+l78VMKl>*smJ-iju!ru^{dxM=w}0Xn68}`6Eor%b`sn`8 z$NoWP4?g+X1AiIbYukg5{`&7f5?<-;#N{YkMEGI$Sfz~8#X{yKF5b=W9e;M{Pm_z+ z_u(x@kyIV>lczj`_oJfL&V|YGjNm zqY`8Z+BR+tW)lDCc@kZjWDQP0c|yn>AqiZ`sMsiU1zy@0MtyqAF2t1q*H$g`>((Am z>=)#tCFzb9i^|q6iwc7ZMnD7gMFm6NCR+fgAXn2D>6b1guW&id?Urt5vaR77>eY~M zthoq9+jOOy2W!NaZ5^(;lasBPfenLmGy}cYF3ytk2r>*Xvzr;Yro6ufPOX>>(HA*L z)?m$5(NiT+Jo&ehKd`RixEFs`Tuw@1_|irIaT|U>h{J6{QD?)3*uS^_U31pG^RLQd zeG|98ya`?T&S!oE0l_)4bcXX2A{z#z_|D6-0V0MaE?J%1=bl8qZu0V#GY!LOe&WJ2 zn~dfqh{R5rD5Q;rfk@uK-Da0SrM==ReE(kv8E<9+fbU8kSnh~~X0VaLz#7tDncJq@ zBx{^Tysp+?<#3I%rUnyp=qX&C3k~ULlKvVPU8xUA0PTNq+V$ zHdSbxb6f*Yn^Zrmi%!P!RqP;T+pyO5CRiz8MLBe|eLc-SS@aP`B3NJn9etwaTm}~~ zEinLeO!69hrQ%RpH(hxZaTrSsg96e|EvL+sU5_boITC`HTMj>TY+4cm!nu|M_I=0; zfATNkVkulqPEqpVs#~p(yCI2CiamOELH81UIM@HGBz5lDz3}P!{U^(tY8^C4!+o#T zfgNwXa2=@CQ&|UQfHt%czp)O`%VymJ^htWR|7^YQ>bH*T>6JX(c~i-J*!Q&#+3vyE?S*XjQ5_kzc7w{qQ9v(5u{9 z_v=ipo8&-8(+@B3)`bKK28I@HIXiOqo-quG&0@98ybS_6Dlox5vTl7XEekvKL@po*&2bkP174v`_@=KSmvhSXKU{?s|*{=S6n0IEx}}xx+7gO(WEo6 zAkOQI*kpsyLQZYsKsZE)^h$$=hR1wz0%FZveV`7*OH7)hICv!MzbExo-s?88T-?%0 z9-+S5C;u8coxs?1nTdiJAg?SuLz&{dHor9<#S?bk$k9JHo7bdPlSN?$b5&cGw6%%i z8REIH0rls#Sw`eHuwfKPOhDYK?OnWDhiYbu8; zEE6gQ!~RIt&dfpIDG8Z)if#D30tf97VA`M%X-SO%>i6{i%hml7 zy_5A?_RcO~lUViM|C+fj@9F*X^?Q2%V*Q@pZ>)NY9lgJ^x?iGq_+#0-LeUbCWp9lA zX#JkvAFAKe`y=&xdVj2bPw$_s?w9BtBax@fu+9@dO_INdmX{!T2tANI#9xp+IbY={ zn{+6-UmxY?>-UWEi}ia(d1Ez-Sd!#j^?OG7?&^MtQId36_D<@UDH8SGAFB68?~l~) z>HV?#J-r{R-_!f2tNSH-N6{{O7cZ2OQ}6u)^?Q21qkd2CAFkii`$y~d^nPD;zeMjc zQ;L-hS2mrh-uvg!qRM-E|A+cLy??2GPw#K9dNb<2xw>DXcLD)r?-ZNPR`v#UKTvO- z-hZopPw&59zo+*x$L!w z{uB)e_d*_DY&g^D0QjbOG>P8EPu@qVWN_qg@?|H^>ewSxh?t~#XfEUw!y~4BRUOb_ zCu_8J68{PHlY-_g|bhWjCWrX=wKCVqAZqI6P&3|Vz@ z=@1Us1q{Ju4OPiyotn$KS-7m_aF)xuS-7nAKrZX13odItkjuL1g3DSDY7x*;}$B{Yz;1;iU`8ccv&sgWo)ejaBKg3zfr( zuI=<=NS$6@C;wb2Rdgtxts`i_>{G=ULjaE#%}2K}ohIgv(d$q;%JX(YW1>?csQ2?#Aq{xu+~9u~V)F-k{L=eFn9+2}y8A z{iF7_vz;^b=E)7A!s^l($8_C%;mrna0wBdxAfD6k$wiB&c3a{i5UJ2+poM0^~M>~MT zWzU|=oj=BU5~S(v!g|%_EzvxBj6Qwm&#!*`$*I`XLjYWzTq0>s>=) zs}ZS>3<9^nVBT4{fD#)+Vk5EIjF;FYQ0@TvlXja1W=8bkYve7#IUC+t8$Q?f$Vy6u%|X@?;R&2H>!InKxNFVU8gsmA8q*jwn9 z&5jnDu;gMrrYF{kBJ`+5(Q5%a)jM!Lc#dB%G5HCx`zF6*?TIJFP?fsCQ4kR&@(!6t zpIm*58%6HHFRG4;a3h>c>)sR-{b}?Wn3m~kS)QVqeEe4vsP%Ad_M+Lk7aegz&;oyVnH zna*&oXhNWr(G?e67Kg+ne#>ys4YP5e&*G^$UUW5(fM-=?qB8EXF2D^K^{?g zkcZ9w7y#b#wxgmDuCRn|nF@uT%=L(m+?`)W8QH~74lMOOV`#X;?8;R84N<64-sR8s zK?tP`Thay32yVQuQYP2sE1C z5(LUdviu9++|BAxal?F5RHrGD4&Wp=?m*+CENaHAwcWF|S(jvkmTAXfQU_XrU^Len z75#F(!EadBcE7OF5lmtIBcx64aMWD{CiszM1@FQ9r`y>Bs3)~d2}b~-}Rw#go__sUFWV>n6L_!&b09TcWnkb3w4jO9#;HbcX3{Y>krhS&Lz!@|ejvaBGyHXEI$^7?3p<1<>ik7gM8jZ}K+H~q%&qMcxvlLofLSXAnOQ~E-tG4P zjJHs=%b;P2TzE`m5&2yPLciQxV2mg+{>IxhG6k2@?sEI=|BZ)+^07raSwi4aT4uFd;wh zJtQF#9Z;}5CLmyi@;GXr1k;DiLC%`jEHXzP?i8$7j*6+Woxxm{?VQk4neBYFpU6TO znINMlmZdlXs#WlDqQKZ8N~zQq{IBjd&T$Yur+U7DS5js#=t7BMo|Lbj1 zQk=i@SUcxO^NF^|u#K2tv+lpFJzjSICAzo9SZO&fj zpnBbCcXkI-HOka#0OPC%A`zC|ZxSQ5oJ6z&Ej8sNhD)7b#K-XTR39X^kNBlC#BoVy zclZC8)}6BDS$CJwu%*rGGslI2@@Bh|<#454O>y-GyV}6j>+DKP^-^BlOyHOXwK%z3 z07C$${3x;q-OdI{XE^Rb;5>Bj*}(BY2pkWD(yBcWGHC)3$7#j`Aw=cP^~ORBRqzI3 zLP)6rbNqyi@rcQK8=huf#J{h1w3(M2HDIj<8ZSaKo z@BY#s`7?|XiEliQSjaia)Z7Il$ii!^Cm}{Gk4B`tG4A_O7J4u3Bf7=4Qu9M|I+0|j zI`fJyQWhA^#8cPLgszDrdDBLmHH;?eJdsp-Du;<6P!yHfK)2>s!@|Y3k)hitj`Z=v zlu&{d`y-DU@GtZ@e)O7T)1>Q(u{@YJO5RK|#GH+9mwU>M+y`qjMPX z1&p7nel4I=rS2Rkn1vy01=EnvtR$1ttN0~ZT|Yg-Qas-j5!u|PE7Vo*&uDK=r+CoX zn&$jSK5Ov^v1o6Y6=SkR%*xp8{=@@__J2XZpdG@FEG}x=>0g2qHk^~9j!4AAX|~44 zlgc$PFHP`}(JVY9$EkU3R9Wnh#NuhQiDHMvPhP%nVM4B*e&V#=jy}PKQvpf5;D0)RCK$1B$g4_39P29=X zTTlbpCifzr9e>~tU-lnZcum1?KE>vpbonWBdJ=bHc^V|->J9aotbWo{^c!^$WvfZz3 zyx2Pj@p|G#ykz^wjg^sK$|E^eIr{=g`Dm9Y7OGTWga>jfdC)j7!+$8i936;)pkFDH zAY~nBq=*fghB#d0oWwy3v_VhGd}QUvIWZ~*l=fa>%)pk5s>qk=-7HO=)zBh>*Uf-} zHgi5#&?FI-cUkj;HglO?yq7YXc^)a?U~D&W{4BJ!%LTb;St*0o-s{ z8~g+a*;SyQ$^J+(4Z%@m&O0T@HI?&Q(Ht~9ImoD3Xy;#y|!>Asf25R9-`^b>T6G zt>=+k;7><7)$kX^uw?A9Jn@|*9$EYtvXB4;Y@U^SEXhA$gMns-kcA^wuKY#Ff&-YE z87+w*dwOo)@Z|UZHLYEwfC9#vIr=qWT5p>dL~&;GN--btSkUf7r${F=RqL)UiaKvC z?L+f2d;5?vN%q(o+GZana&Vh{n0Wh;PlnwrX(fNN*@u$h-agco3`4?Rvf6TPan3(T zbt!36p z)wU@bRruo>lAH8(dLdOLK+)g@ERzHi% zkT=#lA*Qh&kzF%%j75w8cpXx0>O;Ez?N576a_9wircw4E>9`-{wzyNpiv0}AN_(^%H3@EWN6P4{Ys&;-Zz?W zAG{^ro?=3!-e`7EhF25{INbM|0moSnk338#lpT4 zfx>UJxCE#=y+Gl6%Ls+()wl^+xuSSj0cggvg{}b+qa2Aq>JpqNKb4n? zzUp48#MB3z!Q|`M2~S^+?A(vU{aG~U)+tvgaS!N{jIx;~_9~+!6uc?I=tWD+&=Sji zP52u>Bn;{$ydF4EZ_qL>yv}Qtk-I+W zeJd+=Oz^OB6hs$~LF?2{Y!k_4hAm{r8~IFN94L=R{lvw-hSf6cq;VR7RDTkcR7a+d zknb0%tV&?Xsx=xJ%Ay?voVtIeP)G89UM;4?BGAHF#nyw2{0Kx4Ap(nz9(7V3PC0!w zNoPeSyZ7kZNgUeDx052=r#y@2VEk0WrAo{**ND-!X)5YS!CU}55G7LnBdRYCcL{lx zQkDG@C4@j*LJqVj$$mt2aD}=6EZ(8b+QYRRn4EeXERB~K5NAvdDf-V zcD@GEH2Nlpg_-6HbDY8UxzBy}Pagirj~)L^)Y%b%ZUy~)PtkdwWlFZM_QB^=$A0p3=H*x>-CbP zy14R3)Q>pDqFOfQ9p zF@530RoD|Z{(x0;tSx3dFd%u=<#-Q#PaJC(=l_`736+Ds5kK$P`1! z1V9P^)V*@Mz8`gs51SapR$b!C^1QC~K^X#1a_I3^YOy%5%>~VnDJTjv`@3%@J1Ykj()KVwjp?j*3NSmI2*DwH zF59uoax&RNFycti;>*uqp`DCc}gfuP9PBf;m>Aew4IK^=%51o8;fP@Cw5JDshL=iT8mUzQGLlwS` zsa#82(WV3~H;Av`@QqxcDv^t=Ge1doTr%R0vz3{|L^3PDZ**)mAti&qplr4coGxW+ zr-4y6DM1CvDoYdUs600>-TO7|e#pNh7X6a0f zQZ8KsAB;jDTEk)f86j@*0)2Ew7(&$J(}zGt$5Rjc#dSc|0gR1CnxFzd@G{Om^y|fVVYHLXNb=H^^RWE|m+osQ5?%#FGY03Tjs4ltR z3;i1`8T$a9Xe{1CE$4%h6b+vv`iXEm^FvH0ou;t0DU!YilYLa?be4~Jtce)pa22T~ z#qme>2EISUuAgukfNUkMU@Q_=j>FfPbBg z9(G0fhmGPPu1^@gf7o=RmvFEk;v4W8(tvY^?Q~--cTyoFBsvI*yyM<^>s^mTD=#cRWIXVr1n!0C^QqmbR%bek7Lt;I zM0>C&3nDOAsQVPMmbOF3&Fvh?ONH1YAA}<+l{%)f*Mia}&=hLGW#LFg);RrBLB~i* zf$$s3%`Jz9AE`@939a>H7>29bVj~?z z`hxc*YxR-tL$Le@>%I}vwegLJAgt-Q_iDxxOgEsFHHSv9;%6BjzR52<7!wthG$!0I zb65zkmdcjkV#bt6J3n0u^6Yh--zJ(XmO$~F$KzA2X812oo6V%SssCoWX%No9CIn1= z_}YU4Cr@*)-@nr@7T)LdYe3A;6B~k!4S0a0!6r|*SIo|55f{r-fKaOAy`ULoxf!{* z9HTOqO_@@GDk!AzT}AbjnC1@tEEYDDtW3A2($&cNQKnmLW~AzhnwW-k&Zb98065Hl zV4U8}zi834R)}nE;+%WjWPNc%a%PnIGz_b-@7!IH1))dXioGalY z%MGpGXSw0YP)1TpclHO?qS=lCkxmi`4CU*Y((Mj@$omjT?aP}2RlsV+h~Y3!M#p_X zTfFPLQ7v8T+9NZhP0v3P?I#8%OIvAofJA(iq*4Z_3p>m$bHDQw@Xl{=KXn^FM`QQI z`}qB(w099R%Y<=K>$`a{<(|>cAlP5+m=H_p1*tSDo zoaGom9v=Iz_V8Cer!(od?^3}%AMDJzojbUiKf%?p@3!jm^X!EA;O-r}w8SZT+qOCP zJBMGtUD?-g&gF(>mV932_Aw<@Zl9*J%EVw|pOG#hh6n&XcP}oOs_@n4$C4_4M475F!--Mn5#07! zXN&+zZ_COV^T-8PEFOJq0?#6XalkbulddDOI6-%ukM3ZJDT$*qh_xTu@=Gu*wAQho zWJh9XzVXfT@(+lbnZewFWrM2iC)pwnWP@^UNUD)+)vR;%mik za-F3mu2f`O-OgIGY%6N)?A$)&`zvD?`2K*Z1x)CznYh1lb1W>46GTLdooz(RO^8=K zp?F1G@d}W-9<8(H76EEi)WqqcibLD$H~;+uky}uJ#Py1545Fpt+fhYU{V^$IQfn7= z5NR6#SX3%cqn^qm-xp7ODi3?#mC6J1Ln;ryZRm%e9`wTVO+J-}E7Vw}@-%Xe51!&@ zRH;1LI^`E2Z? z!tD03sIbrCXQAR62`QVn=7PZRJ5U_H&r`KI*jfl2XkJHL=f9RdipI29` z>5$_t;)P)SEOkCNj8V^9d5aFm^8X1DdxrK8EQ8y}TN9OAb*U>KNoX54=_b?3w1R7s zIGP6F+?Ma4C%5ak#S=J#Z69KYoj1npV1}!5mbBhW<%S*^jADBuFYPdXJI;*tif_!) zheo}NQX08Gj6bOz#!s%YYWlPl{xE)3Tyz*e@6_Wzozm?(ieAhqAu#uj zJWsuE6}UE3vx4BVbK~;%i-loc>RU(TSSlYKfTm+W{c8RvS>NU7$I+uf6Z|PgFNC-h-Y@G{z_}&}?J`>aq zve1`(y#kMbDFYe}6Ra{F6Q@nb1Y!U>j1moMZoE+iG#b`~!Y_Cp71YotRxt}23TVjD z>Oe`}LM^r4v6>Wp@c|8_Ev-QU6vGwAK#(W{dO#7fxJ$xe{fNsFOhYt}z;T>L;LO81 zu>`ESRr5h3LN6smm=qA&=xe3H#G!}Fr*0@$*^1&czs68LTOM< zk;l%ZJ~HO>*hNjsV>fj`#^dOmveocj=BvC~(($G?wBb!ja3q=`cv=vRC!2M?Ug4I$ zOe#!An;1+?RTL575FHqD`+Tvw?fGz5o*lJ_orhWZnMT{zwEz5{MF$~5?JA&m(v5I% z!K(FjH09f+=H|f8kk=h|r2esL?O%?*BEuS|tJi4QKB$k%j$*;%Vq?ASyLgvoTi8v`-~7^im`6yAZZbRTW4%18W3K!Cuv*3xO~1pXlXhwL$__Q5m9iIHZq%;)`v zU2w9y2dw^=>AUY;-X0R^r%{}M4{L?yws)H|(EeDrqo4Be<$Re0glPd90UX1C!`m)L zJ0F?R*FhIlmqQ>_FFL3039X?xiX!X<226ye&6fi#3p~MwN!|42ySiQ3OsNhsw1Um_ zEyXBxKT1dmvy&AoKRJgCmz~(&n>HYzOff^gw1k*a_JDZQ9Ai`kZgkjZ>aN`B4(+?7 zYlr-$Tz*^E&T`=RZr{Is0jllx?sRXtgzEcVQ^4uJ0$^RYG<@}4K*la)KYT0pW_s;` zMSbD>Rv^@e9WAn~8=WwVN9&*_)zpFv^(Go-f{yIZoBMV2ZDc1Ec+xflO*)%p(?Kdk z`8!X>Ui%>9S6L;Nv7^&`ST&ps1le8~u_;tX9{9{VX(!tA%_b#K>BU%|6`?gj+5kh8 zk0s`oWIdm@R~$~^zmo0`r;r9Y1v<2rIFTaPTv`&}n0mH$%C_q*+8*XaJLh%Wh5uIi z(k&|MIVrZIRuTVK>$xYU4uH2Yt<;S(UC7^N2{ zo%bih8#074YYtq=khM4O{=x%to0drf5=1scUwqdR#mf9all18}U|st#Km>FkYyzRu zLxdgMrtlPIK=0++l+o@-$+V^I3~bYR>$VyYibGhFX*})6gGeF0ef&}@b zJ1&!gsTejXpd=OEu^${PC{&cP+@&GtJO_KGC*I{x>M>8Cp!RH z?nks?wm5H0V+(n>6EX`e30Gr#=C7);X+*h=at&90Woo>OMtQ#9!4Cveew4x^IOFWJ za^Y)o#V9bH0n`YsLU4gufA&g+*-LY%l@~#0&YaW1*dP`|lm3a%Lg$5)CxLWsZw|4) zFw1_ftG~muJ`yL@ajPsX($$zTxnOSm>j$E+&jFi$*4@Otu~6U;+43`U1MWkP1I~0> zDkmcOm*va(0@JtAA22TenY*5sry_r7dhzltynIxL&LGuY*)^^S_Yb^X@A@S7%XWbsR;`75?`AOckxz}O4IwnN?p&)r}%tLr7btowuNzJ%l^AmI>ekbw4DzAahzJ5_Ca`**ARCEkWB zlvt3z@BwPqC*ZfZ4=63)uCRcHOzw<_+z8dIc0Wfywm#MjuMZ)!TpWvL!mka#3jGxLwIPY?*G5*X z4MZNG+1ju@EOtoFTAo(4ud6m8#W}xZg1;g#bpuW+v5|SThD+esu6L*CZvI%D{ zn~D3$ns41pTR0Z6U111Y%kqV!LP$@0-u^Moy`r(+9?=<}$WxAp)N>yIfE}^6;YA=C zt+qUSqV6pFig?p-QOheHK;ySKd%~aJ3NUDOZ}6Gl3RQUcw4R2}p0FfYJ(XLIWWGP| zEKu}7>~jCC(Sa7P)PklL5K;}ivh&F2PzOtO*(;LE>3dp2CWcF0?hx^m}QGx4Opxp0~Wa% zfaPj=U|afbO~CBaNOX73sR3)VDpD9y`){d{0=&$Vg4QBRKm9~qP+5Z&McQCRvIbbW zS{~L0I!OWRqk?t()L>mqvQP5%Vv?2q750uB$-?_s?2T&|V_x2u?p3YWJ1bdJVK#q!;*@U{=?F59?g(90GpR=PstSTQRU zLodNn6|!{1z!&Q&9Qf;pti2p}NX76qc`#mQQGWb5mM15j4{Zc0RtcGu((zLiSWXT= zpXytdnbg#EruX68*Baos#*dyXbxR#&jKk-MXQ|AF!*eBlW8&#wEK_e&+` za0)VPO-Tr)eE3dl1J!{9qN-;R^qJ_nwL;GqiFP>5NGv!mKN6GsxG!4+!VzcbA85v42!{(UEIT1j6Shsx+qCyu@rrFS-Xo1w+P*%`q6RG6Kv&NCK%d;0 zymG*opCmzfE)|m>DW}0UM=8ql1PhX*-=zg8$jv9PK=qv^JaYD6hK+(v2!cvtw=*DWYVsnW4K`FR;| zqHM!2hdh}`HTw6KwBDArz;esu@`FbBs_bCGU|#$EV(zYghTrK}gKeCSiNJMtKZ9_v zGmS4d4GZnf>pZ|>lNq+Lg_Fa8cq#3Tp@w6_m|YPPD%%tBu*xQ8AeL>a!Y`hnZ+VDn zssbLPWGRo6nK6)$yH77cKoE&L%JtXyNR&@Bh6h+@P>`^O>%&1wiuP&4zFQ+W^`l}6 z)zkgG9DgVf8u_um`Ra~5uxEte%Q9Nf2`h?&Q-csl$XwagCx`c-oJh_<2Wc8TH zgcO2==W02TiNj8@CqwwV1nODCbf&^El6e((hl@{LW3`!hxmFCGUoH1A76rqK!Jr0m z>&F67ET;gtc=_?zo~C6bX{VT-A*|*lKfuS=7SB5qQJ@OAbRK4X-huS=G&_(GruIvMbiq

kXi{0ZG~A~VNO?`ErlnRopE{miwP{|bVX+SDCZ43X{EJEdsb%q z$9gHSoCmr78Z+BJD|}C)%Hp3gtr#i5q_C?JMX>?~9L zYi8TB6+7TQ@R_JHpDZCL3T^;cI#V z-naVY3CWdF%TH+@7BE(pwN1n0DwCO4TNh^}yYPNKMZ&x+bk3jNAQ0vNE(0@tp?K6}dxeRHX1)B6R z(~O^c2+m`lbw>2euEwI7AAR)qAN%7U`Otq^-C@)Hr|_iYPh$?C>J5t*H~=@?@s>?QmO(a5}RLI7=j;<{`Ed!*sJf zRxzRm{UkH<$?L_6X4(fxp~F|>H^u-K#bS!uhCsK78uvxzUL+6|U)P$C^N39drih&m z+}PHq0QOb@MH~N0K`qX07}hkIo-seXqv6+T>ss@fEi<36|D`vC*nCRPK_UGlokr_E z1KUAl43yJgEmqq>f=aw(kI#8C$VwdwiRH$RrI5(>PN{0;CYjFL;(Go9JQ{1gXU-qF ztdtFWAI?MX4dSPCJJS(*0%={AYK3U-$J`P?9!}XhX)jvyUEU5a2Ij#+j?5B*CfXNYpta|*f zn3k9Z+Q7#O$G;~B8Ap9bL~C%OgK<2b?6AfYDsQd1vlQ@6_$G){D`iQOscYJkud64% zB?|m$S~@{Iqe)Bs3m>hPV^18nzN=c}E z(3j#jHZM8JG(>*X#IL>M6IC_{Uh!Y$zRb}0Xmyj!+=}b=D6ZEHf<)fk*gdKqh}z%~ zbq8=}VNJ@uUZYvv6s?s4Z5ls7S$=ZiHR%n4!^fP>8v3UKldccFG?)K;iR?MHRDlg9 zk56PMHwcxJZ)qrMtBHI^CQ%Yb~!;hO-hr&Acv(D!|+@Xh}K z2~Gb-3*}!#LemmBm8i|DkLfH-Xu|4x@^9~W{Lt_JyFbJF=FHxRUn(vq#d%3D86q>{@Ej3#14Ih0h+*Aj!-Lo&tr zlpjWd7YWPCUf-6-xrlkH%$p^rTc){VH1qpFgLv1?iCmoVTfLK{jGhc8kiEfn}*kQSsgv-p+f= z(Af!oK^nUrBnzCX#%Pz@|LDVt>fy|~T4UJ3a;{eIh+nSH=j?2KmM#?` z!0o==$vgKI2>@jX+&zr6>Zk6kVv{$*&Z4aHoUvyb>CbIk34Fd469jx;8bTrc{h3|G zH*%3AN^O6prsS<=k;wmsRZ;*5?z5Rvs>kA!mzZdFFLbUbl(E8ocQheC zk8%odvMkg7RN1~)Z907fvCp04eXm$OpY|tDwf!G7_#KWbcj&#W4*e0$CKh)?18}$T zgaP4pz1^2lVjfW|vD521R%CE9O$nY^aLSHyX(AW7Tf=MnF5_uz=VL|aJ0GeoC6LF4 zqnwFT-^#o|p;Hi&#$$Mc0Iy4$X6{h5qhX?{1nWi%Yj-z@gf4xEyI1h(cmE7v8v z)G}a7-&_(1;!qU`3CSukQLh4~j#G@xyvfOo_SH$HB>3gqb&;n6G-T8MtSe7tPiFp% zgb=7xxK3ml4I1WL)o}={FUC6}@|){-QjJ+h^J?%?o!Px-!JdXq9mV=m{9eAMNa{83 z*s;&=P-G{v zXY|RTuz1Tk;3(Ut27y>r=q#ehaH)jJ0Z^K4LRaL5HmI8e~aN9OX53d}hy$Bqf!+KGcOS&7%=YR{U=Si_@$fMV);eZ@F&=oH2Nm|P% z^>ppcwA!K(abtxQEKXzSG$J>jDGnWXE(zP&8JF+{eTwG{Scm%2!Lf`Nz5|5Det?k3 zW)cFv&<&@ZlJ3xXFj?@C0W~|IC24thiorRaza z1$7XxVHp)GpyS&6xT3Dh|94%wDUD%6;GG%#fKQC(X@n(l&fa zi?#0Xk6O;!hFYexO9w;sl^+G+t(n$3*3Kd9V(5a>S8^CRBC_(M=9b@Dk)La(tCAm< zqYGl=IPqVkGbUKa!^Y6^VA zSi;AAGWaNx!%mSQ{^}qOEUj8tUQ%A`tMXTrmmJA+3DaDZ78F@lN=r&BN^45u_%@B^ z#b!|2QrM4bfAz9Vwyv@yQ}(RdzhuthKnZ(S;;-_Pa+$AkaiF?vVdWC$Kgjm-6BD)% zcDf%znNq$qP&&$A6PQr3*uSv6w8&pt%K2>6sQLctqn4L*(>QH%pkiL_ z{KGRgZ2y$|L5vcN4`)ez!<(J_9CquJC$>}XywCm(?H7?chP@5)glf11DWwh$t zzWE`;=15EFklNJkv^UJ~Z>A66K<;v+X}gl+AcM4nj+F9>(!f$*WnHbWvdlNPvaX_( z##zhBSQx1AmDl)ubD1P=n2|9yTaSN zJurJv_TcQ??7Zy!>>)W>IoUZmIRkSBYhd=koPh%e4jMRkVD7-Y zf%yZ649XgmJt$|;z(Ios4IY#`C~r{ypdo{^24@e>89Z?CpuvL&=MK&roIiL-ZdPt~ zZcgsN+(Eg6b8~a^a`SVCprW)fV}$8x zGQM$r@%bk-&fk!~Ve52DYkdCNhFZZ&6gHL3lJ#McjDLuJEO541WLO*u46h>u*@M`N^ktys-PlkD{L*vN_W; zdS?&HD;RqGgo$UE*Dx{k;nh78rcRrE%9(R6zvkM_TOQl?>O6Mb)M+Q5dit3cT=?jY$Xg$L|HIGKHP_bG-P~h9=KUKV z+qQGhJNs@Mam&iAYdgOYdG*lLX{Vm%@it5AnfdK^6_t6%pD=RFb=S|BSGQ~TYp?Hp z_p`qrviat8U9r!(Vzjrt)1AKj!KU@=U7eGbw|BJl8qQ4TAg9M+c-)@!?A; zvqN%{!|U)k>@=NJr^}JzHk!6{P4%?*%=Xybt(p`$M>+aC3}?E#S!#i^WA{0}Mb5Lj z*Y9$z*yL#EUh#+H6i=(9Hc2f~Tcn=lPIk9*pW^B38k^jY-?=dy*(v>;?c6Dj`Ujbm znLWi(Z+V9~nmLAf^1XdsD-NZ%@n)v?cXVmirCI$|&K0+`O=)>q(3R;L>ajO%lT`mq z*V@$jx7($<>JPc<_oe=Lrz0K-S38%#*Ta+Qa@DU%TmG}h@bz&s^=fDRGmiF-W=(8vgOAIc zc9+Lv_j;4;$^0^UQ)ilyZqINvPj6wgw70gmZPLNj(c8^9%elb5(XrLO$NsuKlKNKC z+xB&_N!a`R4_ec=NeHaa^0p>02Ia-@)TePHE9PcSyn7 zb$j1U%De9RwVvdm!^_IAy`gf>wr{^Xb#Cl$hi)L(z37>ua-Ju;XIDN-;@6l^ue!=*OlZ$4|I!P*BaY>+T zLCw}DOD-I6-F`npCtoA#-H zz}?N+&DqO4C}rxX<#|c1lD%z)jv3-8@g`-p;sF%xUB-xf=OkCAGue~m8R6^6k4EJ> z3S8|x&Qwp)guH=G26_f~lb83HKDC#(Ppfu4I<#n=G?fISo3!;LyT^NbCe@{!P}s*k z)RpW$(QUZW9IpB+=XM(JO|D;aX4f$($?hi23*5=M{hY1qpBPp;BXxXI^4Kx$$9rcq znXue5Ho2o?+=M(wQ*W|+h$ne@Zrl3DjAl7aF1o#}E~P$v#pIGEX6Ci8uNb%bi4{XU zeVnJednAud?&WH}V$*4XNzNgj^bt}AZaL^R-|mxi&u7aA_IIQ^z00q<+_}Kj#F6Al zyP>$(K)@q zWtjWxf9XBhne4P*lshdhmpeJx?WI?(f2n)2>Heo3bYn+7 zr_^8TZ|I|MYMN&2Mb|uuBCm1JCYEbj+|!ANQx3mh(i{0Xbyag}EA^em9B!bPo_Mb+ z*OX1X*E*y5Dtkt2U)QHQ7k53cpD)XmRXnKWs{MlxTE%&(YgXlT`h9<%?eF_X{bBe= z*L%8*H)up!m2Lf3K{PQ+^yn6m?pC6e2`%nII!}e|Y_r?il^)%c@ zig6rwFXQ+fB!gnN+qOIHP9w=~7{7b=7%n5bKNIZy&**2QxRX;1=1*>!W(2%Ovf=E) z#7^x`ODRZUS))yxzK(tdJ)^gGL7uG7OE>39=F^jAMuFkvv&9zncH|gBr{c8JsT!U5 zY&J#gGRWG(-ij`#K$0=9hp*dpGLGjXpB%a&*2l>;oHDkf&O`0Dx3G7x53x6_xuY@O zpaVBfHSC_0q`5pwG08C86YcHU62r)A%FftbDMq&>qs+;UbFb9a?sTN_TvEH;%@&@? z+Z>ytqrDUV8(}v*Uc;W^V4?o5fqcl^)mZFs+LH{o<9)lG{Dw!?YWKR6?M6?-+d12r zWiT`U@5N7y`q(|gka>jXyJXIeR&r z#(j2f*zC8aWIN9_a+~$$=q5X|$u8?mHgX)@T!wczU!WM2M4LC}@K+`V$A6XK@V4Y! zI*jffhwFK-9JtoTOzHzO*}rwV`EPssEdJ!#u%9JIo+$wbmBwY`*ZXWzWsK{{;xv58 zz1(){Pmo|u8h4aeU8!ssg-lQhkxTYpKZN>@doOiaKsj^0= zZGgSC&5@L5_jGaw9kx7Yj@M{vv~n5ESZBJf&&5rualF&!S>)wb(CAcL|Gmw&iW}8c z&G)>9#$|*3Z{`#G2yfS?l+=u>D@#y2~M78=G@=8`^i<`qTsMSG76N zVMK+$qb;(!*?#vAdm3Xlb5Wt%CtFQZZ?KtX&2wFk_tj@U)TcbJ%&K_o0 zx?icR$zv2!5$F_*4srf0yF*ROKT{(kng#r-?|UOd3|cVF&Zhl+FmFb?Dz$zAfgH93&CzNvpm zW}DSRvN{|X_fw}Xlj&=x(bpE;xpj4sZIA!N!0W3|w7u75I{oYn`q^3ZvnSEdp5(Jt z%>LduG@B2@JhsDq==hW@={xBnr8yke(09^LwV})9+`0NXhI#w6F?@-=r}TDIS*g)R z@_*ESa$KeVbR^S%4rSR?mhAPXeJH1gUR3&2=|_)bk-n6(Ol|TSC(@s$dgsccY5xbk z=|A?RNd~vdM%};cNwcIMr6&HfUUX$bANs-woAG=Xo8xMFN1MA`+AY89Rb=N~72E#j zJz!5`)ZzXwGoknEL)H3L4ja9mEiFwxx@zd#{%x=3F^>N?JsKy#Xk|1_=+!u)G$uLR zhW#tf?7!~Ye&($If9T!l+vwSxCrVEz{TWTi>GFF2WuL|p+NXJJ+MgBDjQqC3=te#% z;9sHS!_~r=ViYK`6`Gr5%4kq(D-0^efPk&gQj7&bvd_H_Oc@%OZS4|$p z3{XR!hb7NUP)i=gEKo-t#Ytc>c@(q367ncc2207KI0Y;tkK$Bt4tW&Y!G7{QB6*$# z2gsv%4tz`=#q;13@+iU}N*=`y@F{r|FMz+0N3j!pMjpj3@Hu%xl4m#gf;@^B!I$Jw zyac`?kK$$UHF*@TfN#j7colq09>pH;9eEV5f$z!lsN{Ja{6HSX8{kLsDBc7=kw*~$ zG4d$h0zZ>S@izD?c@%rWLGmc%Q~6)Xvqkc}2Yw@u;(hQtc@!UjzmZ4rA^3wlijTnG zZG~?s_-u+BfWoMNHpsOC#Z+_zHh#gr(0oj$xe?c!!l25Fl8~iJMky$yN<~djSk)9Y zLlIRPl79w{sxnY>6jQZ8Es^=Sy^*ErgZiS7svqi)!m0s?8`Hvws8tp!=lFWKMXPX zS*nF-5elg)P$dehs?gaeqN+wUD5|PObttAU?wo3agf*8FtdCR-lWR7FC((ViZ$df@U(OxlJ-$iY{YXP<1&viD{PV3N#yqR9B*_ zB%kUU^dX9JC)Nw5aM%RE=V)yHOo7pOt*8&}xhV`%p-=2GvVG)%|F#hX$+r=0Vp>qO8C}6NOZ70i5`|Qcpb&G0Rga=Al27#*x+jf$#XL9a3`rrLvE zLuOd=y^h{MLDicmf-Kcr=xr2Iy@U3muLaud#Z>#z0c7rwd>^Av zP*4>`pCU{37xWnlsXj+vps?yo^c9MzzDD1msOnqv9g3-bKtCb#1aXY~6jJ?ywxO`+WRKKC$QB?Ie^aqNm{*Dfzi+7Sw{`i2m#DzftH_%)urMjq` zi0OqPl^c0bSmi}YD56S6DJZH+MNLpl)f6>D<}S&QhWNw6!k{VxHAj}J1!{>xs#d5q z3ai?nwkV=%huWj4ssrkXVyaH4GctEeJ|DV^oeiqGqT`rmsk))=D5UCvdZMt3Ka8>U zMiEsX)E7ln{ZM}tQw>0w$b3=qWua^oROO(7$Wj%cKtJ-ShN59i3#*Ptx3QfO)o@hC zoKaOF8p)h7)hJZYoaRfCZ#24G@~Ot4t0bRlEEAm|H=u`6Sald+2=>Q+ zMUzli)elWZ5mkRQ1w~Z@&{PytWuj@wd{^=nBR>kN=Asg0sY+1*g;Zr|9tx}GqjD5c zorM;lsA?fvgkq`+REf;@BwrOe8wFL>s0LZ8T2zNZs>Nst3agf)WhkOL2c3(es`JqK zD5kmqU5L#0C0{*Sj)JNy(3QwiU4^bjA=NeLS`=1YhptBv)eY!I6jcS$O(>?i8Qp@+ z4tOLYgj6NOZFp}SF7wF<395!F5DUKCYX=spxvtwHxA^FztE7Og`; z)dOffvQ!VEhfqkh0c}KK)h4tVMN|)?M^IE1LXV=DY72S{nIB2M$I%lgsCp7Tg)G%p z^fU^oobBC=F3p_fre^$L0w zg;lSi*HJ|E26_`kRT1qA;ww2iZ|XbuV(DsLDc46jR-YT*&-N@~uI9 z*`zS2x*vIvrCN);D5P44l2BOn07^y?)q0eIqN)c`DvGHdLQRnQwdC7?nxdd;BWi{$ z)h3jNLaNOu9feg7qYM;LJ%XB}s49e7pqT1W)DoHBNWLwo6$+{zL#>gedK|SuA=MM8 zEefliMD0*S^%QE4qN=T^1B$7hMjes)t>k+KbwWYaHq;qes_n>!LaJv`7Zg@Khq|JO z>Une=imJk>8;YrRpzg^0PV((UJyB5g3hIw6)vD&Sxb@^ytwv{~u<9OEjUuXhQ4NZ! zEL4kPs{2qKGQXF6YtUj8RNar3AWO9tEkz;KIA2}*Px(k8@d)* zs_p1H6jD8lu18_jbLa*XQ9X}tL{U{31yM}31KotonB;o_-Hd{&o#+;1sdk~2D5Tnr zZbf0$i|95KQN4t2M^V+w=nfQ9y@KvU=FgJvRdg2$s`j9}k)?VKtwJHy>u5C!tKLBO zpor>CbT5jkBFI89)m!L3Wd2q1y^YqOpz0lTKeAMN(OMKzy^GeNu0;vOmz!-2bsT1zLjV%3aV~J?;=Ze z8+s3gRJWt|QCM{c`T#{#ccKqbRCO2n2*p%)qkYKyo8()C_M@O`H9CMS)jjBA6jI%b zK0#rXg`y~;x(|JdqN+9MFDRzEAAN?*KP2B;^f?Nu)}b$urFsB;i9)LN=qnUfJ&3+W z5!FNJ8x&P-K;NR6Y9sm%nSYmjo6z?tsM?HvK$hxZ^dkzX9zj2$uquRND5824{fwfj zE$FW(rg{whg3Lpb?{Rbx1yxU=Uy-GH68(lks;AK(D6D!0{T)SA+t4BOsmRrd8>>#F z(@A#ZKxX~3q;n#s7Y0>sc%L^0J!Gzyuf^cr*cpR1?u86jn_}Q&2=T z6-`4?RS`N7#Z=SL3}jv``DUV7D5yFK%|@2$WOND&sZK?wp|I+7bOws3&O~!iR8@@p zD5jc=N|1SpOUS%R^1X~+K|$54Xb-Yfuc6mbNc9GK6NOa~^cIS!-bU}BsA?~I z7sXWXq4$w_wdDH%eTagpkI+73srI7-D5UxreS*TODEbsdRDVIAp{VL}^aYBkzC>Rk z^BT$bHTnhxRo|lTkfr(_{eVKMAJI=JtcszZQAG7u^b3lr4x(RCO!XW39huikzQ3VA zP*C-EbO>20Z)ZL&=uAFU5;9O&m5l5tqDnyy6jh}nCyJ?>AQv*PlYC8)8wFL(kOx_+ zHYg2+RBcf@3ai?o3=~ndN6k@G)d96YF;z#@5}DUazFx?Of~wxA3$j#wP*)UE^+m^_ zu&N*Gh9au|s5^?P2B01&rpiP;k$HpU%STx#s2YN@k)@}scoEHoEIR0~iEimDc(QWR4yLIGsnB>5^( z849W@(L7|SE<^Pwq`Dj}M`6_!Xa$O>u0$81sOlFVc)!pb8WU1DoRVbud zhgPGo>H%~Qim2A3dr?&NAhJ+Q^$@xbnJXpV2DAnRRU6U$$WlFyHldK}3A7o7RZpTS z6j42e&PGwyR`dvpsdk}iWZo+IcB2{;RK195k)?VG)uE8;W%N7>tM;PTP(<}EdL2bo z@1eygrg|SOLFR3e?{l;t1yx_51ISW+iOxYG)mP|T6jptWK1C7LL9`S_RllNTD5m-i zorlcZCExGpd=ymu4gG{HRgW$Wf7uWcx%!|5q*L?-pOQ`I%K=lnkWE#Ls!&YjM`t7R z4#_qbRimJ)1l1r*Rf=j+NEJYJD6A?&i%~>14=q7a)qJ!R#Z={J88Yvbd}pC^P*Al1 zor^5hLUbMqsTQI0QCL-hE#Z+6mah|)8?=H#r7V&2u^FGPf8BIe$l@ApmOVtIP zh(fBaXgUh3jzcq0MAZ$=L{U|DGz-O4JV-~5mZ~>81%*_7(5Wb_ z>WfZ85mi5QI*O|Lqcc!UH2|H7%=;x@CYpnSs$%3vmTE3qz#jmHR3+&Aj^tC7isTOm zRrAn%mW!&&(OD>_T7VWJbFJiCgid6+psE6$jVx6q;@e$?AypNcj>4*HRKr>#syeh- z@~JLBCopGBbs-vo%yp8l9xZ3iplSuW2w5rTm9&|6VR2I4qg;ZH)MKMO6=?hfqwl0c}L)gOYC(+Khs# zhtVU*Qiaf?D5Tng9z$W(j-Ew9)pO{1WU0bv z2MVcPKs!-bwF~V=5!H+6B@|V?j9x)8)vIU^GB-%R*U;-IsComvi7ZtFy@f)mx6wN& ztlEp-MG@6|=zSDbeSkhhG1W(CA2K&ezWwL`3aUOvpCC*175W;5RNtU)QCRgO`Uyo; zBi7KY)U$Wl#2lTb)C8BIZ9)l@VMMN~!TL=;s` zM>9}NH51K3=4Q!v5}J*Is*}+v$Won(PD3Hp>F5j;R-K9Fpopp%`B79g7nPuxsuTr~ z`LN_GL-SBjH6N8DOLZ1nfI_N;Xb}pl>d|r(QLR81p{UA47o(W!5_BmtACY{Qq03QF zbp^T-S*okh)hML823?E7s_W48D5AOn-H4*9Ai4>~R5znrkQtJEE77eesJac^jx1Hz z`+56(KlxPMPb6g(#>RiAEtyH5!dUA=Owk4uw_Y(F7Dx zO+=GWR5ck*K{3@-G!2=LNxmXx; z87QVY6U{;9XbqTr@nNLc-E74Ub zsJa>jxE5Qg>(KQ~3#o2EH=?lWW^@aRs8*s|QB-vsx*f$-cc43w`IO|l3*C)^s#Rz; zvQ+n=dr?Sbq5Du+wFcdfBC54$9g3Phqz3aPfD zr%_n-4BCbws_p1m6jeQko<}iN7zL5}jO5#aR*P@~OI_k;qaVhen~0sv8=O!m93Q42r0F zps^^b>WRjon5q{VkIe0ouQ!^2f~r1fBC=F{(Iga7^+S_USk)g*K@rseG!;cvnP?h{ zsj^TJGM|-v+2}+RROO)Q$WjeNGf+r12+c%c)nGIWMO3-yBotNUq1h;=%10+7^Et^k z1f7C{sseN>x=-aaY`rLa?~ge>24QbuSjo?e*!l<~N`B$P)>jx+Itlv;V@iG+#MWPE zJ};}{_d;v~gh3@gfn&=QT1tK+h%XPokTQudTNqX*6XpmbN`7XD?+?JJk{^KL3j{Ex zY(h9#Xoe+kQ^H(fP}z(yPiQIA2=j#@Wjf&yVOW_#SRjljn-dNdMwKlHhY4fKmW0O( z%^i}r72$AUP}!RB1fiwmr#|>H0SqbIN+>0)Y)3d!7*Vz-93_k@I}nZ*#*`fi#|X_A zByT6evBIFTGvPR)rSuVw7lxEw2qy@`%C3YHg%Ra(gp-6(WjDgf!kDr<;S`~{Q}Xs8 zoGJ_|dlF6)TFOm?dxat8X2N%cVdcYw?+GKyM+n~+MwKDL4}>x0ql6y{&0Ugr3*kq? zpz<-oeL_q5IN^R_NcjZe0byAAB;m)xi1I1IPlQqBR>G(-rhJ<4Q=z$A@;*cO7hzDj zjqo#}rQA;VxiF-BmhcNlP`Q)v zJE5iAMfkliq})yTgD|Xok?==hMEMfoPr|73Wx|*+rhJ9)XQBC$F=F7rr!drzwWewqN zLQ7doc)Kv9tRuWb7*;MOyi*uaE+M>27*#GMyjvJkE+bqeG+&Xt=Mb(I29@U#-Yc|} z=M!4Okn#e;`-EZTg@kK_5oJB${lchnIpJDiOu2$^ozQ$$@?J#vfH0^u3D*lP<;8># z3PZ|E2p;?z!m#o>!pDRW<@JP*3!};#2%iwfltIF$gyw6K_a?%v!l1G~)v#@+@I6DBx}5M? zVMw`x@Ht^vc@g3B!idr&3=5;miwSoKW6DbiUl5wF%bG7G+$jtyFC*L~w3L?_rsTba@Eu`Lxsq_N&{Ez?_^vRdyp8ZZ zVOV)P;rqgf@(#iegi+<4gdYlH%DV_Z5}FaodpF@eVNkh>aKF$}t|mMn3@Ps+{8$)P z-b?t2Fru^wqr#~2KEh9hG36Sp{0C=@FQVJxs7n2FsvL=Vc3c(e0Nb+ zRY>R;MwKH8=L%!WQG_Kz^L?3lG-0VQs2oEW5L(Kygk{2zavb42VOTkyaK132oIqGE zj4CG*o+XSaClM|Xnjc8s$%G4qLFE*}MM6tCm9Rn>QcfeR6o!>WgjK?b@1@nQ*BvqCACgnJ}t6mGB&4 zOnDmNxkB?J$$L8CdBULb48rq;mhw!(3xpx%9Ks8QJB02p3|m(UUu~2%8HC3Pqe?qr zH(^ZaAnYzQ_sPso!XCn)(nZ))XenjQy@Vm9hp@LWtn?E05k{0rgnflkWinwuVN97q z*k5Swm%OQj1B5|k6T(cPrEE%=B@8K>5oQa+$~3|pVMLiuI8Yc>W)Kb%#+1zo2Mf&u zlD7q6t}v)3^AHK<^bPmb)s&wTC@7dxcyeZST}^FCW?5kH-0Xp+x%t^;IfDZM zc|^wCK+cd-o|I9NJJ?@ZQaU8NEO$si9s+dC!*gnCtNFd=Alo^f?Oaburo{amaVtts zN-D)k=|$;H=|lN%^B;NW1rJB4l3lN^ke`iiQIlqy#(J;fyL|ld#9^<*az5kJ#X6mF zG8mM$6q!f1QA%HmpGWW1`YS363dYPF?;BZNUOF$}D=aOorat)obkLb+RtNkwbySOg zGw96Yqy|;^<$)Oga?qJ4j2z{gB9Bx0mmA^Zkwa{=%si4u5dFKE3kr5!lE&Wht)tq8 zhsChC?LlIP90OTZQFUc)CAG;{dF9bO{YUbAr3EGP{nW)#m5Ub1 zFMKyV7wPa`#@Ad~CQp5sQR`n2@Xc80ubJ;FswCCX!=KH3^c)G|Nldndbq(9k8MG~J zjN@&+p_Px}+|mwf<_AiB{>GC!U(zq4Rruxxe1o#|p%wlT-QR_kho1~2t*hZ_M2*YE zR~%nWRdt}OeCdSvTtAUNiDP-?vWDY3fv2L#!>tN@w2Q?J57bK78X0Z#F}?|}V`}F& zRJp>E&fiO-Xg-k^c5O3cl{t9^(Np)Zm zjn3z9cqEBFhDM%rBu_!1BKmnU56=r>cWvg9G<}H4zVp*;lQ2I3pbs01x0m?qaZF1* zhSGN=IvY1ukx7ANGXnECK{bKK!zNGrVr=55nRMlq($g}&CNNK)DOQuXJ`P91vGpx( zIIPr4p6Mi&r)X)zgK-)&kZ^|X62Vd1HSn129CVBaV;Q%S_2u%KN6DuQp%hSLo4ge1 zFNYrEVU6P=jmsX7hf_|VjGz=!_$rVtJCfIU6Pi}l`1CZ|P)+<{Y6ZRlzL}h&vhr$a zn(`Q-cq7g7RaW~rz&_emXV!B<NWR?aNXVMid^7qfltl@z+fl__=Qth&; z0Mq3ecXA!t%5qY#o+!CkeB z%4z*Gxvi-wV=F7`s!IYqT54XPw&9t3b#v!5bVozHt{|o@ib-oB5VrvW7#fy&Hko^V8K zUZy8$9=AYsa|iHDs{Yx5l9Jq%4hfXx=M2o8tNlp-tO5B0vUO%|bs8Q-5r3fE z9+F7AOWeO>w=r>B8lQhyJUviTx3IRLpsr#`wZE!Y?_zn%oxj3YOdTwy=`O4b9O)+G zPy3=9()&?XC^`E1=Fx2(nT=lisCF!U_Uvk|Prjl_<7FKKd}A6rd0+8)4G$L_#;cq8 z5|5?aDS=uYPG=r}e8>Ln^Bj5hVEj_LaM^!*D|_TQlnqxMt#tA0O&K>0P1&Yn zkJ(gtpw_~LjZZ}5ZnO00^B6x5XRvI%9yau=$JRq%v7GD1dRU(L?6m^kJyciM@s^^b zlKU90K>V|E<9%XXh17$2mDRkFXnde!C0EeuhKDmYK9g(_SJ~=iM~+ea`dY?2n7WFZ zx+*SlRHn+R01r!)2WIL`m1GQ*(s=9Ss>kEd=SjH`@xnF<}N>7)Y{ z)hw&wjg{~0y2@I=FR-*EK*K1l2?SVs1r&eun0CTz~a%E~I)J+AoWG)g^u z3(FVrOtoHt0rLj`PAwoUq0DJ7Iqyt%1dbo6$yYb=-0rl(13o>o^ot!#?6 z;v<)yF_mTdwx*$-7B;ri$(-kzm6el^yhR$@*hU#Ug(f;#TIeWgp@Evw+C+J2Xq00P zH%eJ%Bu$b_rZ!25Wt^;8+M~n`)%Qecj$;~IW2@j*K0x&5qVW@-fm@ zCe#!rjyrCfg&ilx7r} zCiC{7$om-i%%?R)=9Ty7a!)SnlyNdm=9hKG+jo2785gG6Mp61x+EAKMez_pc_9bN> zpc<^Q*3CF{S0vY1jvnL#O}45IX)q*Gj!-|EwBpHg0I(B@#|C!J9a>`k@4SyIZ z14;vP>*mcVuPCeBT-DG)@n2(BQ-cmI)A9kXvfii z%D+h3FXCY;{khp$LLX?qAH=6*%am{KIs7tH4M&g zZ1%e&#O}N0eOu#@eQz7j&II?r(cZ`VWa5Z=w^S{uK04@X_7ggDcT+3h=F3C(7|xrg z@NVxd;wRVy`Qc}&YC3rMIb4Rj19|+Y#CvSgcK7({4U^=*_Ggnk4;LMA#)jl1PeI~{ z={)amS;B}`TfIr{{s|-6%*yci65NU>J*ND^`?XE1wpD)?cTPHz!0;KV-cfxLMwA=U zzP;lTSK;*x_iWEPGE*x?cppg^(YmK2!{+TH?}8f#W|20^@DKYwd@=8pW#7Z8vl91x zN79jb^|-f5t9P;gl^XWR4QET+Hgxzws<3sk2 zr2p!~AG3vZ#f>L=SL*)ws(H<33vKpusLRr)=n&t|HyBgHcG_opr6TE|8=8A7zfJUs zPViinaPXONxF@+|d|u9B+ms~F^20TdB|0Z1dCrzzuW`uU=SlL`Cd`pD%FfB~T$C`6 zER^zn4tFFT{(O7|&j7pt#D``#bxKVgdf_W$)E6`_a3;o86sASHiBQbY)LRut~Dpsn;az!{8@dq~4zBt?k{j6AsJZZ7sbc z5}e!4XlYC17ZPkIv%0}gwM^sp3T#!xxwogMUpjza9Furj+tj}DK~m$8dt!&wt@7sa zu+vj-W8V0XThubOLf(Kk4!JX0q$N+_`^d7cK#P>82FpW{`Mk_$B&A;iUIV={*lzcs z-1tJwTHeF$V-gw5hzYRB`vi;Nk z7q&l3*44QE^-(emgKHHO^9au>6JJE*F+wSVJ*mW@OF z;?S2g9ilqX`(bf>YC~uEkC&3+oRq%%bm~7`X6~)vy(bqux>1|GHyubu{!8?D+!tk^ z>^3qs@tM&^?nJUb@)#N27uakq1m}VqS92F8xDX`y_$@DPz_Po7jHP@BDFvJc)^kDL z%*}FkoyTyrV@G;&+0A~#WylS;4)J~3$-Pilj#l$z)q ze0i3b=4$2s^@JlMCS@nNze(^!Uy}REAxEZ9ywv4B#En&am@p&BbGqF5Hx8rsr6#$5 zP8cz^PrCcZgayW*mz451E`w=o=jb6x9o<(XToXreO?)Mxj~;c#2r5(ag#4oml3Kb~ zCyW?Bvx7UCx#GiwqE@Ln341t^AJMR-PD|LL@iULqXzz20hpWQXE@clRdQq~^b#?Qk z6vS7{h>u+vo`VUQ`|h+SdB-PQsQNCl%ljVBE5{`)b%85AMOHIH)_KsGks_-&8OY)- zWbrBii+VaGWb0dL@05C0!cAk}ZLMUjo|ncati00Y^L9BtDGk z(KLM`)6;1GV+y;cN*o`?3~7F}b7Y&2s<@-u8vkL%J!&f_@|@xSW-GJ)`kt8PijKfGx-T4Ddhp?C}NFMcwDo)I`MqH-^^Re&0l=zb~0Cp)Rz)k%;eA7GSS;7 zp<-x`qx!$6UB?~U|E0Aa!MFO!Ik9V!`@)2j6AOBJhb1(nZf7^?Y&*KQ9M|&yrnjtT zbELOSliqR_pr=fip7L>trI$>RUh*}WjUF;hdq^%M^p0uLJ7y8nGp0+=SWK+FV!HH- z*XdmG9`TsH8T`M#H!Y-MZzrehjc)UP!QpMD1C4L9k2t=~BZ!Y|bFt19-{!Qt2J&%) z%rG&-!?)xQ4^uC5wfCeYjOe+Z2gJ(v^W#I$LG+h4&zgjjJoR!{C(o@3Bf2HIGdx!% zjyTOdz;kXJTjF+aaEpVoo}ie%xEc4-?}_flf1 z#P4xbu9H|Qv8GrnbCtGtXEGu_Oe|~l534-7u8vOoU$3irpR$*&`OofCv^F1<+2ggj z7x#?u+I%u`yf$A(e55u%u5-m}^D!OD|JCi+=OjJcp~%N1@eai&@!<|7A06JE`5?YK zD~aQ~vy1r1?i|#)4)2c5bJV@YPFJs^E>8dPT~&(QRrN~aQoh^Ovf++O5Wk};28Zvc z`0({VzoW9X%vi&3y2$pJn1P%>pL_vRtm z+@o|{**iUarXtU@aBO~&Z@jXWoRK{InkS>#9Ce%cA*^5d=;wU9kx`${4_?rUa_+XX z#8p&myU;OY27h_M$CrGxV=(R^{-ZZ~QuE_CK5e*=$@_|&&S}qcJzUAEa=Ld;89Rc` zf_UTsf`kpw?M=<=8{Z(d`82k1Ok#%qX?pQ2Zp_eP zGRv`AmPfli+w6??tiKi4MrO3TnT65xVMl-!n>m~Sa@6&8)MXYZGtH{a9NV|vlWLaHd zlIJGk_%NlaF;|sLocLee6M8l#u5gOc(UW+WKgDl!`19L*TIbo)MBm`T>%636W3#=4 z)+`O#nYyKM%vUngK6(nLzbl=IwCx}D1ApBKK8SYR-SkfS5>}IQv74uT93FBmY3-Rq zTaOR&xrp0L7?E>nTIx|Bi~N*iOP5cfim+WMl(L2KYl*Wxu8j1hRLku;p$l7fbdGC|S?9IK_lhz>E6ciC&<4zX~(&tcjKr^M)9vY z^u~^dXEh%1WyXFgBoeHOp#;irR0pItCfT56Eq&weB#3gwMZv)A^p*78LyvYWvuVf zc+?ijPM^;4akN;k4>YFY@Xv5(UfsC2x6AZjS&jV)YMY~^w(-bZqDN*oRgu=xFzCTJ zFtl+k9~qvv6F#ZS*~eSe%%dtKN9gy2IrIp9k}x9Y(qoU%9mcVr)Lq_qQ0B|-Ufp<5 z>Jtvi%EW`R>7N{wYmYtFH}H=SGC1YukBjBXJI8Pw9Lq1ON!(G6$s)$xQ=8SN`z-g~M4a}-ns@=V7Cp+pw|8=(XEi17VeS={&UY@< zmAoa1nxQi~r^Qy%Y#`A~6R$w@@`W#pA+`Ja%Rux`8CB|RV5wVPdpD$BI`g%8dCupX z7=!oQMO~&g!uxHHdUlsQ`(4Ac-}NYA@gaHk=i}?^{kf-0U`9Xh&x;m{VEL%L+m<-K zjuukT(_Z&>TSCA7q`aO=EN(Z|ci!B@;d(mfw_F*Imid&8%^uRfXhMkkZOz~E_r`lx z@(}QOQI8*%y_KqMEN*&~z5gK=*gGMSKI|4^u7v4BKaG3i_vi`bZOqx#VY=KayqkXi z_tz4VTBoh#$#h{+7kt`3ut~gE0{`=LZBg3^MvH=Fi&nnNct^fnyy%bj#0awJJul~j zOJJ9G7!bA#gzvCNLiAa*{Z`}Ydj;BS%n=Pr$(&ajEmBa20Lfe4_tx<7y>$k=iA*)b zhL3+@s=2BWx4LhpOOm^$u&(|bbF7~rlqYn`=fS_7WM_=|p#!LEG? zutHPEa?-J>qd;c&O-I(UP?##ECa-6!d=|5r~=qR0OG`f+K!82%X)~kOzQ)n#iNpDT6gRsS`c_K{zYi9NBtj`^aTB%4lO3?|Fr@u?EkH#W3;%3%Z;mAIog)(I!c9;{PA;1W zD#=4||49vVAf5q0y9DUe`2$QCbk8RAOnf{k2H~!EhVA`*y8Uo{%BqI!o72lW?<=Bq z?55f~)BDa_o>iY9vd-+8XLWv-Cr`?g5Aoy(o^-?gsVAwGObr?PW9hxxR*;E)_LtL( z+83DxKShG9f92uyvI$=Kq9O0t-vaEg8v)gdvDO`#TEVIx&2HajsP%g;{LJI}tQz5T3%YAxHhws*AOjixpF z*|)XN@9cU$x)f-w%KZ8$7#K2J`p|fPhs;jN%u5_O?+9MIK{CROTMGs5?NaMK?X&9} zxxHOF`f*+|dzHFH;ya3emwi=3udZ(I=oEebL&AH#w0%jp==vWegEgXCtPxL2`iS}S zl(CM}3@b#Vr9oLGR>`yC0+l{i(pV_YrhfEW+XF^mw=!qoRqfra%NSbpJAeCrZGU9k z(QnJeFrvp8FKk%?_qWk3I3?l9+npC4!%nAJ`?~i1y1ztbm<(cL>!(OZztbO?+bx!a zYvtJox_85npsL?0)$iDy>aE`~dvo``b%v$W|8)|HvrZ~tfb$gE+#{pCzJ0GwL7Ruk z#Ax#i$wX))KFkK%EG28dx_wdG@R8B1Q1IIJed>MhHlgY56iGZI@88(ISc&4NkeO5z z-zE>ASy8-xVs2!E)$O>~sm-uI<1{xz@|MR3!_Dx)_Id;2o$V7D5bL}a?`~f(6Su|s zj@;Egn_Wq(%{smRd|lX=ZFK^*y$#+X`BG}kzw3qJ8#f8TSpv&2H(I%f-mkwmc0p+W zlpG7;PdvLwYGLPB)J#3mL)1w+llopuBK4b35fV}|A4Q7XejhHRaPO(tq4Dg$nr~1@ zW>qqg`Y2>M#~G6%NCdY#UAK`~rQhL$H~-CPe`yyb z?KJS8QnC*NJBs6J14BL}!z88_0zf??f?M*HOl0ztf+bh8AOh@=tBGEvP%d>(!1ok5 zKn{_xWXVnP*4yqe-+HYSJ@hYpMN=v+m$w8HQc`T3v1AYan4--L z^&7C!XUp>zfb{(%bN4Wa?64lcyN~$PUkx*-9A;TBi83qla6DfMpZsJoK}4fE@&V#X zn6Z}Xg<+vYjrYB4sKmPDo@`xsCQo+K;oKivWjf`v%*l38GU>7u%l?P;{WkN^`B(z> zOaCG~xaJ^uqgEn%Jl6WL712W?1*2MDWY_X`HnAUaWKKOyMLl)qNvS_(~=4K51#=+ke4)rFA((@nEMVTVGq&Rvc ze?nJN5(U_28uI6Zsd!~j^y;_4NTn$H52+1~f(4&5i~dpC;9_;{E!@+Y#pG?&PyK@9 zrj^{LPfB%B9mX@tq9kv59?A@Hd)stDSAJ16TV%J=Ct&Po#Vh=E?ARY;x#`MuVC8|< z+|Bmwg0V{-3WiALI5IS(f6-i5KFybez`5fx9Zk=E|KZ7>{r1B6S?Z4?e9QEEkWZR! z0`jT#5JcTTD$!r-mX&-T-A=ws{t;jIlmh{KJv3|1o1u@t`2z9aC36>Kst=SomjS3J zU%m|z$uy}abNV>Z*+~aUox4|Y`BO4d=b|fg9ra{WkFKMhoC)$fOs0BL1l=E%jMVwc zay-nzX!YdV=Mh6$GCWD%BOCG!C!waI8waImBz4M%uxMLkSe#a?`2TO%aeD#PWMSNwDkD_`JPR}+UqrtveTim=|8~f zy$Q-w9Hi;5l;qntMALaboxba8jyFc|ygbCYfjvIf59g%oz)Gcb`XG zzvMrFy-Crkr9aNpUINyf`!BTHM9x2b2N9?LA3OAcMC!r!5YPTGm8SF8ah5^Tc)(iu zd@3h=(sZ1!K8=w4EF^EXT}NZlPinSZ*-+5=W(syL*Kj*mL4R%tGYdZk8r@E2+xHU( zw|zn=Q~2BVgT&&l-xvkG{{g9!Un)1m2_)T@+4$xGTnA>JBx5Cy>Jqerx+hQNpY-J` zIT7gPf^!q;E19W-nO#Z0_htwQ>ABTJdJ#uNUea3<>2G~dr%xdL-W&D%XYl?7N3auE zCP{znN{s(BDDS-O4I0h~z^R_VU2UdZ-dX!LOwOcv$4VZ}5~L4dX0N3ub6-I#hrlgJ zi=@}Eu_S#0>GR(Qw$X~DKl}m?LurNd^Ut;{IjkgIxD2fUi%&Xr0jT`oS4b~swO;%d zV%L8;%-lDR)%jwW`6iut;g`sGiA1`4BU%9I*(>N7*JrHMxyNvkMtZXqD7P^P$(%sO zN-mK7`#7yjPtK57m}*O>uYU(;lT62wM0!0_R+3>0*Qa;Y6I%B1N$L_mDYDxpAvAO1am5em7nN2GFSSg*_c$DskdhLOW zE$e!zQ=OeiU4IRW^;0r5ox11mIb0d~%JX2Iz5qo%Rx&MQj=!7lzJhQof5Dc2q5!dB zIuvNpCH!mH_tbm8K+y8<@kF}wSjd4u;W_OlxOFi$%BVGNak9a>wky|YLSP>*4IBB99${&Pf_)skXP2VSjsG# zdegn(s1`A*`)&PJVQp4j zpjtkbt+?t!T4vI%)Jc1X@V@~_EBPAUOJDvu_9xSN)cMTnG9g>RE%0F^HXN2%^uV!j zot>n!iKP5Zzu`hQ9(!WL@rm^79k2*M8!m{db`FG3S0}7=b$`yASv+QMD$ZM3-qP!~ zKy%=6Hg1fT@z+=JSqdpx1~8VEwv&6e@Xtf7{PS=8le+spv_Suco-7NF@*xxJpzo6* zN@?mccHlvOCi7C5lCNG4rT-O~jk6NTw|7xnd~8#99Z9zyr}(^Cw2->yT+rh`@YfMw zYpL>HfSoA7=3I*a3O=5=lm)o(9zwcE<`gws$1vN;=hMOT18lNO9)$`Vnn>m;O_%%l z=;se9Se;0}d_H=s#mq5P`EDA~1Ha|z zX6g1DWvdJOm>&ETEFoQ8bAptA0esMqk6vzBANe&0&|!(>TjXt-_4M0c10G4~nnb#i z;f)`uG*4#1Z}&(JD-b7C+`}L3%T#=Y!|U`!@=O`-ISeiR1OTTeHV99jW_YMjqmF_<%6n;;@i< zgnr3kbNGW&^c|k3&sl<762K1sxT^kU>1)7Trxt_8%b|qJ%B=~#EX!URmTZK^_kD(X z^7uwF=YrsOG9|0m!7@azt(Mp3UjexV{OWNi`HF3P%9XWP4fSV<^)Dh-qkHkS2jg3piAvDJ4bvYj1kQ9(-Pl2k|MlP7}ZOW96&oRSl61cZW<=C=Juu9oL0>u8&2 z7Hsc0ux-6jLtG|X_h;RrpJ;~X8~IQ(+?J__vZU=rbRzoE&WhHdo+v9F+0b>NkJ`X{8e?z0Yu z9?JY|?C89f+Q*VU?1t37UH6)xHqx7Jxr=bFyn6N=broTl^kg>~I8mVj+*HSk5otAuWTp1I6LAzQSaua`K!k#;LBaUR zYMy91vFJZ8;Ug)$yamBiS(4|!n=z=8R_NRRzyWPZ^Z#PDhqC3*f8D0=`SwI&{_JzZ z=NHNIhXIsY-D_Fzpvu|fQuIfLGyjcSp}!EpEhk8k+Hx%{pDOwqdnS1kyG3u16%U~F zh9>Pd8)V=6Og;`NnUhk9^si5WDd$w#@2l*B@U8XcTkFlY*7H`!+aM@E!KLpWMzZBi zd`0G)WM0AkdgVH_MZj9miJFu|YQsnMskFYGE?UX0w3>bsq+R8Is0~pO#6)<)?9)=(BXw zhVaen66q*?+I2it1V6bL*%$cxo+ZOpCn}Jw)F0L&1_N_>lDYfAyHLfjw#yGB6FoWI zd~!NZH2$PdxPb_l%=+?U6KUsle2Ftm-*G^Y5!mELqe%&WD%+ueONRQIGfIR)1 ziS%{sK!JGrXJq^`i;F&cJbXCk$EgT2<1gWS2NzD=E0L74^cL1hc$-sSnXnd(zE#9_ zG6yHpr$ddOWbscOL40~Q;-aK4XGq_b<3Q;dp16g(vCQ>ZZxtNl9MeCz$@DGrFNbqx z{nu~Wude(bE`?Q~uj@;#4+~=v{?=C(we@DB9FJQQ`*!}A7p`4^75f z!-6~{1O032@%33)Kc1g(RN|n{kHIQlEt!GDe)Gf|=XS{qf)Z+E4@)i@*^^}S$l7mY zpwVxA3oZ=G_i_0mUbu3OGPo2-Pik=4AP;xX;G(AWXoS14Eiv?6OBzF8{Vq-LKgT>nvN2*ISQT9leb1>+!wYUbw85Z}t-X^};4E(R%p5KBXalNC+-@ za2F#vdk$n{JKOjgG;}M9iR2%kBa41oL=C_kt@t)10>@J;ecH{IH7Ku~lt|zDQWT1k z-k3<=wH;BAb6v{cGR{Z+Bu)97E`ec0MwaqdFjex=6UwdhKJT@xPXKX6j$?=u^iask zicBKe&#KS2S3*6d-5i|dnGf=DVtTXUR5Xo}NCos=$lHq3XvIn|U_;+8?a}Q!7|i^0 zFABSjrp8JyhU@;cG|S_E=W)DX5l&@K4qyL6_mZ^`5Efv?lO3(A+}eZ-)48{i-*Zf7 z=ks&WDVkily<nho$5;`$!1N%rIr`}fOx*)i zxfWhgUjM4`Sgsv!vZI%w*;@}~8h;4DTjlLp`*h2xa1UvYjJBIdN554LSHDd6adp$n z^vjGKO}fR8;$a1D@98fyw3=5_&sn^9w{E6(|BxN~3u$ucGS$2V)i`fe$Mns2y?`~2 ze$-$x043K@&e>~~0(IxJF$RP>I-vB)I&+MtApa@6qFVK?v z!sUR5`Uizi-6D}suK=lzK2x1PJL$~Pn_KE$O5WnW5i>cRJNoLbAFPY2lYRA^x))Qo ztiB)rQqF^;4^_}^?IxL-TQl6zq;<=|ODyYkNDxQo_m5yvp19@x4-w1h>y)3YDsP)d z@)X##q(Dr53M!cVBLAdMyp0bKQ~#LNiACOVXw)EO$M}gwhkXoFw4`rjsQAvyBnTr% zZT*YL+)8NMsXt`%GM&lq3Gl!0PwMaB9NGIEk{c0sz z2*#3}!~3b{0rroN@X8fv*5BP@Ss!HS6Hg>kMGnR5zeRe02XA>Ev{^DR#~&EMY9{Gx z5~(ZM;#;Kld^&a9$Dt%ryU)-((2wLTb6*C{lGhGRbl!U^(?F*ahb5M_?Q>YvUkYBq zRgk$xLFyk7w%dPil*lV>`$2T0U*fvNg0_@V(DEzpoc`88`IhA=aIL=5^)su~{vchA zeu*0rhcDz~%htKl_Zt!m+P`W(y%F}c^Rw&mhtfmqXnO$%YxLtg9*4K>yC%wFL%k+3 zr|q??YXx0$)oX)MrSq|tw&RTatg8~U+s^fCHD=w0?7!A1=r}jg(w;C1+B=1f3xDbE ztMn3XV+m)!PcPxNYmf~G^_p*c0?j~{5nMNm0saiJSD3TpjpXLWKxF@p?!dJ}7IUdy zOnI2tmxcKoT+F4?_sJI%5krPH`>$;=*|$G?2}Gic?mcO2wus#P@^&O?*@^cj+F!^9 zkA8{IC0g3vYG763D-3SfD44}o?s}0~@X7gIpEvWLhlIAh$0$jBJ<(BL<>y>U;Tz)d zFYzrdZ)oM{UU#4rhInlIo;n>A-P#U`U4AY!ZkD=PW05^z{tGPY4^Z4$SMuHgdmRNV zif8QvEl*@Wr!$8@(WS~q(EC6$|6bDXsik}01J|tdNN4l(NNC`!KPJ+5Z^TRuuQTgs zwRA7(cQK|s<-P2I^zQ~xsLbZ2D--EIU1V9($5|LeqjWFnTR)B7iSpiyXlB+N_Q^c{ zNp-+33u#x}srU*d7coz%7SQ%Dtp4Jq z++nejbJ@Yk#r%`{z(NjHj;HzWr$^tv6zc&qJpZgzO;4<-^#Sj|Hq9i@zdYT!?ntB} zfX=@nJ$GTA%%fzMDYwP5$vr8Lu1v4)yn_|$rnO}fUQphPe#=yN!M4pCq5>+sC_TUZ zm$J9Q-{Q}<&aMy7_h9Ri`tW=dG>7N2>H`U0$@SUY3naLTo&-Z2JQZHQrBLS}a-$oEm9w1dLrnZaa_1`H) zP;)Egj$(F^qNP$~Q!zdWA`Zi3qx% z$=eev+s=2QEVg$E7V|jOqu&C5Ui%M>{2U!DrtR*0^aec;C{CeP&@I7}erXiUBY4ug zjT(zy)Uoguu z?pik7-q&!!QkLy@GO}cyvSbgDk>%=?<@$wWSgLMW zs&=;D1k2Pd%d|?K-MhM5mgrd0v;H=gr|nWmK=j-DWnk?^M!|j(?CJZ){@qW4Jyjd` zuN+g+PxkMVYoZ)3FHx|k&VN1{mYbQ8R=QtDW|+*rT$=bjEnX{Wbg4amU%mX@4fP$SOncEW!x(_F_jm#0x>*&53YPOT~nv**^$4K8m`oQhjf4^f4 z^}wI(+q##L-*@x;))yOt-DmT{wh?1a=c1_ZGS_vkDC_I1ykN-;s&pM}w7So>_IeQI zy>}Tz&{YqjZ~vKqsKcNB2E4ndAwMvu9xn%Had5nusl8pM_NR+F&tv)@kaRh*u>0+- zl9+Lh$n4j-@kDNhl4g-sbstBjol|nfcRE_r=vJbis73!^%>5ozi>{!VK6R=Q$h8m zQvYd2^$cx9sD29RDNuczyc(kVPSVq%dR=(-z2AvK;7ItH=r@1&!?WK&<1?13q`jxD zC%5lvPpr1jofypk$oZPYlJ>G8)UI%oU7g?JjR$0|3&{nY=Rz_ck<4kyj?Q25Z*y6Ohv~&MuQGQcq$yW|Z@rU)_7NWc9+vk#$Kmz`e_Qzh|7b7HGzaltEQ-X6 zXeA$^ndHkzrQUHM78&4{zAi36Tczl%RbkP0DdN5L^74N{d6H|}`RC`1?!a5&-qz5$ zwgahkKASjwD%$kFP=ZS`UZRvFw@c?GnoIf>cUabEk$iV75P*~($nyifg>3aX&b{_o z?NkxH%knd6pktHt`hHr`M&t8%ko+L?oBr>&;Oh_D_JRWdlYA%LN*(!O_!ypTeNi;j z1*!%9Q9|c$QB(}k^{tyy3or2@QTvhJoSNIYfXrQzIXksq_Z1tV_hhPbS~~kkKT6tv z7xCq{Y=h=X<~to7-5(;;y+7;oSWoBMNv|SZIUfhQ8_66inI9t%Tt((w$^0qN(LF+D zSTc8Ybo*?xYbE{Ivd#~#hd`1pi`4KAGWU?#_Og!FyO9i{-!_D~YmIy_(!yhAzDbzd z_UsFDt#38>{K_xpwSN*!iGFjw%vToFughhcqhIitDqScZ01q<}ThB?&tGlAH_1x6# ziLYq0?rJ+f3hvt;eQT&cL}rA-8n^08UJ;RLH%QOoZGS(lK(MBAE!ew~>jEdnf4_ zxo?n6gxt4FCPMB9BoiX{Ba#l0`w7x9a<@Zc{;wld_AKUv3T#OtJf7p+Y#n|K#3 zXMy5fR0uO_7j2NGoAE`bx^VVePfo3Bn?X4H&VRvd*+)B4p;JQHUroj#jD6>in6sP6 zG=~n`{Sfx`8|oBv7t?zS)h`}(o|(n_6Q^PH+j>&!pte;IiRdS{(CZwZw!UQ642IHL zhlekj^^A-}^*OmcVbH%w^#w4Yseiv8 zA_T9wH7m#Z1*}Oui)lOD_UFZ|WzAwfyn=KkHMjlO7aQvrq>gMC9wYi~rJw=O^&hva zR8+q(!GGrdg-u+pI>u>JoUf}Nje z6Ejlu+rFR?tTrhD=bgY|QuHqx&ezw4!?{_8y)SAvCZztAA0a~Y{XE(B{^95e`ClbF zy54RS^dF2+vNU=jf_?=veI={gw=miMD_V+veJ3a9T1k0wP*fCEdEaZb&t%^z$@UAQ zhy3kZm)xiQ_a=-}lN;*`HY8;g)}`Sl--MuZ3fRzx&wBDwGN~OmagT#X1J6rOo0z@OBXca7i{IV0m(_U< z^|r~QZHd{Pmyo%d%)mR4ZpMLft7Ps?EblDwD($ zMck=%<|~({_HFy=DaI?$OD${9u$Q9WpnPtFU+1;7`KUDWu|#*<5FL$veWk>_wvtgW zRL0fkcLva*HzYdRpFaUmK(sA0y3==Oa&gxhKZ1ivw(;cxUyj<*0txeR_NO`B19 zG;AqMEL87nfWI*SW-(mli@c4l)^fI%Xua0;hnfNi2OfE4?i!n(g;urLxAt5 zFKHDwJOX$h7V@tc0G|L!jUh|0mXus)*g(zXK8d9KO?m&q#YdTz{Sv9)!>{~S(q1Cv zUkoKi`H)0<+3+LzT?3<|_3-*GvesZZP9> zCm2lK8y8LVM%@(dY}q3dG{HLmt*ph+APyPwN4T+Ao&5S%vEFklFRke|CcP zX@3zfFXipz7dzTJSiOv-U$R&II&DwQ@AxO9ne_J5ey#5_3fhhYf4ISJEPwKvhQ$U5 zuUsp=3xCOP&2Cw2R{3zlx~M90r^7l$y7i5Q*Ke5U^_F!e#4k52(tiq0%9x{H^7}0_ z>2}>#c(7q)vK6fQ{dUmo=k{AP#a8lfe!qQ?Q&ydF(AC@Gak0nNbad3wRV%SiGhyxE z+>U?Th-{#&KUq+}+XS8J>E_SpHNRnXd?JR)-e_%v`kxa)ts|{}T+&P@t@^&sO0>+N zC*tH7{-T3u@h7>EB&T>+^Ocf!ox^16XQ>N@EK8!Uww{(~I?R4~vSq!W;4TS2(efJJ zkAAJ!ceD#7-Udx*-EeZJ96HyMRtFBp<>BXB)-Q39oCs$3!O+GK=8sI6f7j7o2F_g} z%(F>9V!*ujaLgeDA(;r~j?Y`x2jFs~pKg29OZxWuj`k~oV;!pdIZS#L>8PLgzY!J! zPHrNYPre+tefScA`6?6Un>*Uy44e%i%^OU`i-{s}&E`s3*~yT={My7=&a0 zz6tYIJ)gG<%<5@sJ|Bu;s+oR5o@~=kmcpGxBY4fb2u>g)n4q;c-9mJxur-l4qF?K~ z^h_TM9BEBvx-WvM*5C$tLTmEmuILHtdf!KxXIX;@T040sA9W1Z;9j$}+w~ee9JVHF z@MHv2t{;vYF@X0I1^7}J(F#3G+X1t6sh0-Gp^7OS9ipPrbuW6g@6lr_;G zKUd!a_!Zo1R}`&M!d-UVULY12olL!e%Yy1wde?f0h=jvzdlk<=x-zbFSlBGgptmIwT`#>}M>0zj$*k)$n-LX|iPV!f7ZDpJBTs&EBN1De^K^;KC+@VY z$K?qbz{i2#x;DIDLvA66TUW;s8gdI^*ShY6&Pe^1C&>KhAr=Xuqh1?+CCx}iUfc3o zNE{Qbp1cVF5(Q2@*>8ts-9&Th$v=IDm{pRYj+*h)`8RDXV;e_;$|@wEE9Oor`3OVU zB>5;VQOjo5i{^&ks~;XOja@bzj5455=4n^^^W41Z z8QatIFde+7`}wnt2hsQ%|4!Dg#`mK9-`e}r_4nC&K3Thyl}|VSlhu2=%cFipe21;7v=v}-k+|YPuKHj z8t!E6O;$eLI_~Lulfiqo%BLHD^nAL%ll6bPdeQUA>i?~jPdAR~p6}{7r-L`$^U2za z>NWnI>HB|e?@iX9T`iC1W3qbFJ)f-Ju9oj^JG)zNSNr$e`_T;OztOx$f1{^SKKlDy zc{BriX5ONCjs8Z^qkQAv$(}z~9?gLM``o-eHxDL8Jckd@;e*V;b9nF^9_Sg^GxP8q zKJA%#jMn72`TX0M&uE?Y)VfUv@2~y&Wb+X9`?W8_kdPNPFav z?3YR-!6k`#iG$`WXi3fKUb6R`j~u^jUSbPA=gA_U^Te^b#~PfInsa35^IDeU*q)BF zoswZ*(VK0#Ext-^Ap2O2Nqo73!+V{2MH&j7Kk5#KbD*_faNQF*; zuw@AV)$3hGebvJ3sD7==oQ5LqjLvNUOi_0XSizYmfK@vxJ3k&}%Q4GjoC_Ku9J7p2 z%yA{vxkYiMPF-nnLnVs&enw;Wj#>7~1m#N`UZ!DHzN`k7y!2(S1_+(%y}jf2TEh4C zX?Rb{i5c5aN~cu$ehuwM<@-05tGAc;SQ;l(ZRynkwf5t(1LHCcP2OA4&_3fNNI}$y z0hC|@QE7yLs_BEnrU5DNtY++;-)ds7#;*wQvs7Wi4W)Kax^a*)A1%hlQ3%;;AihGj zHlskj(O!?x9R>tdVb(uJ6a48~f_402bk@(Y` z+44syA^#@^`b#7J$YpE%fq|j&n$eN6wPs{|bgaCl8n`7pTk#5Z)p3Ks%jI*Wz$sSp zxkA}<{Ytq~w5wh*SQ9}g4~+x@U`=($*i#2GIKJa41F@p6jgD1R&qgmBU2~~Fx?@dG z&zkYU(edHop^>qm()iv}Zg|bm@aR(qGCVf&)Pal+^p}G*{bRu%fF)4YjE(sHV|!q9 zs$)-me5y}=#UG|`qwzw<*u3VFU=Lt)xzFDN(>Ka`Mi?1ymhr*<@=zscexT>4>3u

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

mrA6k 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 delta 30494 zcmbW=34GL4yZG_Rw4D~(LfLnimVMs@1W_1v6a+;@1O-u43W^{uxJ^)56qG>Fq5_6} zRf1&~H3%vwYH$Tb2?`2`8h2b?`Tw4$1*`Y{{O{*;uXiTTH|I>AOmcEga@z9#I5qpD zDcM%RH7O~EVML9TXw-;h2ui0!Q_><4*2U5&GIGk`s6^@6QC3Hc6r*e@!-(kr%X&VW z9%Gr2o+{K54WpT}e{NCw*aNRbGLw-+_<-HqsBTzi%xGYDxv*`ld?a&eZjN2YmvqV@&n)+|vi$|%L?Uq`k&&NRQ_?y4iNcbunxA-~q~rOCoh4mAKe0Qs zXLhWX{DnO&x!J!;n$1ov`7t*8SV^hGH@Oa!tU#{AB~7m5B~7kx&pIo;M6UAWnt!&a=x8l1X;Q5%b`w3E^|2)12N}3%xT+-~v@seggzAtI^ zot07|R~feU>o@;Qbz0rI zxOtLm+S$)in_RO>np_1XO|JRh+P8GBmVD!1+kLmB+3d$9&1OF@X*PSJq}l9uXU(>lSYH8Kj1pMQ>Fl(Hvxsce7K zrHXw~mv*Yl&#vomHmYo2(zS8jjCe|;=)k;GS@g-zOH0gS(~TmL_O#BlL@Gz;8+$?5 zhDK@o)vld0WykZIM}9I>zIQ8~KuhsiStZ-3acmyu!D>a~!n z+k3U-{ba9tyqD`;&oJ!vy_+W`eY+@IZWqmnNBOFG#5GTry}_YUvTg^d&Xf*>^_0&CQh-Z z&2uXJbA|d82Px6K__O;t2Z@yraoDo$K3Q#QzW+OCG|Hfm73~^NC3!TFm7kc&+Qd@( ziL42&i$a_;HkXY{efeftd>5K7)n!e4`MT_PbYfz^-7&kCvE3e=-HZ2y*|i#1Vq}O* zl012nC?lt3CP$=bt*l68#n&nJ4x?gW&5QaO9P82jhP2#odXac4*Fa8{o*bCeL|hL? zV)taeLt8CLgw#pQwlC;gcl*&Zd&AIHRZ_5vvV~j;WCL+~Tfvn^KN%uZ86s!c z>!0jXM)xnl*{Kmv$!TDhda5n^@Z6JEQLiXI@zfNW`K?c{q6L^wwkqsDaz)fQX8(MB zUWYTyI-XL_h|sK}w5jYn5`RX6NdS8AV|OiA27K2PfCdDF6J*P{P8f2m{g(pZ6zWk&?B9ju2)Ac zmwJQ?nYvPe>hvqJPpnUir`m_x*REs4WhW9*DU=4MyTIjZkk^Q(*tI(}YaPptN)g|Y zN@v=rgz-|0FZCk-Y;3eX`->_6FHw7neM-eCtX?NlHIJ!sVI9IkL9%H6c-=cwem6gJX#Aq zSKP>{70Jvzd*JG1{$@mtq>-!}N#u;li*oJLmZ~P!F7WIjvzm1*I(r-WstGv;^m}`` zq;fjs%C@)|@}+{2@T5Yi;tFzHI@r#vbE5^*?OUIy5q)C1y=cf4(PyUHzYXaTT{zwD za#a%}wDYeTl9kJqEbY8iV%Bu~ovW%Fx%P>x22j{(=-{kexr)vdE*M%TI(xeP(om^$ zU}(LlJKa7#beNH64;yw><@M9q0rn`)h|7>b!;g%~scY{YHl*EuFUri7ep5$}IO7Mq zZW=jU2E~DW)zu@Tg`~SWE{ovmVY0a3ng+7C;hKh(w9l6zzEplZB|p(Ho+9}cU$fn4 zZVw-RCGT5?-_3j55mSxkh1*74Y!r1((W0&?n%MnF(e)FhS>%qamwpO!{_H15U6MXq zzRlCq?e9l*h(0;pZgN8#j>y$F+!f^@eRM;8`{5f~59j)qaXBR~wP$veVJS+lJb`0L z>tVd)t}m`vn30>R@tLYdR`l!#v$aE@$)+ZZTsBtJLC_LY6OqDdH;p!ollFt7dmD}I zH%Ip}&M7RP|Ej_9-alsQzrM@78UOe$v6VQdV&S5(8`J+)^WZ;grk~WB5qr(VuF>h! z3%{8-(=h7Uci*wasAadkb1?5S?u;Al?6r5sTb${FqD5U$G!bg|k|F&dr%f($=@Dp@ zwDNZL?{}Us_S#498fN%*w@FQiZ=O_z_bHPSyuUta#JQ)R;4n8yyjQ@&GZOZbEH|a( zw9n=tFO$|0aHS?1CEPhIoM^(I%W_>br@cM=?iqjg3`QdUch8WP*qL^wXUM!~2uEx7 z>T9CwIhCs`q}63WNU>Wi7-YA)H&(o|vaBRqm?CEx;`_E1Jlu>Zt{mr5?r`z}6-sQ%$S7y! z{wdc*Yenp|X-$oEyY;lL%@59$CQz03C);WtZA5P)+pWr3DY^-wvtuNPQ!B#`7F2BB zEMK0msUxSeCRJDde>PzsnpUk!UWtyYU1rVVu4=Jec6!I?$_4g?(`&_+N=KAxTMsv| zUz|}n=5Qh9)VJ47?-idf*A)Ads42IR+?|z+MDzlX?ry2up#jZQ6ZFKrk z`^s7E%Wq~}N~E!C^n2TnFh>2)Zmoh{+A1GCdBm=5b&j2qI`!=lR`2Ni>Gt>;73~+T z($ND)>^)Yk*j@?~+3ec6G2K>uyUFbSQg71in$ZQ)MOLs^&(29Hu`ekf_p2h46NMwn zR!cSN72aK8LyUvpvifwReBqYrrJ}|``*`i1c6zNUMqt;e)dY{PQ-$}3>ztQP$4qB% z%zm>@gLHYA$jYf%xR09pp<()gx*28T8FC}^_vaGaaJ^Zlk5SXE81KR`dRhF^Cf2&5 zp)#*bf9a8P4Vq<{-q}VxD>1jYC^rMlTW9<69?9zQQe1)6Suy|22Q#=E;Z`G_kyAZ0 zUOt{aCLYVrEgz4?E3uiZQkinoQa5pSw9No#N82)qk7C8w6yM^1uUDgJ#4f8NUYd+| zabkvq@;SBa=W@qoZ{gma>pGBI{0erHycXOu49Oc~G_+sU_tteA^RP0sZYOR(o~YZX zmu!JUnbU%0I!hTQQWFOmwQ0;Uj1=`uSH@GNDfeTA#kp=D?L)MiTJxk?RJ2p;J;uYM zU9T_ipVsTod(-;;Ni?bcfi{dlX^ChrjxK$1r%0oCl&~x@!-Ynv!qp9@&}SF6YJ9+mx}V#bO(#bG`qrM@^kZ3E-K4<^wpNM!a%The-F}7c;*uOQqtM)-=hY~3{<7Kod zmGI9h1zF$Ovzpha^UnAGoLSgY(mTGl_cU*3oUl(fkC*-S>{JuEj)iuM7E*G3i-yCO z@tNYsXCu)lKe4dnHsVZLl{8QGkCimD3{y**uk}~SOutB`%5z%QF-{c5TUIlYCqI|- zl*;#(+hXabN+o2rF40`RsDWgXtw{2Mj5$Z`MZ7CE8EnnUY2<@X_k$m z{qhWHModQ(Heb>C6;>48a~rwoS!p~y@qi(>Zt`d!Z(Ex-)TM0)-tTO?mG{=? zv?`-F5vAjqTsbLRIpfZm!=qlbT?ej~PVGjwsvu)+BRP7J#B6<}jAWLQb#hT~Z{F>X zr#|>0T(frOQ_0L!*4mvG)-1&!AY=8eqxSHH=e0YgAJakOpCbl0XHh0HxEs|66S>Q0 z&LBYp&B?Q0T39W1ice~1+N503u%dnR^e?sl{dd3Gz+Usr4ScEcPOq%2NVg;3FFEM! z`*p>Ic7-`xhH_+NG)QD6QkiBb5)DF*i-5t5|V6Cc_gAw zeKMX*o8mc(60w} z`74d6!kL@@tm2#h zlB48qt?)l?{u|f>%pRFrn3UnX>aE7Gf=)ck@f2tGo%aaS2Jg>XgPAt4yFAtVA2nw` z)sB+apQ_)4Hg$C6@2fV<*nQ}YNM=pLNXF%EPtJ%mc|9w7;xg^QP9I~x?KrKHAOFNb zk~Yd19uVv9({MXQ>TpO_oGGot#Bxz-G*MA+mUTK!Cts)W?0dFPqE9*fTZw+|*S`_z zHrF&wkGY3>-1~5I6OTtLB(_V6basH=E7TPB$VKPWDJXhk`R7@o1Jcva6Vt{;=Ts@n zg(`PZ%_EEnIkHV@8}H9q+&-o|Ti@=ySX;xC#f^-`_H&C{kkFfMmj`n>-|PgBv>dSFlTFLbPrR?C z_VF_ttW~tJI@*dxF0F6uv}+FQU=O<|BPEX?o3vO^-Cno4lby5R%+0S+*KW38Xz`tJ zZry)BlNH?x)2kH+_5~03i#k8*t(L{i0ge6bhtJ+>HGL#2`oKR5?|I~G;r>UmVkdv# zL{!!ru<7*2e7ez$;tKKDE-9>X_Qvrbq{4w2-HPwz%A|3Z!tJY`fu%p#U1oNUZ78ZV zdFI(PpUupQ{Yk%FoMvuOg%*#VtuW)!tk_3Vp`z}`NkYqZ`Ulom#mm64;X*1ub3$het)|{IeF}rSs+gEX=8jM(z|e5b_JPZ ze7nq~#$JU|GDGms|Clc@jWKyC^&&FsRrH^I(5Tsf`9f|f<-s(n_k7WKWCgc<6LL#) z>&7K;fG(=2O$8SNvJ#ym<$ z51la2Zk#sRt(5#wt!T2{XV93$a|w`!#a=sfBjONO|t*{ zd!-~=aST)P>Tzo55cMoEeIU26bQjZ^B`@ygm>D@?*Gg+y=O?Dn8%HA@IJESO91=`_ zD~CFoIC`gfeNsViQ*-WoJ%O8r!y?O{o^HE;>@J#FZmA0Pfmj`){-y6I_Jir-u9;`tJflu|nj#M~v3O~1n5b#qt`4EAO#|jEaf!asw27R7<0if3v?^Q9XLN-|>d|AT>`#_ekA8Z} z9=@`A?7dUeE-kf<*X%MYYQ(nxy<(lLh%PxLpEl;$_fu+0sct;>gWLj@kx#}BN?2Jp z;@jV@sA0TnU$e4C^u=FgPoo=tmAu9(S!XPi_t-S~Ko#A`KYr1j`%V|JPk%YH{pbh3 z$fskk%Nk7+eW9r8+F$IJ%d5wpl!6MnjmLhG?-E`6vo0q8VwdIX{-TO~&q$d_(|u#6 zV0GP;^D{rNo88mOp18W^|9y_=(KS_v9O1Vn67nW@LD7W4JsCZ6;vj3dA!Uj^C@Q0b zpBwUnR4I{1OlF9sNN!X30M~9ro)@AQO^`XXp8SmJuQfwsI?FlJ9=`Ve;;`_GwQr`D zdFH6GFq5mCA1q~_%r58X|ETqeTA3a`}SS6P-hyqIs%^(-O=uQGP_ z^KDDbr%XyryYKTujX&&V&kr!l*grr2V06Yr`|iREWU;NVJ&VF03d zq&n*^WGTC2f4}*jIa|_r>Ur)l`_?TTnMYl{<-%$+({m!36c@0RO&w&<8Dn|&)ApxZ zI~XeqD{WhnmfN>Xj`Z}Ai+IWMj0Y3Tj`b}(@N%?N(qZ-{Gp!pdM)LC>MR#x=v!#k2 z;QC0ySM&s3~nKjVgMLNqV|68u*e#ir2wcBvR}K$4I2u1CEnO@dh|SBE_5FYZ58;f|KOgAbH*b z-;gJ%dK-O9BE>u4I}#~;5Ryo-4}4D|#k=4K5-HvTKaxoCKKO}58zs>P;AavkJ_Nsz zN3kEA;&;JZ)dBPyi4-4$-$|tS1pGlF#X;~Vi4>oL|Bz^tB>D{e#Vmz_E6y-TB#1>( z3UXDcC=Gck=Ex(Fbm%KHP$?9sN~25^sEzQstm>$WfI?6_Bf{h$S8ninJ-Abf%5$?ftK=83I`!abs4%GxvDGBVC1Q;L_?6Tx(W?Nfod4K8ilHB&~RjK zmxLqGwa8M9G$P5!bPvI!!s%}6xB2RS_8jXBaJ{p4p)mU^h3RSnDamaj865fi& zBTF>_-G&_1M3lT8y2?A?oyZfV-Gxq*Pm3mjyD17(_n>=GsJai`kIa`O-()l>NNstN(RcMa2X6$%aJJwcSyn&Xr&}ntwIk0hIW%1o zs@5TwGGDbGJwjQadLGR{VNzKLH&E4lMG|gAnsNO#OqUeVY1XgsPrsrX*DLLcLL_>VuMfq4}mHybxZ5ELA_$A33Uv(E#MCZcSlO zNg<(X0=fje< z$XCrmZ=*mp8?8s7Y7TlHnQuu#8x(}s?X3N@8}O?ss4lRL5}J# zbT4vMr_p_AhlqKB$Ux=>d@VB2B`8ou(WNLv^#3Vv5H#PDe5vR%WT|8`mm^0NLsuYI zm5v4@PnChLM82vN8iE2=6|Rs5hfG8CEblaD`%fWx3AjAf&%k)tYuu0gJ+mHprfpe+9Ow01$984swz4c`KoHD zBMMa2Q706tYM}Fw`Jp7Ni8>=oRSTVu998Wkya2k&I;acsRB_Z5`Knx$K!GX`bwi=5 zF6xfVk0fC|)B{;y zRX!Sn9MxEKGjdh8pmE4k-HOH|Uo`5=tw5e?C0d1i z)oQc`1*)~^Srn?CL+gJXFy@~dsQ1upi z8<~eBp^x?O-y!p`d@e-aBTMxI z`Vl#*pU}_9RsDi~MV{&u`VIMq_4(&__y-KM@K5v~6srD0r_tokNtl+#(?J^f1hFVe zL5?aFQJ!#BF_exxRR$`Bd{t?bi2@Ph56p(47M4L}k@WJRUWE~992D3AGxXqs3G!HjZkCc ztD2yuC{Q&+%~7aofm$N-sN`#%gx5k#*#?b7j;bxX4!Npx(DleuwL_zjuWFBOK!K_Q zx)Ft{bJ0!6{8AEjM5B?V>XepbJ{~&C^C%pHTvcZ@7I~`k(ap$LU4U*ufvO7{heB0X zbSpByl7tB~9$BhxXaaIn-P0I6P?Whf1Ks^ut#eANn+-kk9#P_Bd-6o#r*s1!24mV~QOX=JI^piJbb)}k!rs-8vJ z$WuLs${=604wXfL%0)TN8Gl0MdRUG^^Q0tv9+gLyst{E`j%ov{h+Nf1R0(;iO{g;R zRhv;26sWeKswh-#Mb%K!{6-RPgVmv>dI8lyj%quqiCoo-s21{6FQMAVSG|ntpg`rJ zI0{ueP%bjRm3*(DJY*%6J7HbusCJ=x$W^_H>LX9}I%rfH}Di@h3RINvok@>wOd>%c3EL9WWdg`3ed6sWeK=_pifMGqtM2T8aMJ%TLN3up#%RNK)^ zjok0MX?5_$~zqGar4I12_^=phS*svT%HGJlkWub?@|Qtd=Ga#Xv}T;!@=MUNv- z^%{Bt`Ks4Z0SZ*f-EbZZm3z>W$oxqXzJZ=Xmg-IPG;&mX(R}2p-a-qIr+OPLM84`B z^b87AK5|f~+Lwfjp!u^Td>1W7mg+sU1Uahr(Ng59K0wQmr}_{rN51MKv;qaH{b(f$ zRR_>2Wd0)gKCZy{w;Ec?Pbgf29MwUz7P+cV(X+@?eTJSxzUmNKhXPfATokGfqxH!A zRT6%Vo=2AIiwcZ?h0swRp>P9oRY%cAKNLBLe+7!6`7|b;R&=2 zS*owm3&>HOM9J;YRel3sM4swf^b+z_-=UXLpbF8c3M5p0kA6euZ<6o_^gFUtKcYX7 zqxuQ`iCops=s%qQgr~G>GPR#WLe*@v4+W|@=v@@5Z1f&7f0u-F(fi0!J&ry=j_L{Y zA#zm(=p*E*=Ar#LjDNoJNqB(5K=l;*7=@~*(I?3KLlVwM2a%;(fIdZzY9aazxvFQ- zA>^qX6d+%<2p!I0{0o$e;pY^FswLQ$QaTKUlqZ23@D%ZfTq4^(4xE7s6mg-sb4RTb^p>L6^T8F+vp2|fb@>T26_b5<3 zkA6U*su2B%%;aB^a0C1aTB?ocXXL0hp!&PSH&0@MXLs;(%3Tva#J z9eJuAs3-DOy^^pu43vFPUlgh?L>D3RAxYQ|^+%TKVl)6bs)6Vdt`3=TRXtACZI`&_-mbHlfYPQEfq6 zk*nHRkM|BJxN3QAw`Wktvljs}dtG-3wp+FU) z?@{>Z4*C6qAK;JBd`uGlgnmYr>KF7Ya#W|#Z^%{sj{ZQN>QD3^T38cRM6Rk9s)RgMBNRuzsxit%fvO40L!qiEs*B9olCT-7hb&cd zR3AC2WDD2;y2_TQA@Wq`qE^UPbwsUEpz4I$pip%lYKzP{lCU#62U)7~Q9IEyX zNisM=Upbt@!6;DOghrrHH5y%u%(;>lJ|PL8LUWL%dK%ftQO!qlk*ivO9!H*PA$kJ&s%KCE3REkRgF@9Rw21Sc zFbgE%YPc9#sx@c{a#U;4QskfdbVQtQ|K<_sD49}P|{WY z4)2AY>JM}u@>PGLA5oxckX!V-5uqrp2^vGbrv**HQPK%wZNSC3q*IMRw<1?{EgFx! zr*}yIKN3!Wz7}4GZbO0UdNdJ*s!`~6WX_iqH=sL^rMeN_i5%5U=q}`{Mx#l{Q{|(( zk&k%(7z6KtffkNM_o7gBGrA9%3nbw!=ze6W#-Su~R1czukgM8Mk8@p*gsRPGAM#aO z(7PxQC1YFRdoa|(ZRmYuE|i2XpbwCx+KxU%j_O795pq>8q5a5Hy^IbZU*(~XQJ~s^ zK0%=>`3gJ;&1WRxPV_0VRJ+h;$WgtD4k1_d8VZo7dL11`zG^r690jU9=nE98-ato? z=}5jelkh0ClzY*a$Wgt8zCy0*ZFCHIs&~+Fv`>`QjB$!z4hyu%K&|DM%2ZL*2APW_ zUkYlAELCbV`rmV)qm<3GL#`@@+9OYujyfP;m4VJhfvObhh(c9q)Crl3C1EBy4_T@# z)EPOdY?M47y2>)}0_3U6qAti+<)E%8P?bXo6spRjZpd6B2`ixP$Wm2AJ&>cSgnA-Z zRT=e4LQh!*_C~&{D(Zs*RW;NXg{tc4LS!zLgf-Ab$Wqlr{g9)oh592`RU2K5JXIYu zpjncH$~YWIVW7%Im!MFUhb~3tGD%n$4MLWx9=Z%Us`}`1pu~fICJ0zj%e$<7ku8Q^&>54p+i6&FiS3Q6pM1krdGzEpKsc0H9 zS4qC<=wW27N(vu=GoYh|Gtr~SRXv7gAx~wY*~nMTK{g6hbJ61{R6T(Tkhxki%tKEi zOZ60b8ab=aVp0euKV6<~m9E1NsqJs;i9A&=)EoJ#KBzAWR2QO) zP^jv+JV^%$O;-|LOyK}zsRp7;kfXX34MMK!GITleR9B$E$X8v7hM+)o6&i{{)i885 zGS^GKYnC(q4TqL;1clckM>P^%hg{Y5XcY2PH=rAlueu41Mu93HjX|MmEV>z)&r3oR zO-7dL0hD|YI?9LO6y&O=qG`xeO-BzSU-bx@fdbV`^e75dkD*z}ER=*6nvE>g9AqO$ zH5Waegs$=lSb#j$JoF^;RZpR(QJ~6O!PE5$5~}K<`pDcM2^*k>$Wk>zjgg~jf|?>% z)eJRX!T9GXTfmkS`l>dlEecfUpmr!!wMQM0xlt0Hi#j4p)d`)4993s@K5|tTpf1Q$ zbw!C4jDNne8|+SDpz48oqEOWf^+x6G!A*HN$76mtL{PfqCj;Yn#2Gfs_sWg z%FL}g{+Vzxw6;q62hfAaQ9XpFAXhaNO+%h)2AYX{)uU(>Lvx_A&}_;=)f{9abDLy% z0u>-@n~s0;;FHkN!l%&F$W_fp3y`N;h@L^d%0Y`zpjwQUpis3GEkouDl5ja%fh^TZ zv*f|`6yJmXdyDUOTzVN8nRT+qXo!O z6{11>EY}q!V;kT`_Qun~O=vUnRa?+j6sWeL7f`6$j$TCOi<0mq^fIzk9@>E%)hlQx za#g#~T;!>eufhk|H(&W0dW>cgs9s0AsT!*Gpf`~Dk|gxeK4hsrL?0nXwI3ZouIgv> z3-VOCr@8J=lTVeGgxz4Etc$v%P*o50K<3Mmus-UEEL8*43puKWs5f#|jZh!tsT!lc z$X7K%7otGb^mLN$1BS|G6!t@=CkdOQ{>W0bKo=uN)e;RruBsIph&)wmbP4iRZP2AC zP_;#aP^dcRG~?f8(A*&j+fjHqvQ+KS706L_K!cI1Iu~7uJXJ?D1o^5?=qhwrm1;y9 z@k)fEbc3+5(0oN0O*N8{CSpsSLfBO3C{qcW30-9xVRNCUj1jgF`bz#sMj|bRfii=z zl`vG6B5W-*cS_>Ygl&XYQk_ZMR_rLV2+t9^%51`RLQh$Su)WY%mL=>U43s&9=L$n* zIl_)YbC)D8PuNLlDJ!HV4gOLAJL-yrorSKl65;tmPg$Ap0->+0LfAzZD60~76^2Uw zEm8hlftUHJB(6@_O=u}=5Oxl5}Ddddcb7Ylu5L&5>VKyr3+!jFZPvXJl-p`+YDc#s!Ae^YNH{#1%R7m1IGJ@re3UkZKY%Y}hmi-bN*W~SduyhI#m@qL6#g`x6( z!ev79ElHdtTrRYfCgBR9qnu2*Qs^olAY3K%ln)ZF7W&GE2-nE=_)A9F^c2Fi!caMt z@L8exwvIVp45t_afKS;P!Xel2e+$D6BQwU!by2`19uL(WnG{V<~zH&O@ZegH&m~f9U^ple5 z5#l$*=04#J!Z(GMawg$kp`(11@GYUMe2nmIp{JZh_>Rz5T7F7ZQFZ^pwvK9uoRWhcFNZ%0+~Sg`sjW;pam0eaX8d zi|hXjv87&0ctq$Zmk}Nny2|B*UkW|t3c|01zH%kuF=3!wMR;5oDpwPp5SkxI;x&X{ z3zL?5E%8aQqkNX|8=1kSluHSB34P@v#n1oLtBK!`Vpq9_@J*qoTuZoD=qsNkd`lQ8pCf!*7%JBhz9TgEOJbMM7h1~o zg!_b!GWk65yJA;eNcf)6Q*I!9U+6105`G{Il$!`Y6o$&pgdYjb1Cn?P;eMf|+)8*r z=qR@leq2??UswGC{zQsB<#xh@LSOkJ;itkt`4Zu0!ch4#;US^ zCJ3(;I?8T@BZaOonckiFIkBlh9K3 zB^)hulot}_3tixTFOfZ z#|s_hrGyiNu5u9JZ9)$+{$55rQS58+<%G8j1LYNjcL+n}V8T0v=BJYQO2WH@mU0N; zB%!0cituiss~k#rkI+*NBfK{$_SIJt-X{!{*AU(>43)zPwesZ8NE|zEMDlp?*Oamw z24P*Hql^;P6S~S2!umo_nM&9|=tI7L8gWB$pvAK3M#4~;PS{vz9+FHMgiVB&vJ_!c zp`$EK*i7guGYOjuJ!KYQ3!$&fCTy7$2kJ6}t%RYnEMaS*8A#$B!Zt!nS&p!+&{38r zJV)p%D-gC5ddiA~?S;Ow5@82n5a|6!W#V(ip%zym>?kx3OX8}8orIRM8sT|DM_HY) zv(Q!6AUt2_DQgm5AoP{B2)hUaWo^P_S8=GWBcZ&^&n0o3u$$0Q<`Q-nI>J4v+5AdZ z2Cr0Jab6|O&*F7me7yeKhmpFN9~ejK^J>6L>dNO*BL!7z8P%%{E3Vg=bxn9R<;CQG z!T5bf$AaZ?qe8)unnv@EUr?<%Me|r*#FD>DjZ|WJ=3ikvvy^myr(l}5;N6-=%W5T8 zwV=vnqi()++=Occ%zO{qi3`DjpHW9nFE@T6RApZz%s`2@>@pT zb?d0>CXTwk->6$~$ql!}t$6V(-TD<<7mTQFR4!Ou%V;eBhDszol8}FrIP+oS=Fztd zzj4B~*NqxJ>eg}B-4tnbTTbLWYV+Nv| zUgs9CXAxmvB4r9H*D;dG8546Nm%w*eC!3Rm{BslL3BJv~oQZc0zw@Ti6Gx35ecQy6 z$z&%=x)YXlS+^H84f)2Md7aPe0$$hN#`l{zdfYAX8?GHacGUHq;x~`JKHmBKctM$} z#q{NBwAx>Ym2u6(BT z+wh`;jg6`Fgm&y#hD~IpO{O)ZNBg5`0C|kpL!f08ryoS-x=u+@@4Wo|H zwcuM(qM&k3qis$%>h>6Q-MH&VbsKfvO>NuQ+t=63UOOpA_gV6%73_~2{Y&+nFk#$; hF{5v}zMxaCae0B2Yt%H%X>P8;E4tauP1%|I{{Uzi=t_QB}db|JcQ$0QD03Ppt_I-c}RbjR+Tv3(m}(D?Bm`MdW|Z6r|FBz&1roH3_T5YC@uk%OHd>nZvl5TbRkr z8~AzWnNc#zlBoE(r?vGkbNHF(C;O9Hr1dFvJTDnHk~0r?46%keB9h87`wb|_$jL2K zCXI`Yi!Vya>e;JzUjC@!z5@qL7=8Yj@uLTodxj1hK4Ro~6*I<;xp4Z_X%|eKFlBPp zoa$M#$Ao=wwV0zG;}40?4yo%zsW^40?Qs#$V^1AA%-o__Fo)ajaV^MXj#s-ysGF){ zRgB4OtYAW||HzKYYfQs8%EIQ){2ojtYhuiAmE=z`KXt>2~ z3YK!TmvW#KikL=*n<>#_T9{$h>N`B91;`Yax*fTU0Rfs}>0;Pmlao8#CeK}5{u!B^ zA*gu|A5F1@I*$p>nTLj3-HL}5i(HMAO#A?`8;S3*1?hJ63sYyS*;G!+YLq$6Z!2Si z+_ngTVRm?uB+X)IcoYyPEbzR2&tcY^v3?AE0Y^Nq0`AIKiG*znJkJ^7$3uX}DTzVf zqui#!TU%ROw<{&WM0@~ugSlEOF`iCk%m(aRb{z)Q#3_^dXLFYL><+cUnyOkYNiN`V zp{~+ZsVET)`T3T_I2Y;!s~$I0ooz|wX4P*=qK=J2#}re?p2hr^dKUAWJ&SRx=UeO! zW2bspGlHGsFR4DO>{j$y-HJYUw~kuVEoP4eLZW_YO`dKhiEx?>0~Y(==oX|WR&B5) zpC8Vhu?`G{c}zHO7m7~*Q5))VbAC9G>7%t}m-?7BPTgxWW1t?grTWoowA<{qxP_}G z2`b`Q#}@z=F1uUo8SGw@&fH3_SjUHkLk1uPVgt|MC1Qpq;M4HK+~z&2N|Xvs@*CXB zU|yok7v*j~7_!%TI_>fbXb}qX!MW@Dvn418L0rX=VlQ@yRzy5Xu~-gR@qDY!vM2OC zb_I#!Ix3=gzGKkHEmZ0hTj1Gw=V4GpRJxo)!Ko;s8ThEWUA@;H*Y&19Ryo|Ies0eh zr>O~v_d0Y7^6jxLaEn}l_B-l|#~|p)150B`cfg!U87AsquBd=^REPCSF?*DSL389$ z$ee&S(UXY`OBq|D-W!(cPyUYvQ&<0X4Nv|}J)l<3R&Wrnj&`JxI@CE*`M2tNN75j$ zAxTq*r6DdTo>so#Oim=V<1{ct{m@YtX7-rU1zB0MdZ9B9^0nSs7!5hFcueCoo98i; z4!z_|_Uk%CR#lU9gF2CQ`ES@i*sWi&e^nVUfCeLGFcoraN%99PtDIVQ+(I8tw?z)8 z@c7RUhk{6h4GoGsTZ7cU*}5me_Mf2^5LTKAgt|5&#kqN2cDG1;3Db6eG6uDk-w zPMFw$PqQlxm!~slgu^Y(PdbNtl5QT%(&0Y9(v*nvq#W*Xr9lo~aHV)@qDOKeYP;9% zN{=Ls&jUd)eZxpNJ?;hRHW>DZF3jUvm|@9e0t6x3?BNSC!bXD@TaYlMG-rgn!|*R0 zjzE~l6`yD}`C$$2ds@#xxi9i7(7Bg=+;nrvu#{w^AfBA+W0)8BBK0 zPCk-}=-2w7nO{_QfeEHPu#=wXxmGe-G~eGFohm-OQ++Y|JiiGH0HcaXYKz1)7Y9QB z_;A?xT^Z=b2)IjT%t;*6pKy4hb@2}ali;6x4i_Zoub5#)Js*W!66C)d0)o~H!yD$d z4!)NB69~J>ZFLtCJA&C3%C=}W*5cWFx8|Q%$j^iA)g{LU`tOSw26J+j%mif*h6Mhh z&6BV&!v?N71}hkJ`_OQw+b+k541p6OFJ;T=bkKOil|!nXB-ce2preToA|4v9=o}V) z2#RLp@Usx|Ey#JaNLGP>5|Iuw37asETplo3n&yJSPbcUx#^O4chGkMPP^ejXD z#4CfTgF>})GT4lq2uvYPn%ou?Ap>V2W2WGT`fWqM!3_&_f*eEB0=Gpv2Ed{OM;uY` z-8kaim^N`5hun1bkb~_uH6<>^Pu1n@X{5(aBa2}ty&z)XU_s+R6Vj}3P)tEHLh}>( z(3m!~Qk`TejF=T^mOawzIDBZeZ52tCAa@)VHdokaoscVBn5|LUr1p#-8Dr8_nwm9{ ztud=t#AlNo1gr^&j6+d0<6}i#ZFvFuj$*mwiSV4jk1LWnJU_uX@sz-rz-RGngv@$c z@yF$2nra6rc7w_2=^oP7HEy33X)lMBiFA0rgeH2rdk&nF z=zH!k5Z$pqk5K3I{)GHeKf3bYnXSQag9u~f+C>;+ORb#Cm$?8LBp6_(vMQZJ%U~KA zto*+bA}>dhTQiDS6p{<7C6k!IV3xq?^K6m)0E6ID$_23JCSr|CH6`{*r&TG$q8J7b z1L5YN!nwe+9jTrJ_~WWnrzS4)8emL79&IU`E1aZZ#=wB4EP<2*`DOu3-h6=ISwPI zzTai^|G3Wd%>DvPoEtLx^BVQ_%t!b!^)Fd*;-=fwyR-W6n{?n`S<}SvU1~-4hzv}* zx|j=3-vyq1x1ndclrzUQZWnrxtiG0gMBKAe?cFU-Y`8<+n=>@&o}Dx$xoLokMGby1 zGtD4&Qg*Idk~_fv((Ss(?q<}(H;gbQ%=e5irh!k4uzP{$OCy}M!1GNAcG?}Dp3-2m zq2Zl?{kBk+*3?~q2L?9_;Lt4aJYXzk98oJ5|qoK`47!BQQgwfD#JJpxFB~CbY2HF!w7-&x#VW2%_ zgn{<75eC|S?oj)8PxS9FCW9?^8)2YrGr~aoCnF5Bj~ij2JsN_^UkeFLIAr8skaOd} z{sPz^N|fgeF~}6-F-?dPh1Xzn5>@aL)Is&z?<|uX47IZ4Fxfo6}knj z>M{X6a)-LJ;Cu1Qc6D1}hPY{u`gmcN&OhCX+R-$71@}3n`)J0jNA(|luUS2o=%u)q+ccHLbVAY%cAY=hpz1#8y@sh z_UZe;AFkfjw-EEzdwqwa_c^5-c}?4a(qx*-j+Z@& zq^6Z!^*bc^6Y4_)GfMQ4bSqI@n_;;p{36d!Y_aEDS+-!78aXHl7`hEgj_ptk zTTn?3tUcBJi~9I!Z7Pj2Tt+yI5EBKyGyRZf=fsF{%&1sCX>q+MrwKV&5vv2tBwH8pwAdp02K)l-%u+4O+9{tHsp?QXLeP?N^bgKuFBwrosQnR-d2Lg?Cd= zPjdItrBG;6h#hY!Z&+W`I7w%RTo=%firv(KlPB=5ZJQ>q6?}`DIBf=8hl{3lL;Tih z@%Vding@S>oHo1f*Xm*B>E?O*I{X~wp0(HG_aVjAJ={WrYVsVv8VR|co!8)JyYM`* z3BUIVS9evtV14?j8$fX~DCQowXWiByVD>z2_IxL^)Ik?cwRDEZW^$;HUOIu_u15Jg z@l@3`eTo*3OV!*txGb7Gr$yA&(~t2k>XaGSL|Ug_GovTyKQv>xu2fHeC@9sinPV+C ztOh|hs#|B4if`^vUz^#JU#BXwy7BMSUbE(R&jL^6QvMqlGLLW(i~+-RK#L`(ra{=E z$@2u-j?A)ho~1rhS%Gm&nw^38fY~29vao?H;DEqtsQ308$xpXkRMm}xxwp=lBOnqc zZ#DkT_x{4u)p>KX2BJ7gbq3qxW*&djJ%jmjij2x&$)p%?Dm&vRS0s2gwSgv#wWl*# zW+I0%HP+XSm#F2wxNs<*)Ie<5F^}4H-Zb^@dNLK&zSXewpZf0XQzBP{hQ_I0R(hS{etF_0hsDAn?+_A|srA(QJ@p0X_K}8R;`uwNA@%&l@xf;LQ#0!B z#w5T%823CTin9ZiWPW6#MR^<;ft4S@ zVchY$B5=!-CXn(486uOZhnsTpO`cA=2WtXH;0N4z@UT8F6Nfx1;E+qQ{-C)q|@@Pe{j!L48~ot9!U5Jazs08 zR4V~x`(oq?X>O3MkgN>c{*dm2&7YxA6vj(8>6ntZ>k;JTh#ZL#;9>EeZ>=PYDLQu&9?1OUufx)xEnYy4CpWwS7==;ZOE;v{h|UG zy8#!?@6=u4ylb^Mav8NqH!8P&S!2@=da`!s5ATT;xo zsc$Yx>sycSrMsfK;_2jZ7@$~`qC;a^NfV%yXB=PD2wc_F=@r3-b zJa7PQMPGYyoVZoX-h4oHEl&~~4ycDw>Zkp(>R0>K*5yf-Pxhm57!`hNznXVRlI2(^ z;c=ND_V1Uq@7PaGS+1jOLT*_@u?UKteh9HR!tvolvh14=5pMDFLz32`56N~9X^9WW zAIptYC!XqDqt&@w{#d?xFo<7#`XJS|yiEzzjCkomiTv<`)LF|ON{^xR+a8py9o{E@ zHa|$+5Le1X%W5iRrc&SSQ_C;SQ2IDtQ)ge8r*6Kqvsq5TYBzt9dhF8j&@7zopQ*+D z=bnY3S|-fPWR#iX08VPa1WF@_`wK9Fo}EzXbj1^6*sA9}8TL*VM!;waau;D++RX_M znU8d`Ke}y6H~BROQqz6NwF#TI_yKZ~b^%RB@ldXuKB4}ZQDk3W-B21oHaQVWR}z`Y%GRFr#8BL!W8cm9o*ha9<^D04i#jKR^QEJ?U1 z2kC4CxGF|BhBJ$s4fTBXr0Q=l+`h%^()ve2naGQPQrKZHT0ScTn?6EmchD9>Q zY`9FgT7a zfM2*6>UF3Tr*2gp5ry%%sM$o-wJt&Jy6)LHnXPvq30cXw zI;*xTO2XGE8j`K}%}{4==%miRB9cF^He8Vw^%K%uC@J&s`@FjSih`&fCKrnXG#J0( zGWEkN`tb(UePw}tC(;Q2qw0(+udv*<9{es)zq&Hv+VvLIzCPZ1l2TUp3FPPUPHMmP zd5O$|6*4L~@JmRW(I?{ONp;owg8cK5O8mGGzeN5l{1QQ-Qf8OziVtZtPAAn<>xXpJ zaqdOwY^1)9UmEYf;MaoRP;GV6h7#VnZNr8g=JZ2O%)rPTjf;MDcq-yV*rB$HYbPjb z_rUgHtPRWh5lFim7m0}u{~2*An}SOpj8w$aaJg}H9DmoAzsGIIyicK9v^LmP^*+`= zT8+Bl(J?MI`VMc^;5mN^1KAO>2n4- zXJSOQrmR6sdJEkF_rL{X)S)}jL%O-j^3?5H*ZK=$T~a>g+BG%pK+735e~pRTf#Y0E zG#aIR;4;D_BS?#5@1Qbuu>e;gt|DB;YWda?(PMqh?y9;v_nc+T-X?dWx2i^My0N>z zeV|+FtE-y5^L&l97kg{m-o{2>qpx|sw=o!PX?FYOx)*sD`5KqC)WaTntDC7@eGR40 zt*!I6)Hk&>H24~k>}&8gRyEi9>fN>V%|O-CfW)fCc`b{)_08_O+NNfAv(M+Q^ZD!N z)z)8BRaaXhQPfv0@>0g4s`_OpR==>Ry3yCv)U(EWQEjzXrnr5Lpq>iWpbvGv>V@8= z-s+a-syTIDiNigorD<8Cx5?Mih$;jSaawyI!AnrnvZiM5BKP7JzpuH5eHBz6vjZMu}prYQ}yu{bIP$P7HRlUEa&PY(5&(}a5tgWx9Z3KgfgLSowFz@7g zd(G?RZd$g8xB|xc8oJUL_Vm@)EtA-ST|#A@SkPA6vPeUOL37tb0=$bFnwQl!xvSh- zx*r*ouEPM^$8L$QrLM+3NAkG7xwd&31c(}42999}o8~n77JBR54YdtkymGK&==Qelpfbq?W^%>b#(hsiaa1KVX57?W_-V=`ou|1Z78gyB6b}!^$^D@G z|E0?>5fi=SUFeNaqXZKXL?97HlN=F8ak|K8Q6810*@0M^h$|i!m4)4u6DHxLG|Hp$ zgp>R!8j55FtJOUo6FV+5uG4WY_7Sd^aQzwAeq1|otptz%S1x}LaRYv5;Tnr;Ag=DX z5^*_k{gU8fpW=EK*DJUV;o6Ps23+fLEyXnfR}wCc>u>1DUvRyK>p5J1!nG6EdR!}T zHQ<_oYZ5MhDgF@qDUj3-#5D+4Ij-MI*Tu_&a)WUVVOKrF9ldJ2b6VzA*4EGU^_i;4 zApY{icgZO|@D9o$oC^MKfoJF!>*3Ximlgb30G!I8iHyyE8_?xA`TogR#;kA|#4Z4O zP7398TFuy#aKpq;oHNlKI83!;82e^Fk`8iYXuZYiO;EErODBv>mom?R1v zRz)}41a-r6o~7I`kP6$gMqub(>6Lo2QikloYdHM!id6V9ccZA+^ArPLxSxSLqfbIM~P;( zrRsy*vz>J?OvTi>C!DXUpKMRD#9T@oR>N-1p8Va_jQyKfb2!8k$?L@!KFPi(EW~y_ zq&#W=wGog-f&;Mn*7OT|PY5#osPm*=9lt5hK%vDyq1npFuJErh8Y%fS_Oq=>e-2>8 z@f#VddkZdbPad(Yk+GLfAl?l{o`WYYo@8p&ju`Xg#f-hICft_eR|1UHQ1Mq?t6@NP z14Y_bvCd%zcBFmPOXeM>ynmdL_m5sK4sY=U<^4RkW4)hyISHp8(teJOqOzx{td$S8 zp%HX57?9G-)}gqSYvpZ_{WOKg*{HG}(js|xCMp0%?^=M)KoZm9i4#|=t{t8HpFs_R zX|^cvMh|I89A;B|3z$r!47wP%Li3_iSA{BqOp%5^5n~hDS^DZ414}o7GE?I1jE#R7 z%@5(G<<~QIG2*ibNEy%AQy71WFKc1!b1lxqb!awXHM%Y-+zO&eh9jajgDB9DwiYSX zl(MPGP=-V-!h9u_Q9s*}*!j?9!OVlswob?khO~z}IS;GpJJbBv0hbaD>)dXvH9pt*LkGCCGlOR4U`QJu<&4bKA=IDPDP{`t zVSZVf-`mMCq$fOi0K?qe?m7W~e*}R1Xt3V$J+kzWR-0{UFbwYSDu=Dw0CyfJq%n(e z;%7D?KNK7DCLU{vJr*%>sbN$vpp@_mL&^?_9F4UM(wy9LiIzRR<48}Zkv5F{^hc7N zB*S``{#a~G7e9o*0<->f4EeLjoR2thtUHFhmEz!7q%`ANHDcKB4SCTyhI|X*3Nmaj z;yT0p>?oi=#<6n_c@a8r5-6r)$X`bXzN3bM9dIR`y90(1_p@^i`MG+KOUQyfC`VlH z0hz{Bn{DwS3kGevwG1;P^qkb$(#Jq?uZlS5HUr#yZ5rI( zYmFrMCy|@11zWz6LG|V&$3P_X1#u~tm@}<=4K1bo)f{cT*^tofX<@g0+C@i&`F)Wf z2it0G$IntOHAguJO$Fin&J^vq*$-$wAgUil^;-!*UF+jU>^_m|oR(q0{uuc!%=$~; z_OuiqPQjNL%wmtr6+5#!N)1IC%;~oCj79vi4E0kFDUJV~zBUK@x+T?9>dTVd;){dY`++iQm7HwwophpWbSgNX^Rbm)faQxJ%U6y#te%UkUIXIN;IrA+8f$fp|O74YE|MKK?P60p~g@F)G?RJ~K4FtYn=z+4@A6_Jpo?o6NTJ z3?;f2!WJ@XZ1?suC0CeJtu{kKvE34F{m~!_#nUaltdBze^7LB8*Yi%+RY=i8{xT@u zB7@P$PvJSX*)SS$vtu%Vd^?dlu7`tKK8$$r-<56-YV`yGXwg9}o+bdTIjA)oya8&+ zK@GV9{CXqt{>=t@dsie{dK)@al3=$zaE670yo`o$^aa}9laigzGpbKTo*sH%813{L z^s2Yt%wiHyVK!B+PKtT7D@JA=020#(=*)JCla$If4p6b0WJ?E}bK}m!*-;qJbN!Am zc92Cs`$KL93F9e{^T)glDXum+mLYNN3qTjw`H1V{x`q7bc6mK2i|I;shSB)naG#hT z9s3TghO9<{Eo3!V9>qgOV+5d3hn51+JG2{dy+bDuZ|{!{I@8nOAz3>(3QwDJt^WVW zD{_!m)ECH8o-xO29uWc3BZ5V101$GBHWKiw4pC@k=%#r@EaL0+;D`XW&v^8;BN;ma zTbbgs@aTu9Gjl1b> zWjBgl1v9!a48YS?tiT6=E~TOegguMWzBM6@?rsV4zdPwVq&wy-MlrTZ{i!Xrq^g#& zx3B}3GRdZ}sjz4JP-l{j_8qjKSJ?&^Z`@&-X=@#>j^F1V`vB&;tV1&CtQNkSh5Nz} z$ULT8K9sSaAnz$h@yd(8zYR)13NSVUB&W>5E{O8w490F#AKMq}-*iQ=12>s$TSG`u z?q*Y>V>SlkC{@~FN^xaE)J_5D+Y5*I>X2ije&3IrJmk0m$edSbXesAmQ2L(Ay z%sSGD=7=fDc43c@{J`XrZ^6U{47V7MMYi;?Wxe1&<_6GCVp5hC)3XSBVd!L8R+_^J zFO@d<3f7wJjwxv31c3fG^Teo4pz<^TiP_p>*wN^JE6<2oi3GpRfrVb4*|N((aKC|Z z){nE>JJco01n(}ZJt3piZnoTDsFD1Exr5Wv#cYz~Ta2@0KTq^olcfI~v!tbexAvy` z4Ni5C^>l;ItZbh3`)f4YwT6ynr=W~Y9rw4*PJL}eGj0nf@6sufwg$HXDR`Jeq(G}+xU1^K; zVszK^0}LPSW|ysjmHiS1*0geKFy-%5s4tY(^aIX4d`X4=A$Rp9LTr$kx4p3n+pfg5 z)t2isb?OLNy!9wHZ}d=Fh#ip6LkSp~vb8R!ao42mYddqj04v!5gHm=~l#}9mDBFAn zeEk{l6|pfk?D4Flx>seyyoI{kDV}6^q#^wP;we^hwCjAx#<6I)R%J=)K;E&DtIt4s z%^C0}x*rwsr}M!Iu&L}*@T(dVf$76djJ=IWSu9enCY}*wnmQ=R#w1Gm7Ih`Vm69va zzU25I+BGL!$^g(Mlkkir%H0%(w7TIzo-gC=yHQ}evaNVUkm&=tV+Xi?in=<8$6HI# zS3MBRt+NaXW#!xz+^CwZ6)L+JW;seSItCa<^C(*ft`Fw14q|Hp zg)Z^nGFyBMZAxqefDMTl+K|{y@g6wY1ao#6aV+r9T*S+bi4&Ap8YAPUDUMBw$5CGf zGGaNsu=CBV$Bdo4!sfEv590MOc3!G=lL7WD#JJ?Od&mXTIG}N>e zVfhBl>!I+0lnxt8h0nmW<)|wk?c_I#F!~fFg<(E*aJ?}G5q6??|Sl^^OD>jI$r3#ik-3=N}L_mjxci*=`B^D!6PPQ``*xCfScGXed9Iu;^( zgn%Dl+x%iT2vo+yV;lr~SJYEMA92fxnMO6iLYPo&1?UzrXhdWaL9rLXXg-1h5kSm* zj4eKhqkdgM=1*G~n?t}P0HbbZ?1KjY*cGmfxeTUs4}dEHC=k^t`v9Cqu_?Qk;h@h; z!K`oBF}D6?0AGU6N1)nH0;pX1^^D!q24IVsLu$34v;P?wuza*-IxUnxfuhJitC4?B z@DE$f*!7tD`;50T@diw{^8I;?tvG<=sXt+9l!Ywi{jTT@rE~^mcf*8_LI=9$feXs= z{9x9nD1028kYlfmnWF8%b!|3-RkZ3;7Ec1N524;ul;opvzCHy*djxHEbyJ#`(!PP* z5E)%tDd}^nSvnhIp_gJx;|k0%pr-2~@b;^SAT@W<^71tW1VAX5a^+Nv6{KwkH?MJNlfTTlJEiM|HN;>>Z@Oaf&PF4)Pg=Fueup~7kSAa zSpE*V(gUqky5*`bJ)Ulf1|N%GSEHVYwS0$>)}sF8SrnyuroZe&yE4i{ewmK`uSl6E z^FYjFXkFX~Cs~=12t@%j@CZ%Yhi{_=z2ghF{CeKq zV?%k0wHlmwSZ#eW-oJMcV+%BN%Jmntw+ojKbcwI%51rjnb^-VEP@{+dEXyv4)Gq|= zh4wjXA-Tt3cPJ+S4($fHT_`6Ra?=AGl{tMNDQ^)#ISW909wfjov((XyH36b5kp$i; z>2R^)35^2rRt{~3k);-Xgaq%HhUL_80My*_493O~Ku8mZfzPNE@hmiyiBChcheLIV z!uVTJ<(;cE3ga_?%%49`%gUszYnN;E5&$9+XX1zp@FiUZRk7A%<^}nkOQ0%=*E3eV z4*=yH+Qir=_XmOZTpFpJuQPM;0t~6Cu>g)0^nYkQdh*Of#{LFMIuAfku_+ji!&^`p z)Ag`4-QQ5tpHA>Ub1jamKZRBggU=hV+i+OMxsr(qXWD@v@W}+6mVZnGV$YO=6|GAQ}N+;=4TCI?kixLBd~noU_%C zFyWgxEmyg$5~l>820G%6F9pw3fNKOW@i)@N7FaZKG5(mIO=E07q$p+@H@&0cQ3Nn1 z%RiZ$>==hV07{UtEeo;=_ECJsLf8{v%R-r#RxnnN!O5D-O<$KV_C6Gz%1pZ)qKP=l zFu^d4rn@eJ8AUUZYq)7g0%H$SGl<`K4P#FLGsPEG;6N65o3+NzP2Ud3J{`o7GLAw% zp2XlHuEau2pV^DJ8?v%#B@X7?8wAdeWNa;_FqyLr6ud-0G3O@#d6*}N99p050XfR; zBebSUR*|76-7?j!!~PNVF&-7hgA?&@NPvkK!BnMJUkEsLbtZ|Wrj999jD3wxb$S3tZx=n(*uK;`5ajIAbMZa7y~H2~`o0M{UAzLc8nQMIY75#VZgY3W9w&5!p$bM|$HM@qp?;8(2mTxcnzJ#%fb`-E zVa=AUX6*Zy0PF^E`~t@A3?dM-Vf&S|9yka{*?a8JkIUwP$2U*`LVD>9&=jzO+GgTW z6i?AbP&av{DB`feZsnxvS<;yI015NeOP}rHzZk|Nn@W^-EOFy(#@4`!=Z|oWqpB1g zX=ZYV&txlP?61d=@*+3=vXrs05QqG?xM@Tw9{mB^DgF7|z%;Ol(#iAAr}&E?ly)bS z#W!JsItUAwAB&#ktPSGh01jb1cM$UK02VKSAA+%?Vsy+7RiC+AtD3(A`A5+aS(wgk zUOt(zFTmJ*0B7?e_7WU0v?6!1p{JO%whh{a9#H?L%dS>H4fAK9t5W!0MOUAIROCl- z({U8+jZvidH=VKU45r7e#Hv$EX997iJdE*{WW6S3asuh|%NS{j^y|hW?kQqy3EJuB zFW`bArmFBepyM0D5waV{k}oD@IAc|> zFg9QUk|pg!7QtJjltdsC*fwXT{dZv_49p!)^D>N$AAg%MrHEL9XRktv<>}lMK zDu6_0z{IBg3BX%Ut_&E3Y594`3+1%TM;DQkO*xyIvFDC~&rZNFY`zf-X97mZ&evFh zxevHvx&ivp@F=vWD;G16YWi*(+W$M$C#okm;UqM3V1#q2kAtAI?-3yPvd$w4sY}X% za5(MzvHtHQajyg$L@KB@4Fv^Rb7w93pNA0|lS9=tZ;S|5 z4lHKuhXc5SQ%AZjRi>a-vy9!IqNMv#-t=4yb{~MP+aX!~5hC^hGpZjO29tRLerC0W zYm95~1yd%#2u`~L`%09($qz`+8bn$9Fs$DI<)9W=w|B=g_5yk|2nH<{4a%U~mQUiq zFfd}!9o!L{2eEP?ejhJzw89G5q5xc^*d0CqZU7@naAu;RyN(12!y>KK^k9!3VB+l7 z6^0VU^XwVcxiElwkasMOf7`kgntJ4uvA{=y#sul5HJ3@tBr3?&< z9tQ2@J#CK}S|79-o9=$wvxY_nJ%GFN9V)fq^zd1gSV({dZGtQ+U9Q7jGXZh(W3tpm zZxBQ;lrso9ng&Na;e|DU*m$-e0bPK?4{o6(SPd3E4wKd>m>ACxN|FkB3(USudX<#0JG2)G{WbPr zQdG8mQO?*+mjoPv90z}#-2l?sjbdK>Ps)Dn77>ubE$syvfIBE z+V~!{FSR7iP=#+hwHuZa_Aqt0#M=OcN*xgi8kRZn=MxM5`~!bXd)J~GB5oWp&w#~v z5(V8=Ji>f4Y`p{VO;XEefYVZ^VG6aNX{4WRyB}kD9?WU_Y83ehq)1B@(N@A(C6M)o z$M$?ZV_U$h-bq|gn&|`#C+$x6#_{NgHJ35AnR2taX)*9ys8%C49as-{5*+J&F}NvR zR5`)0NsN&#%**4Z@BQa%qm&10rOim5A;FX@fnPiXyr%WHG4>)5tDJfh`c+tbsfsicXXr000KLQBLjxpCEWy?EhdBSsG#5 z1&*QCPgDI%;v6MCVT2)r)M`u>fLs8$ZD~7})lO3Wo7^6COAlB?$^-zbcNWQQ=)-K} zXZ7bT$eW%>6XLNAbPsxumy44ayaw+<2i3T96(w9w!<-s z8*e+HEqT|$mCyxlfYyS=xCvfxX>UBR^!4k-{wl~grg<&s6aV57&?;T0=iq&h_Qgd?BNF_{tm$u z-{Ftawp813IPXgmX64ZxF!@hIpQz51^I*9SY5)_LLWcyLQ>JW&Z1~_|4NIds;&>ST zybcL7O~ugv0~sSqxtI(si~by{`2@`G2rNvQAW1U$hfc#*92FYIO%Yxk>YzCMYtziD zvEKu|8d1ni;|9a+L?=e%q266+AR>JvcAya%m2>v)-qsaZU3C8?Mwr=J9gq1#<2CBEd}KhR(?9vdDN7x&wzhUKAQ zjENqoO=>@86-?=O)Mi4WY~K|Pe>vCw`|UgXK)&^W8XBrxR3DX;D^XSa4l3Dyc@pd_ zT+TrT;Q0@5! zpI>eo*cRiJ2~?~rcZ|Z^R1ONczZ*}mW*TfGUxR}P@rDHDA?!ih-Y}$45$h`k{_!h$ zjCH>u!TdLzL%rRY;Fjl5J5*XvmGD-uQ23Qvwy7Xi4}2r<9#xC!avD*;k;mAojk%kk z_l{`;u}-DzZn)3-Ww~Az37_)r)`i*a31OE{;}KS2n2?-w#OQrP9mfY|>njF0%+JHE zKMptGxD`(KDMNyN8aLZ^s`tK==zpM28?lKPv9Z@^BR27JEK+A_Lp%`={VC~#U=ko> z{A2hnvUs^l>^~ zDMNMkanqB%us=ibJKEzD?f@#eHtUVtCj@f8=ce?b*q#bQ{Nwic1jLUe<1PWxCqU*+ z<&fv|VfoOuY2$Qkb5R`o+j@M$H0)Gwyq&R0$e6GI?Och7*n~gI_jhQD6jy`&jwgCE zw&@tW9PHF78|yVg7}oBcN}(f`-y<#hZnVE>F1!je zfbmNEev7od{nlXv%Z8kbO^z=>@=;)kzQhz6^$viO021Xng&zTZM_DUO{+^DZeX#xl z?nHV5oeJt9QQpP3e&o>;P-D3%+Ln^ro)Gu6CA{MU-cMQLJ3iozgpLn*)AQXd_$<1d zJ0&I2=X@AF)Y7j$i=GS8|MO?j{T*pN>tS^5S@i4>84S;>gL(|fuHB5if{~kqcdjwv z02RvEBJ7^g!0ZX*%8Rfsbebu;Ee!elDL)w+=Yw=ldee@i4+x9@i@{iQVu&a0ptNdA z8-l-6C@F!Gj-V!RYC*RPQ{l(jocN=MNwaYx#dt6R33jGLL}@20CRNg%Ya9s(b9aSunXqV`Agt&_D1=WGovcs81=NGs%`>f6 zqqH76(X(KsIRpmVkHNEGJ!FDbCPF(@Y|Vwn>p`MvF(kO?5y&qL2~l_iGT8vfF1JU0 zgLX#%g^M1PaYDN16MzS0Tx(FWmD-ODp24>cGeOM?L*lWWuumlJLp&ptyE}@;)14(r91l z!u_ZHc=-0OFvwrRhm^p55J$NmbKnZpgvp!wFIcu~i?RFg3V^@R^sy2-%8M9+OJ7FP z1@eBh+^&*)C$p}@ttaT;G(QkC(Mm?NKVnM1RNTuzgOiV2fFzcZl7U1fy^SQ4`vDV# zm#xQ1gK45F zhHD}$E4M&84DN1V0_d_7-04|6y zJ3dCd74b3Ei0T1hI7W?MsFX=c zPH8}Hhl!zLy3)(G2m`8ziVGCjNO?*vx=#(-rx#;X45V4@IgV|LZC3~7Z6#7jXMt4< zavJChze?5^6?0$|cF^Kcd9+8kH=GQFI+ay} zn^Ky$6NdAYOexI=0O-4#zb9MYBG(`o&N$zX0-tqqX8K`2 z^+4zQ4FgRjMw|hYrghYtp-SSp_2w{!BcwMcf`C+;(=<>)wF&wcK>MqwZLHrIrE|EF zYaM2k&ZAa)$0PCSoTRe{%GeSiov7eA1irWdl=*nFV^n9{r~@ER-}^fpzi;6EJG>`C zp1$vheKC*in9#)G!?BJkGy+fC|Aq>I5YiwTzxMuDpwfRZfd9ex&B9Ef2YdOZgtOx2`2Q0) zG-Cf(4eA3EY%kP4`+q|~d;9-y2=E)N!T-^i8;797!wcsa0!-EC!-rH-Zo};g_=^<< zG*edqMMpO=3@flCEAp!~H?aY>x}rvjv|TenZw?7wrOZZtgB~ipNI3)3&)ZNC9&<&V zhG`>VTBJmtQEU&qxOb>ni&ktm6}t$<5Hz=!&a4Pp0E^e?Jz<>(z(^5^7n*sLT+Ct~ zztBSVPJ5Bqkh`#jLcYa?4sN9-uO2RR;z^d@U;YBmcxtHTjyr>RqPdk;xO%uS=1e@= zY8UzP)_)hhpuRk=f~!8`6JhN!RnGzZH(__CO;x9T>F(PV19BTiXGSMsdj@ty4>N{~ zNG2#JG=u6Wi!a6sK;8Rg!o=5%6-J2(whjpug7|VbxDhSF;g*2YGjPjSd;pS^qpi3} zi>%rGXw5)*O2tKlH~Dmu|NYr`2^JPDGp-fZ4=3p)0p23qjyL<^a1cgnMkNoAdK$n< z0F}2}lbFLqorDW8V>S+gp>&zNi98X z4DM(_AHN4sd&nD%%~}&&A6^4in?`_lv_7nflj}n<6me0B9Pu|&0zYZ+f)dM12bd(u`QYLmMFzN5b9P|#(-a>Z&^|sbIiRaHI;Gz4j zA_g&F(SqJ_O5~knlN!Re3<#Uv!Z9=rVf*-tc8v6n__HulVh|%eSYu-Nu1ko-hBt8- z5~(_gc^M$A9U(nFqTGY&{ud0A=*$KEDrW zUn$`Ot~>x`?L{R&qi2?p*pYXQ%dkJu_SDxOaP!}ba2Hs;_n*n4JXL+}pFJX`VgEwB zV7JkIz=l-S{IAVoU25A+|5_o;S76*@+ETyo!Oi76a6noe_d|TttdaE2cW@3TEbpwu za$ZgQF@E4-=z|ZV$*Z{eDvY5XEbHyI{=mE)tyn6CJ9;C26mhe*^;hDK#;eG~9ReMjF6?3rWGC3#XtGDu z!4j688Z%mfh?*m2>_%KNL$c`@<)AsrIj!fQxxB3yh2!UL?v1(d;onjZ|CBVzg;5~H z#J9ePYlv3>noT)COgXK34iG;v3dd!Cr5`m_@EYD!d;OeLvYn~}F|qilUfmV|beeL2 zm~vcDGksuhorU+Js$cya-(~Y)>>+9Fux^H;>A~_KHl3@t;l{_SYU|H&T}tX1iw%~0 z(OB*v%FTebUUHnuSw4X@N4=#+{1R_*E@bQ}S=+xAqLBi%hX7j39eNv}?Ffy4_S)Xr zWS{84Kd0G^gI}PSp5b#2!tVP>lu~BV0|HN?xbq^kLHX7zkl(eFmTxUZKJLDds)n9* zabCWQ9%%7Hac||^X|(;hE>8b2nAC&8oL>33@k#*YoVOf@k6r;V2f*XkG4>JV z008RHBS6l{aB1i<8Z(}}agNE-Fq`q@jkETAt-kp*fYduNt5`9JvfS5A(0c-?-22OL zZUb^7bKU|5dUi+V)XijUgI;IX9T=il)yw{!<0-~uL;kK`3%|2?rhK?yt}_})N&GoKb%?%=uq%|9DB@3j9!GVc~1IIk;T z6j++cOVvLG!uCJVm1mm$(CS8SLt|x2J>Fiw&|A~1v5C2{4_k*9yHh+J`Bh6QFY@Y8 zQ*&c&{k&k-9N$u{3W_cA$;y!LCVUOW8!Un1!I-a!A`lGu^#F=naHe!}Z**V0`|y5W zA3l!n-&erxW~(iAw@pRY5^7xo7R4d7mfd?h>H&hBd*y?^@v{&}=0 zE)6&*@qU5RmvU?1(QA2_Yi?0#K}~7#oYIn#;?k-@Zy>#ur|!R_f=_e=E_#f|2eLQt zs6fL|o*h|LSd~{=Q&pW;ls~s1uO{I3^RfE}&)^Q5D9#H+ZQy+jc#G$h6cy$bmU>G| z@PUE6>cH~3yzlN;P& zw}w3lVbn76OIEPgOOb(YE@bqwF?^h%vHx_5p`W{O=0HZTX`S2XTU1F>j_)xvHzB(( zGHCa{*++uk-_p`|ARE>@P!}P_#nP+Mn()1!=E|xNDc=W0JTk(#FNT7^bvw8I5y8_v%q^+ zF=;@5>LOB`8O@jtRW*1iJHE-$SXIY3_&2Z7x1^Hz!RVzmOB$;h8lb3^O)Ya6-Z(>y z96C9F#Xy!b0(bPK`d~oWqd*$uV$lj#16M;Q6g3uUC06r{kdDgb9#ZRKjhsK=#6SkEsHAU$3;MMrDQ+*N8qz4 z(JRji0v4f#xxTs@77H1XA5p55-+Evv7`!o6%jS3|*Voq9HZz>83sn4rcefA6G&Ofo zGpi0<8ZCOJz88xrGWZ3NDfuj{T>?s1Fkj%AXi+eqUfhH4K+rchnkwl79g-k609Cav z7xh`e=zUO)E%odJcq~D-;ME}i3_*P`aNmT6xv+2ryRZjX?`;mU2lqMxQ5^pA7lD;A zVq6rx_NcL04oiQMqK^Xa$B2w!bmmZsVs1;{9`2UDT?eu>v@BJkvY`=Q{aDKWf`!m% z?=rR#f(R?Y=*>+e|CNiXnycrt<|!=Tu!_n4l+c(J=dWPa^FoAU1QTIygX0NCjFy-h z89ge3kAy62X<&3%P!dAKtt-&EUsDSauUZ!D)Q7#~5W&%>{AQ?7J)^tJzPWRoyq)x} z55+$1T#QoFe0D`3KUTQq-DV7YU}b{n8`u~tM)E^}v8BQjsDF>AbQzSb(^ilV!s!i{ zHNHwI;8Fvc*wcaOarh?e>cH|i(Otnq8i9>M#PO|xBXJ@wX9Y;t`c?vMTL~J`7CR9d z`XUSto+~9&5*PU9OP(2ZVw7G36?&q*z@`WhAH^mZD1i*L1s2DPc>Y6RY%h_se{a0F zkw??kL3I^Gw|)s}4IZfp9Qik|88);(4!Tq=nc}UMU#zJ1HF_Dn@o|=Bi zM4clTfmg`B5XJOPlR9rb!`m7IH-FCK!w&UB7napmv$cT%okeozY?Q>8av%t73ZmY6 ze7XnS^Rk7pED(R1FAjXvS;UJUy9EZ7iP%8QS3H|<4wR*c?*5FBjFI{#L!zX&Q0XO$ zR5=$PgkgiNI#GQwPFo?gORAa{u?CE=Hr|C$Lb|7*eVL-UrJ>G?nPCY_4OJ|H($ZZ~ z*;~yh(jH4?Wh_(`cI1^%zcJn-EAV{^o>;33j7}8;{q)i|Ouw3udan!g12(mZ`Jj$8 z63vY;=&X0U@z$t-Vlq0*u%xzmekF{06}|_zjF~2*dVMv%Q`6*S3or>zrOyMgOQ3+T z+YR%*i@cSniSHz_A0Us!{ir7P2AM=}^L$@T6T_Rl$aHrN{N*n^CMU1GhtO_%2utc9 zCZd8q1KIbOaqtZ$b|SDmP2{@vmFo#dRV&bYu5w{m)_&?kzTM>pGtAqG+_OJkLR;#RJ(5;Bf`V_ zzyrs$$r5_+ADtog@}x9OH`+u)$F!l?-lbB}vbqFfGsTpik3kVIWuX0LMrZvnj^r1! zSCFoWBIZoGnNN;F;K-M}^8iMN1~g#~>f%Ezbv5`R6-&Ta zY%Jr6Q!CMXH?*e_pB#dVM4veP6a-ZhL3HW}UvCOd6z@YQXfBkKnnp?6D4kVMN!r5I zznye(;OA_S&bI~9azyO(1Sm>7{z}=$`Iw?u9JGEU3{4e2vc;YP;9Xi(-CRk?8NCP@ zUk964U)k(4w)p~vV8Yb%@%1l;<4gNJ-}1RUwhc_yR28D+ZLFN{UCOQrgmo1&qYi+q z3hLKLeB_4V{e2osTEF7qe)_14x1PMS%9iH2eaTH^^s@80zQ!e0jWw7Mf&$N8fqGO{ zHqNW`;k!&_aI%7v(+YMO#%HXreryXE_GMQ+BfkV{8HFCv{42Y6<%FPT#_07xveiCX zvy2|~gw~#6G~nziQ0I!O8c$6XzM|I1aJV#Z`U`#^Uqf@nIt4Fg3@rZ}uj@gZvh;Z_ zb|g4r(qgnBwXW*oWsE*60wJae$x#|e?;&!d=!6-hGB~ui^=E<4KH@z*H-kv6XRyL^ zA;@q-u_A1$t!`p;D?~2O?#0Mxh4M?Fr2S!THM4>DI%pH}zAt%qo_~E$5kHn5=af=I zHjGZ-(1+64Rv0tsm=oJ^?g-t)N50V28rT6w52&I}JNq>Ycv%(;{9+b;_$JM6(Gc>l z15fo7^WFErI@EaONMdrE*F2xy1vSv9gmd)8eoXh7yvqWx1eOPm$f8BQdbSO+6pVg@ znVN=wN63k>1m5W_;_B$d|GNG`yXY;Ji)!ojbqejI>+68v(&aiD11Y##HMWsPX9;~v zjcrE1gEps0J}HUMm+7c}hT8#^Oo}msOSL|BKDt!Z*jTlUwGI!g&l9T$B_L%HIn1=C zAdi{e@YR7)>|Hdzs0Eq~gCEmwh>9_s=Lg2+i|%ps?*5QmSAye=j_d@EOyM!{(%{fH zPlov#;fRvV(S41;`=iB7{wR57F{PU3(`kFD(xeX&Xs8Nv-+b0R(MF^OF|Y|&H{BG@-FGeF}faniLJJJvb0I#V4<-!K+^;{MERwGfkmP; zo1XL`J4x1;UgJ-SMREGLM@{WK4Adb|9OyNTCnd6E7+Yy)p-c1FFencx0=qMCtVope zU4f`#(W&!hh|I)h_>|Q_Q+L0dpyUJ-xbCUU z`g^DVtG2#8u6raTtHXv6qepInD?XyQ1t8obtWg3-KII8!xR-EIu6mz$;e!L=B_d_~ zbdW#Ci%&OdlSQRQLnFH!HMLJeNK%1?Q+ z2pb%Dq(tQUXlsNr=!0qy%oa>!H1E?BWeqTHSQ}R(9h(sS+t-E9 zp_jo$IC;n5+S(v)T5Ob+!Hq5}^E3v&G>NbzdQwb!Wkkas%q`Wvx;lI+l3hxH z2Lsznd8~p5g97_M<0brsz%QjDF^3-USyTn@N2B+PVL`u~c`(<}#2|(H(ZIkmk>IE) z)R^-BRdsctaTQVc+==$V6x5oUCQWPFP2)p1ZIX>iY}bv#)|fv-e@qdI%XW9uy=J?6 zv)8qVgcV6m_o>+y7N-a5GWiPHe%et++xrsY>DTtFNR0jJj6f8S>9cr_ zA$CS00n!UngEi7NO8SDfY`V7M?B4mt+Hw_EkFU!nB$~X^TGmSsswUkXSA7j=2ctnI zwi_a~csj0XPRl-QY{#*Df5(eVV}irRud=A$OsLL-y!Q&KokHMAf#n-K-e82`%AJva zu|$UprQ;YMOE#=SUOd(tZ6D|!NC>N#gSDEl*EX!%hE!)`D-6z|N@GM%45>-S(`SZM zTf-OFmiGJt@dcM0b!~&%Y+n5mmipnlPEP|$Q`4|rO`jp20y%M@Fd^)N6XfY%SB2*Q zKmc>o!g39Mz92I+T_%I!F7XNdI z=m;mNVxgeCt1rHlukHEicqVRb#|HuZ8%ulVmY&$Ryc7 zK0}>y{o{RV+m`o%M9K(fVL}c?*sUn=%GHhaU&7sQ{b`@mJg^H~-8ujd7G5Mq%rqQT zSgsi+qmCJEw`%4JjE=Us$mcdD5kp4xL{dcuH^E*3O+y;;^Dc{tU``pB&glD63*(Vh zXXINwpHxi`@hX5Zn%x`U(cdT4z{JZiaFB$h43T5uRYRSpUG%xNqT6MB$%D^LV_sqV zuB`j2uym}Ud^RFFAmSIEzzM*oU_Gw_T`Ae2s|VEn=2k$|j=L8Q`CDNeaN-Ag>44hO z`yD1V>6wVVf{!2ue(7YL7_jXQ(`$2ERc`BV;c(5jPPtid z7T%HwTrsG%g7$WL@Q(As-j@J=$Pd^oLBl`i(nW@V zl{1yHa^tujN{@pPMsQ8ZL0sMo*P$Bs<+JFh`u$h P9#zXn7gxvCKO6o7POmLC delta 38548 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

nRMNdMP=d33QCtd+{ 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 diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 7f7bb4247381400745a635febf5a295801e3748b..373c93f533844fe4db0630dd96d5766dff3e560d 100755 GIT binary patch delta 5944 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 delta 5938 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 diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 131746904204cc3faa563e28912e7372e759f5d6..e61db822b6762d0cb5272178d5d7dd6abc135219 100755 GIT binary patch delta 16765 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 T4uT&(7ya3vMt>IkpDX_liiWTQ delta 16532 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 diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 58dff40f048c99f93b6798bf1de26963f302c4ba..e3c02138b403c00bed0215d39817b1911f8517d8 100755 GIT binary patch delta 17567 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 diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index 3e476d5ec4aa9dcad450d17a744e092d10a0ad1b..bbe3d0bf3f77459fc19e8678a92134d6d24733d7 100755 GIT binary patch delta 19575 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 Cs6wIu diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index 4a91dfb6fa7a597bfddbf2d0998380faf73486c3..3313aacbb7141bd7c52996d84b6f6c49da189dc7 100755 GIT binary patch delta 54096 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⪼SA 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 Date: Thu, 26 May 2022 16:41:38 +0200 Subject: [PATCH 077/197] [fix]: governance overflow, proposal validation --- apps/src/lib/client/rpc.rs | 121 +++++------ apps/src/lib/client/tx.rs | 62 ++++-- .../lib/node/ledger/shell/finalize_block.rs | 184 +---------------- apps/src/lib/node/ledger/shell/governance.rs | 195 ++++++++++++++++++ apps/src/lib/node/ledger/shell/mod.rs | 1 + shared/src/ledger/governance/parameters.rs | 18 ++ shared/src/ledger/governance/utils.rs | 35 ++-- shared/src/types/token.rs | 6 + 8 files changed, 347 insertions(+), 275 deletions(-) create mode 100644 apps/src/lib/node/ledger/shell/governance.rs diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 34652ac825..597205656b 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -13,11 +13,12 @@ use async_std::prelude::*; use borsh::BorshDeserialize; use itertools::Itertools; use namada::ledger::governance::storage as gov_storage; -use namada::ledger::governance::utils::Votes; +use namada::ledger::governance::utils::{Votes, VotePower}; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::types::{ Epoch as PosEpoch, VotingPower, WeightedValidator, }; +use namada::ledger::governance::parameters::GovParams; use namada::ledger::pos::{ self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, }; @@ -414,7 +415,7 @@ pub async fn query_proposal_result( } }; } else { - eprintln!("Either id or offline should be used as arguments."); + eprintln!("Either --proposal-id or --data-path should be provided as arguments."); cli::safe_exit(1) } } @@ -427,45 +428,8 @@ pub async fn query_protocol_parameters( ) { let client = HttpClient::new(args.query.ledger_address).unwrap(); - println!("Goveranance parameters"); - let key = gov_storage::get_max_proposal_code_size_key(); - let max_proposal_code_size = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!( - "{:4}Max. proposal code size: {}", - "", max_proposal_code_size - ); - - let key = gov_storage::get_max_proposal_content_key(); - let max_proposal_content = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!( - "{:4}Max. proposal content size: {}", - "", max_proposal_content - ); - - let key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_fund = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!("{:4}Min. proposal funds: {}", "", min_proposal_fund); - - let key = gov_storage::get_min_proposal_grace_epoch_key(); - let min_proposal_grace_epoch = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!( - "{:4}Min. proposal grace epoch: {}", - "", min_proposal_grace_epoch - ); - - let key = gov_storage::get_min_proposal_period_key(); - let min_proposal_period = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!("{:4}Min. proposal period: {}", "", min_proposal_period); + let gov_parameters = get_governance_parameters(&client).await; + println!("Governance Parameters\n {:4}", gov_parameters); println!("Protocol parameters"); let key = param_storage::get_epoch_storage_key(); @@ -1558,9 +1522,9 @@ pub async fn get_proposal_votes( query_storage_prefix::(client.clone(), vote_prefix_key) .await; - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_validators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap = HashMap::new(); + let mut nay_delegators: HashMap = HashMap::new(); if let Some(vote_iter) = vote_iter { for (key, vote) in vote_iter { @@ -1570,7 +1534,7 @@ pub async fn get_proposal_votes( if vote.is_yay() && validators.contains(&voter_address) { let amount = get_validator_stake(client, epoch, &voter_address).await; - yay_validators.insert(voter_address, amount); + yay_validators.insert(voter_address, VotePower::from(amount)); } else if !validators.contains(&voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key) @@ -1587,9 +1551,9 @@ pub async fn get_proposal_votes( .await; if let Some(amount) = delegator_token_amount { if vote.is_yay() { - yay_delegators.insert(voter_address, amount); + yay_delegators.insert(voter_address, VotePower::from(amount)); } else { - nay_delegators.insert(voter_address, amount); + nay_delegators.insert(voter_address, VotePower::from(amount)); } } } @@ -1612,9 +1576,9 @@ pub async fn get_proposal_offline_votes( let proposal_hash = proposal.compute_hash(); - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_validators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap = HashMap::new(); + let mut nay_delegators: HashMap = HashMap::new(); for path in files { let file = File::open(&path).expect("Proposal file must exist."); @@ -1641,7 +1605,7 @@ pub async fn get_proposal_offline_votes( &proposal_vote.address, ) .await; - yay_validators.insert(proposal_vote.address, amount); + yay_validators.insert(proposal_vote.address, VotePower::from(amount)); } else if is_delegator_at( client, &proposal_vote.address, @@ -1669,9 +1633,9 @@ pub async fn get_proposal_offline_votes( "Delegation key should contain validator address.", ); if proposal_vote.vote.is_yay() { - yay_delegators.insert(validator_address, amount); + yay_delegators.insert(validator_address, VotePower::from(amount)); } else { - nay_delegators.insert(validator_address, amount); + nay_delegators.insert(validator_address, VotePower::from(amount)); } } } @@ -1701,7 +1665,7 @@ pub async fn compute_tally( nay_delegators, } = votes; - let mut total_yay_stacked_tokens = Amount::from(0); + let mut total_yay_stacked_tokens = VotePower::from(0 as u64); for (_, amount) in yay_validators.clone().into_iter() { total_yay_stacked_tokens += amount; } @@ -1788,8 +1752,8 @@ pub async fn get_total_staked_tokes( client: &HttpClient, epoch: Epoch, validators: &[Address], -) -> token::Amount { - let mut total = Amount::from(0); +) -> VotePower { + let mut total = VotePower::from(0 as u64); for validator in validators { total += get_validator_stake(client, epoch, validator).await; @@ -1801,7 +1765,7 @@ async fn get_validator_stake( client: &HttpClient, epoch: Epoch, validator: &Address, -) -> token::Amount { +) -> VotePower { let total_voting_power_key = pos::validator_total_deltas_key(validator); let total_voting_power = query_storage_value::( client, @@ -1811,9 +1775,12 @@ async fn get_validator_stake( .expect("Total deltas should be defined"); let epoched_total_voting_power = total_voting_power.get(epoch); if let Some(epoched_total_voting_power) = epoched_total_voting_power { - token::Amount::from_change(epoched_total_voting_power) + match VotePower::try_from(epoched_total_voting_power) { + Ok(voting_power) => voting_power, + Err(_) => VotePower::from(0 as u64), + } } else { - token::Amount::from(0) + VotePower::from(0 as u64) } } @@ -1836,3 +1803,39 @@ pub async fn get_delegators_delegation( } delegation_addresses } + +pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { + let key = gov_storage::get_max_proposal_code_size_key(); + let max_proposal_code_size = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_max_proposal_content_key(); + let max_proposal_content_size = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_min_proposal_fund_key(); + let min_proposal_fund = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_min_proposal_grace_epoch_key(); + let min_proposal_grace_epochs = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_min_proposal_period_key(); + let min_proposal_period = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + return GovParams { + min_proposal_fund: u64::from(min_proposal_fund), + max_proposal_code_size: u64::from(max_proposal_code_size), + min_proposal_period: u64::from(min_proposal_period), + max_proposal_content_size: u64::from(max_proposal_content_size), + min_proposal_grace_epochs: u64::from(min_proposal_grace_epochs), + } + +} \ No newline at end of file diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 1d41ebbc77..29519ec8b2 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -532,7 +532,43 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let proposal: Proposal = serde_json::from_reader(file).expect("JSON was not well-formatted"); + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let signer = WalletAddress::new(proposal.clone().author.to_string()); + let goverance_parameters = rpc::get_governance_parameters(&client).await; + let current_epoch = rpc::query_epoch(args::Query { + ledger_address: args.tx.ledger_address.clone(), + }) + .await; + + if proposal.voting_start_epoch <= current_epoch || proposal.voting_start_epoch.0 % 3 == 0 { + eprintln!( + "Invalid proposal start epoch: {} must be greater than current \ + epoch {} and a multiple of 3", + proposal.voting_start_epoch, current_epoch + ); + safe_exit(1) + } else if proposal.voting_end_epoch <= proposal.voting_start_epoch + || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 + < goverance_parameters.min_proposal_period || proposal.voting_end_epoch.0 % 3 == 0 + { + eprintln!( + "Invalid proposal end epoch: difference between proposal start \ + and end epoch must be at least {} and end epoch must be a multiple of 3", + goverance_parameters.min_proposal_period + ); + safe_exit(1) + } else if proposal.grace_epoch <= proposal.voting_end_epoch + || proposal.grace_epoch.0 - proposal.voting_end_epoch.0 + < goverance_parameters.min_proposal_grace_epochs + { + eprintln!( + "Invalid proposal grace epoch: difference between proposal grace \ + and end epoch must be at least {}", + goverance_parameters.min_proposal_grace_epochs + ); + safe_exit(1) + } if args.offline { let signer = ctx.get(&signer); @@ -556,8 +592,6 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { } } } else { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); - let tx_data: Result = proposal.clone().try_into(); let init_proposal_data = if let Ok(data) = tx_data { data @@ -566,35 +600,21 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { safe_exit(1) }; - let min_proposal_funds_key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_funds: Amount = - rpc::query_storage_value(&client, &min_proposal_funds_key) - .await - .unwrap(); let balance = rpc::get_token_balance(&client, &m1t(), &proposal.author) .await .unwrap_or_default(); - if balance < min_proposal_funds { + if balance < token::Amount::from(goverance_parameters.min_proposal_fund) { eprintln!( "Address {} doesn't have enough funds.", &proposal.author ); safe_exit(1); } - let min_proposal_funds_key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_funds: Amount = - rpc::query_storage_value(&client, &min_proposal_funds_key) - .await - .unwrap(); - let balance = rpc::get_token_balance(&client, &m1t(), &proposal.author) - .await - .unwrap_or_default(); - if balance < min_proposal_funds { - eprintln!( - "Address {} doesn't have enough funds.", - &proposal.author - ); + if init_proposal_data.content.len() + > goverance_parameters.max_proposal_content_size as usize + { + eprintln!("Proposal content size too big.",); safe_exit(1); } diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 1f758a2f2e..a52876492c 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,20 +1,11 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell -use namada::ledger::governance::storage as gov_storage; -use namada::ledger::governance::utils::{ - compute_tally, get_proposal_votes, ProposalEvent, -}; -use namada::ledger::governance::vp::ADDRESS as gov_address; -use namada::ledger::storage::types::encode; -use namada::ledger::treasury::ADDRESS as treasury_address; -use namada::types::address::{xan as m1t, Address}; -use namada::types::governance::TallyResult; -use namada::types::storage::{BlockHash, Epoch, Header}; +use namada::types::storage::{BlockHash, Header}; use tendermint_proto::abci::Misbehavior as Evidence; use tendermint_proto::crypto::PublicKey as TendermintPublicKey; +use super::governance::execute_governance_proposals; use super::*; -use crate::node::ledger::events::EventType; impl Shell where @@ -53,175 +44,8 @@ where let (height, new_epoch) = self.update_state(req.header, req.hash, req.byzantine_validators); - if new_epoch { - for id in std::mem::take(&mut self.proposal_data) { - let proposal_funds_key = gov_storage::get_funds_key(id); - let proposal_start_epoch_key = - gov_storage::get_voting_start_epoch_key(id); - - let funds = self - .read_storage_key::(&proposal_funds_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal funds.".to_string(), - ) - })?; - let proposal_start_epoch = self - .read_storage_key::(&proposal_start_epoch_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal start_epoch.".to_string(), - ) - })?; - - let votes = - get_proposal_votes(&self.storage, proposal_start_epoch, id); - let tally_result = - compute_tally(&self.storage, proposal_start_epoch, votes); - - let transfer_address = match tally_result { - TallyResult::Passed => { - let proposal_author_key = - gov_storage::get_author_key(id); - let proposal_author = self - .read_storage_key::

(&proposal_author_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal author.".to_string(), - ) - })?; - - let proposal_code_key = - gov_storage::get_proposal_code_key(id); - let proposal_code = - self.read_storage_key_bytes(&proposal_code_key); - match proposal_code { - Some(proposal_code) => { - let tx = - Tx::new(proposal_code, Some(encode(&id))); - let tx_type = TxType::Decrypted( - DecryptedTx::Decrypted(tx), - ); - let pending_execution_key = - gov_storage::get_proposal_execution_key(id); - self.storage - .write(&pending_execution_key, "") - .expect( - "Should be able to write to storage.", - ); - let tx_result = protocol::apply_tx( - tx_type, - 0, /* this is used to compute the fee - * based on the code size. We dont - * need it here. */ - &mut BlockGasMeter::default(), - &mut self.write_log, - &self.storage, - &mut self.vp_wasm_cache, - &mut self.tx_wasm_cache, - ); - self.storage - .delete(&pending_execution_key) - .expect( - "Should be able to delete the storage.", - ); - match tx_result { - Ok(tx_result) => { - if tx_result.is_accepted() { - self.write_log.commit_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal - .to_string(), - TallyResult::Passed, - id, - true, - true, - ) - .into(); - response - .events - .push(proposal_event); - - proposal_author - } else { - self.write_log.drop_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal - .to_string(), - TallyResult::Passed, - id, - true, - false, - ) - .into(); - response - .events - .push(proposal_event); - - treasury_address - } - } - Err(_e) => { - self.write_log.drop_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed, - id, - true, - false, - ) - .into(); - response.events.push(proposal_event); - - treasury_address - } - } - } - None => { - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed, - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - - proposal_author - } - } - } - TallyResult::Rejected | TallyResult::Unknown => { - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Rejected, - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - - treasury_address - } - }; - - // transfer proposal locked funds - self.storage.transfer( - &m1t(), - funds, - &gov_address, - &transfer_address, - ); - } - } + let _proposals_result = + execute_governance_proposals(self, new_epoch, &mut response)?; for processed_tx in &req.txs { let tx = if let Ok(tx) = Tx::try_from(processed_tx.tx.as_ref()) { diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs new file mode 100644 index 0000000000..e65ede6c07 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -0,0 +1,195 @@ +use anoma::ledger::governance::storage as gov_storage; +use anoma::ledger::governance::utils::{ + compute_tally, get_proposal_votes, ProposalEvent, +}; +use anoma::ledger::governance::vp::ADDRESS as gov_address; +use anoma::ledger::storage::types::encode; +use anoma::ledger::storage::{DBIter, StorageHasher, DB}; +use anoma::ledger::treasury::ADDRESS as treasury_address; +use anoma::types::address::{xan as m1t, Address}; +use anoma::types::governance::TallyResult; +use anoma::types::storage::Epoch; +use anoma::types::token; + +use super::*; +use crate::node::ledger::events::EventType; + +pub struct ProposalsResult { + passed: Vec, + rejected: Vec, +} + +pub fn execute_governance_proposals( + shell: &mut Shell, + new_epoch: bool, + response: &mut shim::response::FinalizeBlock, +) -> Result +where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, +{ + let mut proposals_result = ProposalsResult { + passed: Vec::new(), + rejected: Vec::new(), + }; + + if !new_epoch { + return Ok(proposals_result); + } + + for id in std::mem::take(&mut shell.proposal_data) { + let proposal_funds_key = gov_storage::get_funds_key(id); + let proposal_start_epoch_key = + gov_storage::get_voting_start_epoch_key(id); + + let funds = shell + .read_storage_key::(&proposal_funds_key) + .ok_or_else(|| { + Error::BadProposal(id, "Invalid proposal funds.".to_string()) + })?; + let proposal_start_epoch = shell + .read_storage_key::(&proposal_start_epoch_key) + .ok_or_else(|| { + Error::BadProposal( + id, + "Invalid proposal start_epoch.".to_string(), + ) + })?; + + let votes = + get_proposal_votes(&shell.storage, proposal_start_epoch, id); + let tally_result = + compute_tally(&shell.storage, proposal_start_epoch, votes); + + let transfer_address = match tally_result { + TallyResult::Passed => { + let proposal_author_key = gov_storage::get_author_key(id); + let proposal_author = shell + .read_storage_key::
(&proposal_author_key) + .ok_or_else(|| { + Error::BadProposal( + id, + "Invalid proposal author.".to_string(), + ) + })?; + + let proposal_code_key = gov_storage::get_proposal_code_key(id); + let proposal_code = + shell.read_storage_key_bytes(&proposal_code_key); + match proposal_code { + Some(proposal_code) => { + let tx = Tx::new(proposal_code, Some(encode(&id))); + let tx_type = + TxType::Decrypted(DecryptedTx::Decrypted(tx)); + let pending_execution_key = + gov_storage::get_proposal_execution_key(id); + shell + .storage + .write(&pending_execution_key, "") + .expect("Should be able to write to storage."); + let tx_result = protocol::apply_tx( + tx_type, + 0, /* this is used to compute the fee + * based on the code size. We dont + * need it here. */ + &mut BlockGasMeter::default(), + &mut shell.write_log, + &shell.storage, + &mut shell.vp_wasm_cache, + &mut shell.tx_wasm_cache, + ); + shell + .storage + .delete(&pending_execution_key) + .expect("Should be able to delete the storage."); + match tx_result { + Ok(tx_result) => { + if tx_result.is_accepted() { + shell.write_log.commit_tx(); + let proposal_event: Event = + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + id, + true, + true, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.passed.push(id); + + proposal_author + } else { + shell.write_log.drop_tx(); + let proposal_event: Event = + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + id, + true, + false, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.rejected.push(id); + + treasury_address + } + } + Err(_e) => { + shell.write_log.drop_tx(); + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + id, + true, + false, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.rejected.push(id); + + treasury_address + } + } + } + None => { + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + id, + false, + false, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.passed.push(id); + + proposal_author + } + } + } + TallyResult::Rejected | TallyResult::Unknown => { + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Rejected, + id, + false, + false, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.rejected.push(id); + + treasury_address + } + }; + + // transfer proposal locked funds + shell + .storage + .transfer(&m1t(), funds, &gov_address, &transfer_address); + } + + Ok(proposals_result) +} diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 1f9759a2a2..4495cacc28 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -10,6 +10,7 @@ mod init_chain; mod prepare_proposal; mod process_proposal; mod queries; +mod governance; use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; diff --git a/shared/src/ledger/governance/parameters.rs b/shared/src/ledger/governance/parameters.rs index 79c3b4d5b6..5b280491b9 100644 --- a/shared/src/ledger/governance/parameters.rs +++ b/shared/src/ledger/governance/parameters.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use borsh::{BorshDeserialize, BorshSerialize}; use super::storage as gov_storage; @@ -30,6 +32,22 @@ pub struct GovParams { pub min_proposal_grace_epochs: u64, } +impl Display for GovParams { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Min. proposal fund: {}\nMax. proposal code size: {}\nMin. \ + proposal period: {}\nMax. proposal content size: {}\nMin. \ + proposal grace epochs: {}", + self.min_proposal_fund, + self.max_proposal_code_size, + self.min_proposal_period, + self.max_proposal_content_size, + self.min_proposal_grace_epochs + ) + } +} + impl Default for GovParams { fn default() -> Self { Self { diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index aaca277f91..7b03ee1bd6 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -15,15 +15,17 @@ use crate::types::governance::{ProposalVote, TallyResult}; use crate::types::storage::{Epoch, Key}; use crate::types::token; +pub type VotePower = u128; + /// Proposal structure holding votes information necessary to compute the /// outcome pub struct Votes { /// Map from validators who votes yay to their total stake amount - pub yay_validators: HashMap, + pub yay_validators: HashMap, /// Map from delegation who votes yay to their bond amount - pub yay_delegators: HashMap, + pub yay_delegators: HashMap, /// Map from delegation who votes nay to their bond amount - pub nay_delegators: HashMap, + pub nay_delegators: HashMap, } /// Proposal errors @@ -93,7 +95,7 @@ where nay_delegators, } = votes; - let mut total_yay_stacked_tokens = token::Amount::from(0); + let mut total_yay_stacked_tokens = VotePower::from(0 as u64); for (_, amount) in yay_validators.clone().into_iter() { total_yay_stacked_tokens += amount; } @@ -110,7 +112,7 @@ where if yay_validators.contains_key(&validator_address) { total_yay_stacked_tokens -= amount; } - } + } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { TallyResult::Passed @@ -205,9 +207,9 @@ where gov_storage::get_proposal_vote_prefix_key(proposal_id); let (vote_iter, _) = storage.iter_prefix(&vote_prefix_key); - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_validators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap = HashMap::new(); + let mut nay_delegators: HashMap = HashMap::new(); for (key, vote_bytes, _) in vote_iter { let vote_key = Key::from_str(key.as_str()).ok(); @@ -236,12 +238,12 @@ where if vote.is_yay() { yay_delegators.insert( address.clone(), - amount, + VotePower::from(amount), ); } else { nay_delegators.insert( address.clone(), - amount, + VotePower::from(amount), ); } } @@ -300,14 +302,14 @@ fn get_total_stacked_tokens( storage: &Storage, epoch: Epoch, validators: &[Address], -) -> token::Amount +) -> VotePower where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, H: StorageHasher + Sync + 'static, { return validators .iter() - .fold(token::Amount::from(0), |acc, validator| { + .fold(VotePower::from(0 as u64), |acc, validator| { acc + get_validator_stake(storage, epoch, validator) }); } @@ -316,7 +318,7 @@ fn get_validator_stake( storage: &Storage, epoch: Epoch, validator: &Address, -) -> token::Amount +) -> VotePower where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, H: StorageHasher + Sync + 'static, @@ -331,9 +333,12 @@ where if let Some(total_delta) = total_delta { let epoched_total_delta = total_delta.get(epoch); if let Some(epoched_total_delta) = epoched_total_delta { - return token::Amount::from_change(epoched_total_delta); + match VotePower::try_from(epoched_total_delta) { + Ok(voting_power) => return voting_power, + Err(_) => return VotePower::from(0 as u64), + } } } } - token::Amount::from(0) + VotePower::from(0 as u64) } diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index f3e4cd4ed2..4942051826 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -139,6 +139,12 @@ impl From for u64 { } } +impl From for u128 { + fn from(amount: Amount) -> Self { + u128::from(amount.micro) + } +} + impl Add for Amount { type Output = Amount; From 2aee155197527cf2b00ee0cf0ac447bc03cd1b62 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Thu, 26 May 2022 17:23:36 +0200 Subject: [PATCH 078/197] [feat]: added total votes to query --- apps/src/lib/client/rpc.rs | 21 ++++++++++++++++----- shared/src/ledger/governance/utils.rs | 4 +--- shared/src/types/governance.rs | 25 ++++++++++++++++++++++++- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 597205656b..eef53cf7f5 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -13,7 +13,8 @@ use async_std::prelude::*; use borsh::BorshDeserialize; use itertools::Itertools; use namada::ledger::governance::storage as gov_storage; -use namada::ledger::governance::utils::{Votes, VotePower}; +use namada::ledger::governance::utils::Votes; +use namada::types::governance::VotePower; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::types::{ Epoch as PosEpoch, VotingPower, WeightedValidator, @@ -25,7 +26,7 @@ use namada::ledger::pos::{ use namada::ledger::treasury::storage as treasury_storage; use namada::types::address::Address; use namada::types::governance::{ - OfflineProposal, OfflineVote, ProposalVote, TallyResult, + OfflineProposal, OfflineVote, ProposalVote, TallyResult, ProposalResult, }; use namada::types::key::*; use namada::types::storage::{Epoch, PrefixValue}; @@ -1654,7 +1655,7 @@ pub async fn compute_tally( client: &HttpClient, epoch: Epoch, votes: Votes, -) -> TallyResult { +) -> ProposalResult { let validators = get_all_validators(client, epoch).await; let total_stacked_tokens = get_total_staked_tokes(client, epoch, &validators).await; @@ -1685,9 +1686,19 @@ pub async fn compute_tally( } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { - TallyResult::Passed + return ProposalResult{ + result: TallyResult::Passed, + total_voting_power: total_stacked_tokens, + total_yay_power: total_yay_stacked_tokens, + total_nay_power: 0, + } } else { - TallyResult::Rejected + return ProposalResult{ + result: TallyResult::Rejected, + total_voting_power: total_stacked_tokens, + total_yay_power: total_yay_stacked_tokens, + total_nay_power: 0, + } } } diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 7b03ee1bd6..93671f99fa 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -11,12 +11,10 @@ use crate::ledger::pos; use crate::ledger::pos::{BondId, Bonds, ValidatorSets, ValidatorTotalDeltas}; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::types::address::Address; -use crate::types::governance::{ProposalVote, TallyResult}; +use crate::types::governance::{ProposalVote, TallyResult, VotePower}; use crate::types::storage::{Epoch, Key}; use crate::types::token; -pub type VotePower = u128; - /// Proposal structure holding votes information necessary to compute the /// outcome pub struct Votes { diff --git a/shared/src/types/governance.rs b/shared/src/types/governance.rs index c8bf57469f..8ff1d96993 100644 --- a/shared/src/types/governance.rs +++ b/shared/src/types/governance.rs @@ -15,6 +15,8 @@ use super::key::SigScheme; use super::storage::Epoch; use super::transaction::governance::InitProposalData; +pub type VotePower = u128; + #[derive( Debug, Clone, @@ -83,7 +85,28 @@ pub enum TallyResult { Unknown, } -impl fmt::Display for TallyResult { +/// The result with votes of a proposal +pub struct ProposalResult { + pub result: TallyResult, + pub total_voting_power: VotePower, + pub total_yay_power: VotePower, + pub total_nay_power: VotePower, +} + +impl Display for ProposalResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} with {} yay votes over {} ({:.2}%)", + self.result, + self.total_yay_power, + self.total_voting_power, + (self.total_yay_power / self.total_voting_power) * 100 + ) + } +} + +impl Display for TallyResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { TallyResult::Passed => write!(f, "passed"), From 9a08d61d4a29e1c5d767125c7276b9ade0190e4b Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Thu, 26 May 2022 17:54:34 +0200 Subject: [PATCH 079/197] [misc]: clippy, fmt --- apps/src/lib/client/rpc.rs | 41 ++++++++++++++++----------- apps/src/lib/client/tx.rs | 13 ++++++--- apps/src/lib/node/ledger/shell/mod.rs | 2 +- shared/src/ledger/governance/utils.rs | 10 +++---- shared/src/types/governance.rs | 5 ++++ 5 files changed, 44 insertions(+), 27 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index eef53cf7f5..029da55866 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -416,7 +416,10 @@ pub async fn query_proposal_result( } }; } else { - eprintln!("Either --proposal-id or --data-path should be provided as arguments."); + eprintln!( + "Either --proposal-id or --data-path should be provided \ + as arguments." + ); cli::safe_exit(1) } } @@ -1552,9 +1555,11 @@ pub async fn get_proposal_votes( .await; if let Some(amount) = delegator_token_amount { if vote.is_yay() { - yay_delegators.insert(voter_address, VotePower::from(amount)); + yay_delegators + .insert(voter_address, VotePower::from(amount)); } else { - nay_delegators.insert(voter_address, VotePower::from(amount)); + nay_delegators + .insert(voter_address, VotePower::from(amount)); } } } @@ -1606,7 +1611,8 @@ pub async fn get_proposal_offline_votes( &proposal_vote.address, ) .await; - yay_validators.insert(proposal_vote.address, VotePower::from(amount)); + yay_validators + .insert(proposal_vote.address, VotePower::from(amount)); } else if is_delegator_at( client, &proposal_vote.address, @@ -1634,9 +1640,11 @@ pub async fn get_proposal_offline_votes( "Delegation key should contain validator address.", ); if proposal_vote.vote.is_yay() { - yay_delegators.insert(validator_address, VotePower::from(amount)); + yay_delegators + .insert(validator_address, VotePower::from(amount)); } else { - nay_delegators.insert(validator_address, VotePower::from(amount)); + nay_delegators + .insert(validator_address, VotePower::from(amount)); } } } @@ -1666,7 +1674,7 @@ pub async fn compute_tally( nay_delegators, } = votes; - let mut total_yay_stacked_tokens = VotePower::from(0 as u64); + let mut total_yay_stacked_tokens = VotePower::from(0_u64); for (_, amount) in yay_validators.clone().into_iter() { total_yay_stacked_tokens += amount; } @@ -1686,19 +1694,19 @@ pub async fn compute_tally( } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { - return ProposalResult{ + return ProposalResult { result: TallyResult::Passed, total_voting_power: total_stacked_tokens, total_yay_power: total_yay_stacked_tokens, total_nay_power: 0, - } + }; } else { - return ProposalResult{ + return ProposalResult { result: TallyResult::Rejected, total_voting_power: total_stacked_tokens, total_yay_power: total_yay_stacked_tokens, total_nay_power: 0, - } + }; } } @@ -1764,7 +1772,7 @@ pub async fn get_total_staked_tokes( epoch: Epoch, validators: &[Address], ) -> VotePower { - let mut total = VotePower::from(0 as u64); + let mut total = VotePower::from(0_u64); for validator in validators { total += get_validator_stake(client, epoch, validator).await; @@ -1788,10 +1796,10 @@ async fn get_validator_stake( if let Some(epoched_total_voting_power) = epoched_total_voting_power { match VotePower::try_from(epoched_total_voting_power) { Ok(voting_power) => voting_power, - Err(_) => VotePower::from(0 as u64), + Err(_) => VotePower::from(0_u64), } } else { - VotePower::from(0 as u64) + VotePower::from(0_u64) } } @@ -1847,6 +1855,5 @@ pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { min_proposal_period: u64::from(min_proposal_period), max_proposal_content_size: u64::from(max_proposal_content_size), min_proposal_grace_epochs: u64::from(min_proposal_grace_epochs), - } - -} \ No newline at end of file + }; +} diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 29519ec8b2..a4660f2284 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -541,7 +541,9 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { }) .await; - if proposal.voting_start_epoch <= current_epoch || proposal.voting_start_epoch.0 % 3 == 0 { + if proposal.voting_start_epoch <= current_epoch + || proposal.voting_start_epoch.0 % 3 == 0 + { eprintln!( "Invalid proposal start epoch: {} must be greater than current \ epoch {} and a multiple of 3", @@ -550,11 +552,13 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { safe_exit(1) } else if proposal.voting_end_epoch <= proposal.voting_start_epoch || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 - < goverance_parameters.min_proposal_period || proposal.voting_end_epoch.0 % 3 == 0 + < goverance_parameters.min_proposal_period + || proposal.voting_end_epoch.0 % 3 == 0 { eprintln!( "Invalid proposal end epoch: difference between proposal start \ - and end epoch must be at least {} and end epoch must be a multiple of 3", + and end epoch must be at least {} and end epoch must be a \ + multiple of 3", goverance_parameters.min_proposal_period ); safe_exit(1) @@ -603,7 +607,8 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let balance = rpc::get_token_balance(&client, &m1t(), &proposal.author) .await .unwrap_or_default(); - if balance < token::Amount::from(goverance_parameters.min_proposal_fund) { + if balance < token::Amount::from(goverance_parameters.min_proposal_fund) + { eprintln!( "Address {} doesn't have enough funds.", &proposal.author diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 4495cacc28..336fc781ea 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -6,11 +6,11 @@ //! (unless we can simply overwrite them in the next block). //! More info in . mod finalize_block; +mod governance; mod init_chain; mod prepare_proposal; mod process_proposal; mod queries; -mod governance; use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 93671f99fa..aeb75c53d0 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -93,7 +93,7 @@ where nay_delegators, } = votes; - let mut total_yay_stacked_tokens = VotePower::from(0 as u64); + let mut total_yay_stacked_tokens = VotePower::from(0_u64); for (_, amount) in yay_validators.clone().into_iter() { total_yay_stacked_tokens += amount; } @@ -110,7 +110,7 @@ where if yay_validators.contains_key(&validator_address) { total_yay_stacked_tokens -= amount; } - } + } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { TallyResult::Passed @@ -307,7 +307,7 @@ where { return validators .iter() - .fold(VotePower::from(0 as u64), |acc, validator| { + .fold(VotePower::from(0_u64), |acc, validator| { acc + get_validator_stake(storage, epoch, validator) }); } @@ -333,10 +333,10 @@ where if let Some(epoched_total_delta) = epoched_total_delta { match VotePower::try_from(epoched_total_delta) { Ok(voting_power) => return voting_power, - Err(_) => return VotePower::from(0 as u64), + Err(_) => return VotePower::from(0_u64), } } } } - VotePower::from(0 as u64) + VotePower::from(0_u64) } diff --git a/shared/src/types/governance.rs b/shared/src/types/governance.rs index 8ff1d96993..abe65f9d37 100644 --- a/shared/src/types/governance.rs +++ b/shared/src/types/governance.rs @@ -15,6 +15,7 @@ use super::key::SigScheme; use super::storage::Epoch; use super::transaction::governance::InitProposalData; +/// Type alias for vote power pub type VotePower = u128; #[derive( @@ -87,9 +88,13 @@ pub enum TallyResult { /// The result with votes of a proposal pub struct ProposalResult { + /// The result of a proposal pub result: TallyResult, + /// The total voting power during the proposal tally pub total_voting_power: VotePower, + /// The total voting power from yay votes pub total_yay_power: VotePower, + /// The total voting power from nay votes (unused at the moment) pub total_nay_power: VotePower, } From afe779a6a08fce87d450b0710f57a185e0b1d325 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 11:25:09 +0200 Subject: [PATCH 080/197] [fix]: clippy, fmt --- apps/src/lib/client/rpc.rs | 17 ++++++++--------- apps/src/lib/client/tx.rs | 11 +++++++---- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 029da55866..3167895bcc 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1538,7 +1538,7 @@ pub async fn get_proposal_votes( if vote.is_yay() && validators.contains(&voter_address) { let amount = get_validator_stake(client, epoch, &voter_address).await; - yay_validators.insert(voter_address, VotePower::from(amount)); + yay_validators.insert(voter_address, amount); } else if !validators.contains(&voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key) @@ -1611,8 +1611,7 @@ pub async fn get_proposal_offline_votes( &proposal_vote.address, ) .await; - yay_validators - .insert(proposal_vote.address, VotePower::from(amount)); + yay_validators.insert(proposal_vote.address, amount); } else if is_delegator_at( client, &proposal_vote.address, @@ -1694,19 +1693,19 @@ pub async fn compute_tally( } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { - return ProposalResult { + ProposalResult { result: TallyResult::Passed, total_voting_power: total_stacked_tokens, total_yay_power: total_yay_stacked_tokens, total_nay_power: 0, - }; + } } else { - return ProposalResult { + ProposalResult { result: TallyResult::Rejected, total_voting_power: total_stacked_tokens, total_yay_power: total_yay_stacked_tokens, total_nay_power: 0, - }; + } } } @@ -1849,11 +1848,11 @@ pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { .await .expect("Parameter should be definied."); - return GovParams { + GovParams { min_proposal_fund: u64::from(min_proposal_fund), max_proposal_code_size: u64::from(max_proposal_code_size), min_proposal_period: u64::from(min_proposal_period), max_proposal_content_size: u64::from(max_proposal_content_size), min_proposal_grace_epochs: u64::from(min_proposal_grace_epochs), - }; + } } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index a4660f2284..8f286215f8 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -541,13 +541,15 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { }) .await; - if proposal.voting_start_epoch <= current_epoch + if proposal.voting_start_epoch >= current_epoch || proposal.voting_start_epoch.0 % 3 == 0 { eprintln!( "Invalid proposal start epoch: {} must be greater than current \ - epoch {} and a multiple of 3", - proposal.voting_start_epoch, current_epoch + epoch {} and a multiple of {}", + proposal.voting_start_epoch, + current_epoch, + goverance_parameters.min_proposal_period ); safe_exit(1) } else if proposal.voting_end_epoch <= proposal.voting_start_epoch @@ -558,7 +560,8 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { eprintln!( "Invalid proposal end epoch: difference between proposal start \ and end epoch must be at least {} and end epoch must be a \ - multiple of 3", + multiple of {}", + goverance_parameters.min_proposal_period, goverance_parameters.min_proposal_period ); safe_exit(1) From ee303e5419d550b1d51777c7d46247827c9e4d63 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 11:48:03 +0200 Subject: [PATCH 081/197] [fix]: clippy, fmt --- apps/src/lib/client/rpc.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 3167895bcc..237f5d32de 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1824,35 +1824,35 @@ pub async fn get_delegators_delegation( pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { let key = gov_storage::get_max_proposal_code_size_key(); - let max_proposal_code_size = query_storage_value::(&client, &key) + let max_proposal_code_size = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_max_proposal_content_key(); - let max_proposal_content_size = query_storage_value::(&client, &key) + let max_proposal_content_size = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_fund = query_storage_value::(&client, &key) + let min_proposal_fund = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_min_proposal_grace_epoch_key(); - let min_proposal_grace_epochs = query_storage_value::(&client, &key) + let min_proposal_grace_epochs = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_min_proposal_period_key(); - let min_proposal_period = query_storage_value::(&client, &key) + let min_proposal_period = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); GovParams { min_proposal_fund: u64::from(min_proposal_fund), - max_proposal_code_size: u64::from(max_proposal_code_size), - min_proposal_period: u64::from(min_proposal_period), - max_proposal_content_size: u64::from(max_proposal_content_size), - min_proposal_grace_epochs: u64::from(min_proposal_grace_epochs), + max_proposal_code_size, + min_proposal_period, + max_proposal_content_size, + min_proposal_grace_epochs, } } From 124b2d975e0af7921a3c52e438f21d26bcc256d6 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 12:47:28 +0200 Subject: [PATCH 082/197] [fix]: bad validation condition --- apps/src/lib/client/tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8f286215f8..9decbc127c 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -541,7 +541,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { }) .await; - if proposal.voting_start_epoch >= current_epoch + if proposal.voting_start_epoch <= current_epoch || proposal.voting_start_epoch.0 % 3 == 0 { eprintln!( From 4373e5d2fbb70a10479e0a18f2dc831f24a21973 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 15:37:28 +0200 Subject: [PATCH 083/197] [fix]: governance vp author address, proposal submission validation --- apps/src/lib/client/tx.rs | 6 ++++-- shared/src/ledger/governance/vp.rs | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 9decbc127c..84270e773a 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -542,8 +542,10 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .await; if proposal.voting_start_epoch <= current_epoch - || proposal.voting_start_epoch.0 % 3 == 0 + || proposal.voting_start_epoch.0 % goverance_parameters.min_proposal_period != 0 { + println!("{}", proposal.voting_start_epoch <= current_epoch); + println!("{}", proposal.voting_start_epoch.0 % goverance_parameters.min_proposal_period == 0); eprintln!( "Invalid proposal start epoch: {} must be greater than current \ epoch {} and a multiple of {}", @@ -555,7 +557,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { } else if proposal.voting_end_epoch <= proposal.voting_start_epoch || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 < goverance_parameters.min_proposal_period - || proposal.voting_end_epoch.0 % 3 == 0 + || proposal.voting_end_epoch.0 % 3 != 0 { eprintln!( "Invalid proposal end epoch: difference between proposal start \ diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index 6ccfc7a33e..aa66fa95d6 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -88,15 +88,18 @@ where let has_pre_author = ctx.has_key_pre(&author_key).ok(); match (has_pre_author, author) { (Some(has_pre_author), Some(author)) => { - // TODO: if author is an implicit address, we should asssume its - // existence we should reuse the same logic as in - // check_address_existence in shared/src/vm/host_env.rs - let address_exist_key = Key::validity_predicate(&author); - let address_exist = ctx.has_key_post(&address_exist_key).ok(); - if let Some(address_exist) = address_exist { - !has_pre_author && verifiers.contains(&author) && address_exist - } else { - false + match author { + Address::Established(_) => { + let address_exist_key = Key::validity_predicate(&author); + let address_exist = ctx.has_key_post(&address_exist_key).ok(); + if let Some(address_exist) = address_exist { + !has_pre_author && verifiers.contains(&author) && address_exist + } else { + false + } + }, + Address::Implicit(_) => !has_pre_author && verifiers.contains(&author), + Address::Internal(_) => return false, } } _ => false, From f7cb4c09348bb4b8d0d5684815956bd11056d840 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 15:44:50 +0200 Subject: [PATCH 084/197] [feat]: vote transaction validation --- apps/src/lib/client/tx.rs | 28 +++++++++++++++++++++++++--- shared/src/ledger/governance/vp.rs | 30 ++++++++++++++++-------------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 84270e773a..68e7dced01 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -542,10 +542,17 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .await; if proposal.voting_start_epoch <= current_epoch - || proposal.voting_start_epoch.0 % goverance_parameters.min_proposal_period != 0 + || proposal.voting_start_epoch.0 + % goverance_parameters.min_proposal_period + != 0 { println!("{}", proposal.voting_start_epoch <= current_epoch); - println!("{}", proposal.voting_start_epoch.0 % goverance_parameters.min_proposal_period == 0); + println!( + "{}", + proposal.voting_start_epoch.0 + % goverance_parameters.min_proposal_period + == 0 + ); eprintln!( "Invalid proposal start epoch: {} must be greater than current \ epoch {} and a multiple of {}", @@ -692,6 +699,8 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } } else { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let current_epoch = + rpc::query_epoch(args::Query { ledger_address }).await; let voter_address = ctx.get(signer); let proposal_id = args.proposal_id.unwrap(); @@ -705,6 +714,14 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { match proposal_start_epoch { Some(epoch) => { + if current_epoch < epoch { + eprintln!( + "Current epoch {} is not greater than proposal start \ + epoch ", + current_epoch, epoch + ); + safe_exit(1) + } let mut delegation_addresses = rpc::get_delegators_delegation( &client, &voter_address, @@ -752,7 +769,12 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { process_tx(ctx, &args.tx, tx, Some(signer)).await; } None => { - eprintln!("Proposal start epoch is not in the storage.") + eprintln!( + "Proposal start epoch is for proposal id {} is not \ + definied.", + proposal_id + ); + safe_exit(1) } } } diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index aa66fa95d6..7f50f85fc7 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -87,21 +87,23 @@ where let author = read(ctx, &author_key, ReadType::POST).ok(); let has_pre_author = ctx.has_key_pre(&author_key).ok(); match (has_pre_author, author) { - (Some(has_pre_author), Some(author)) => { - match author { - Address::Established(_) => { - let address_exist_key = Key::validity_predicate(&author); - let address_exist = ctx.has_key_post(&address_exist_key).ok(); - if let Some(address_exist) = address_exist { - !has_pre_author && verifiers.contains(&author) && address_exist - } else { - false - } - }, - Address::Implicit(_) => !has_pre_author && verifiers.contains(&author), - Address::Internal(_) => return false, + (Some(has_pre_author), Some(author)) => match author { + Address::Established(_) => { + let address_exist_key = Key::validity_predicate(&author); + let address_exist = ctx.has_key_post(&address_exist_key).ok(); + if let Some(address_exist) = address_exist { + !has_pre_author + && verifiers.contains(&author) + && address_exist + } else { + false + } } - } + Address::Implicit(_) => { + !has_pre_author && verifiers.contains(&author) + } + Address::Internal(_) => return false, + }, _ => false, } } From 4903744b272e3bcfd5bd9d3e72fd50f55f53553a Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 15:56:46 +0200 Subject: [PATCH 085/197] [fix]: error println --- apps/src/lib/client/tx.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 68e7dced01..53bcef6047 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -700,7 +700,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } else { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let current_epoch = - rpc::query_epoch(args::Query { ledger_address }).await; + rpc::query_epoch(args::Query { ledger_address: args.tx.ledger_address.clone() }).await; let voter_address = ctx.get(signer); let proposal_id = args.proposal_id.unwrap(); @@ -717,7 +717,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { if current_epoch < epoch { eprintln!( "Current epoch {} is not greater than proposal start \ - epoch ", + epoch {}", current_epoch, epoch ); safe_exit(1) From 3ca66b288051639f0b59e94b2242923b4d0ff5d0 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 16:08:45 +0200 Subject: [PATCH 086/197] [misc]: clippy, fmt --- apps/src/lib/client/tx.rs | 6 ++++-- shared/src/ledger/governance/vp.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 53bcef6047..2131410839 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -699,8 +699,10 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } } else { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); - let current_epoch = - rpc::query_epoch(args::Query { ledger_address: args.tx.ledger_address.clone() }).await; + let current_epoch = rpc::query_epoch(args::Query { + ledger_address: args.tx.ledger_address.clone(), + }) + .await; let voter_address = ctx.get(signer); let proposal_id = args.proposal_id.unwrap(); diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index 7f50f85fc7..300787fba5 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -102,7 +102,7 @@ where Address::Implicit(_) => { !has_pre_author && verifiers.contains(&author) } - Address::Internal(_) => return false, + Address::Internal(_) => false, }, _ => false, } From 94500543274c1fcbea368f75c7d77010b8481055 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 16:52:07 +0200 Subject: [PATCH 087/197] [fix]: e2e test --- tests/src/e2e/ledger_tests.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 22eb4f4ac5..f438bbad85 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1174,7 +1174,10 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string("Transaction is invalid.")?; + client.exp_string( + "Invalid proposal end epoch: difference between proposal start and \ + end epoch must be at least 3 and end epoch must be a multiple of 3", + )?; client.assert_success(); // 7. Check invalid proposal was not accepted From 6cbf9dcc242c632c641b825d16ba26638c1c423b Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 17:36:49 +0200 Subject: [PATCH 088/197] [fix]: e2e test --- tests/src/e2e/ledger_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index f438bbad85..fd08f3a3df 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1402,8 +1402,8 @@ fn proposal_offline() -> Result<()> { }, "author": albert, "voting_start_epoch": 3, - "voting_end_epoch": 6, - "grace_epoch": 6 + "voting_end_epoch": 9, + "grace_epoch": 18 } ); generate_proposal_json( From ecbefd0ebfa87401d7655029a2250b19e9bbab4e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 30 May 2022 18:17:20 +0200 Subject: [PATCH 089/197] Fixes e2e tests --- tests/src/e2e/ledger_tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index fd08f3a3df..63c99efecb 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1079,7 +1079,6 @@ fn proposal_submission() -> Result<()> { client.assert_success(); // 3. Query the proposal - let proposal_query_args = vec![ "query-proposal", "--proposal-id", @@ -1161,6 +1160,7 @@ fn proposal_submission() -> Result<()> { "grace_epoch": 10009, } ); + generate_proposal_json( invalid_proposal_json_path.clone(), invalid_proposal_json, @@ -1178,7 +1178,7 @@ fn proposal_submission() -> Result<()> { "Invalid proposal end epoch: difference between proposal start and \ end epoch must be at least 3 and end epoch must be a multiple of 3", )?; - client.assert_success(); + client.assert_failure(); // 7. Check invalid proposal was not accepted let proposal_query_args = vec![ @@ -1337,7 +1337,7 @@ fn proposal_submission() -> Result<()> { let mut client = run!(test, Bin::Client, query_protocol_parameters, Some(30))?; - client.exp_regex(".*Min. proposal grace epoch: 9.*")?; + client.exp_regex(".*Min. proposal grace epochs: 9.*")?; client.assert_success(); Ok(()) From e49d8b06449ba38725fb22f179fdc82368bba30e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 31 May 2022 17:16:56 +0200 Subject: [PATCH 090/197] Fixes test artifacts folder persistence --- apps/src/lib/client/rpc.rs | 13 ++++-- apps/src/lib/client/tx.rs | 22 +++++++--- tests/src/e2e/ledger_tests.rs | 83 +++++++++++------------------------ 3 files changed, 53 insertions(+), 65 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 237f5d32de..7c744b9693 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -349,7 +349,14 @@ pub async fn query_proposal_result( if entry.file_name().eq(&"proposal") { is_proposal_present = true - } else { + } else if entry + .file_name() + .to_string_lossy() + .starts_with("proposal-vote-") + { + // Folder may contain other + // files than just the proposal + // and the votes files.insert(entry.path()); } } @@ -371,8 +378,8 @@ pub async fn query_proposal_result( if !is_proposal_present { eprintln!( - "The folder must contain a the offline \ - proposal in a file named proposal" + "The folder must contain the offline proposal \ + in a file named \"proposal\"" ); cli::safe_exit(1) } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 2131410839..feb5194340 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -596,11 +596,18 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .await; let offline_proposal = OfflineProposal::new(proposal, signer, &signing_key); - let proposal_filename = "proposal".to_string(); + let proposal_filename = args + .proposal_data + .parent() + .expect("No parent found") + .join("proposal"); let out = File::create(&proposal_filename).unwrap(); match serde_json::to_writer_pretty(out, &offline_proposal) { Ok(_) => { - println!("Proposal created: {}.", proposal_filename); + println!( + "Proposal created: {}.", + proposal_filename.to_string_lossy() + ); } Err(e) => { eprintln!("Error while creating proposal file: {}.", e); @@ -685,12 +692,17 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { &signing_key, ); - let proposal_vote_filename = - format!("proposal-vote-{}", &signer.to_string()); + let proposal_vote_filename = proposal_file_path + .parent() + .expect("No parent found") + .join(format!("proposal-vote-{}", &signer.to_string())); let out = File::create(&proposal_vote_filename).unwrap(); match serde_json::to_writer_pretty(out, &offline_vote) { Ok(_) => { - println!("Proposal vote created: {}.", proposal_vote_filename); + println!( + "Proposal vote created: {}.", + proposal_vote_filename.to_string_lossy() + ); } Err(e) => { eprintln!("Error while creating proposal vote file: {}.", e); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 63c99efecb..feefeb9d46 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -9,8 +9,6 @@ //! To keep the temporary files created by a test, use env var //! `ANOMA_E2E_KEEP_TEMP=true`. -use std::fs::{self, OpenOptions}; -use std::path::PathBuf; use std::process::Command; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -24,7 +22,6 @@ use namada_apps::config::genesis::genesis_config::{ use serde_json::json; use setup::constants::*; -use super::setup::working_dir; use crate::e2e::helpers::{ find_address, find_voting_power, get_actor_rpc, get_epoch, }; @@ -1034,8 +1031,7 @@ fn proposal_submission() -> Result<()> { client.assert_success(); // 2. Submit valid proposal - let valid_proposal_json_path = - test.test_dir.path().join("valid_proposal.json"); + let test_dir = tempfile::tempdir_in(test.base_dir.path()).unwrap(); let proposal_code = wasm_abs_path(TX_PROPOSAL_CODE); let albert = find_address(&test, ALBERT)?; @@ -1059,18 +1055,18 @@ fn proposal_submission() -> Result<()> { "proposal_code_path": proposal_code.to_str().unwrap() } ); - - generate_proposal_json( - valid_proposal_json_path.clone(), - valid_proposal_json, - ); + let proposal_file = + tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); + serde_json::to_writer(&proposal_file, &valid_proposal_json).unwrap(); + let proposal_path = proposal_file.path(); + let proposal_ref = proposal_path.to_string_lossy(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let submit_proposal_args = vec![ "init-proposal", "--data-path", - valid_proposal_json_path.to_str().unwrap(), + &proposal_ref, "--ledger-address", &validator_one_rpc, ]; @@ -1123,8 +1119,6 @@ fn proposal_submission() -> Result<()> { // 6. Submit an invalid proposal // proposal is invalid due to voting_end_epoch - voting_start_epoch < 3 - let invalid_proposal_json_path = - test.test_dir.path().join("invalid_proposal.json"); let albert = find_address(&test, ALBERT)?; let invalid_proposal_json = json!( { @@ -1160,16 +1154,17 @@ fn proposal_submission() -> Result<()> { "grace_epoch": 10009, } ); - - generate_proposal_json( - invalid_proposal_json_path.clone(), - invalid_proposal_json, - ); + let invalid_proposal_file = + tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); + serde_json::to_writer(&invalid_proposal_file, &invalid_proposal_json) + .unwrap(); + let invalid_proposal_path = invalid_proposal_file.path(); + let invalid_proposal_ref = invalid_proposal_path.to_string_lossy(); let submit_proposal_args = vec![ "init-proposal", "--data-path", - invalid_proposal_json_path.to_str().unwrap(), + &invalid_proposal_ref, "--ledger-address", &validator_one_rpc, ]; @@ -1384,8 +1379,8 @@ fn proposal_offline() -> Result<()> { client.assert_success(); // 2. Create an offline proposal - let valid_proposal_json_path = - test.test_dir.path().join("valid_proposal.json"); + let test_dir = tempfile::tempdir_in(test.base_dir.path()).unwrap(); + let albert = find_address(&test, ALBERT)?; let valid_proposal_json = json!( { @@ -1406,17 +1401,18 @@ fn proposal_offline() -> Result<()> { "grace_epoch": 18 } ); - generate_proposal_json( - valid_proposal_json_path.clone(), - valid_proposal_json, - ); + let proposal_file = + tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); + serde_json::to_writer(&proposal_file, &valid_proposal_json).unwrap(); + let proposal_path = proposal_file.path(); + let proposal_ref = proposal_path.to_string_lossy(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let offline_proposal_args = vec![ "init-proposal", "--data-path", - valid_proposal_json_path.to_str().unwrap(), + &proposal_ref, "--offline", "--ledger-address", &validator_one_rpc, @@ -1433,7 +1429,7 @@ fn proposal_offline() -> Result<()> { epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } - let proposal_path = working_dir().join("proposal"); + let proposal_path = test_dir.path().join("proposal"); let proposal_ref = proposal_path.to_string_lossy(); let submit_proposal_vote = vec![ "vote-proposal", @@ -1453,31 +1449,17 @@ fn proposal_offline() -> Result<()> { client.assert_success(); let expected_file_name = format!("proposal-vote-{}", albert); - let expected_path_vote = working_dir().join(&expected_file_name); + let expected_path_vote = test_dir.path().join(&expected_file_name); assert!(expected_path_vote.exists()); - let expected_path_proposal = working_dir().join("proposal"); + let expected_path_proposal = test_dir.path().join("proposal"); assert!(expected_path_proposal.exists()); // 4. Compute offline tally - let proposal_data_folder = working_dir().join("proposal-test-data"); - fs::create_dir_all(&proposal_data_folder) - .expect("Should create a new folder."); - fs::copy( - expected_path_proposal, - &proposal_data_folder.join("proposal"), - ) - .expect("Should copy proposal file."); - fs::copy( - expected_path_vote, - &proposal_data_folder.join(&expected_file_name), - ) - .expect("Should copy proposal vote file."); - let tally_offline = vec![ "query-proposal-result", "--data-path", - proposal_data_folder.to_str().unwrap(), + test_dir.path().to_str().unwrap(), "--offline", "--ledger-address", &validator_one_rpc, @@ -1490,19 +1472,6 @@ fn proposal_offline() -> Result<()> { Ok(()) } -fn generate_proposal_json( - proposal_path: PathBuf, - proposal_content: serde_json::Value, -) { - let intent_writer = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(proposal_path) - .unwrap(); - serde_json::to_writer(intent_writer, &proposal_content).unwrap(); -} - /// In this test we: /// 1. Setup 2 genesis validators /// 2. Initialize a new network with the 2 validators From 2a385bd918291be19ad2c44f67af8b2721952e7e Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Mon, 13 Jun 2022 16:32:24 +0200 Subject: [PATCH 091/197] [fix]: votes accumulation --- apps/src/lib/client/rpc.rs | 72 ++++++++++++++++++++------- apps/src/lib/client/tx.rs | 2 + shared/src/ledger/governance/utils.rs | 67 +++++++++++++++---------- shared/src/types/governance.rs | 5 +- 4 files changed, 99 insertions(+), 47 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 7c744b9693..d31059ff35 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -232,6 +232,8 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { println!("{:4}Status: on-going", ""); } else { let votes = get_proposal_votes(client, start_epoch, id).await; + println!("{:?}", votes.yay_delegators); + println!("{:?}", votes.yay_validators); let proposal_result = compute_tally(client, start_epoch, votes).await; println!("{:4}Status: done", ""); @@ -1534,8 +1536,8 @@ pub async fn get_proposal_votes( .await; let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap> = HashMap::new(); + let mut nay_delegators: HashMap> = HashMap::new(); if let Some(vote_iter) = vote_iter { for (key, vote) in vote_iter { @@ -1562,11 +1564,25 @@ pub async fn get_proposal_votes( .await; if let Some(amount) = delegator_token_amount { if vote.is_yay() { - yay_delegators - .insert(voter_address, VotePower::from(amount)); + match yay_delegators.get_mut(&voter_address) { + Some(map) => { + map.insert(validator_address, VotePower::from(amount)); + }, + None => { + let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); + yay_delegators.insert(voter_address, delegations_map); + } + } } else { - nay_delegators - .insert(voter_address, VotePower::from(amount)); + match nay_delegators.get_mut(&voter_address) { + Some(map) => { + map.insert(validator_address, VotePower::from(amount)); + }, + None => { + let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); + nay_delegators.insert(voter_address, delegations_map); + } + } } } } @@ -1590,8 +1606,8 @@ pub async fn get_proposal_offline_votes( let proposal_hash = proposal.compute_hash(); let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap> = HashMap::new(); + let mut nay_delegators: HashMap> = HashMap::new(); for path in files { let file = File::open(&path).expect("Proposal file must exist."); @@ -1646,11 +1662,25 @@ pub async fn get_proposal_offline_votes( "Delegation key should contain validator address.", ); if proposal_vote.vote.is_yay() { - yay_delegators - .insert(validator_address, VotePower::from(amount)); + match yay_delegators.get_mut(&proposal_vote.address) { + Some(map) => { + map.insert(validator_address, VotePower::from(amount)); + }, + None => { + let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); + yay_delegators.insert(proposal_vote.address.clone(), delegations_map); + } + } } else { - nay_delegators - .insert(validator_address, VotePower::from(amount)); + match nay_delegators.get_mut(&proposal_vote.address) { + Some(map) => { + map.insert(validator_address, VotePower::from(amount)); + }, + None => { + let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); + nay_delegators.insert(proposal_vote.address.clone(), delegations_map); + } + } } } } @@ -1686,20 +1716,24 @@ pub async fn compute_tally( } // YAY: Add delegator amount whose validator didn't vote / voted nay - for (validator_address, amount) in yay_delegators.into_iter() { - if !yay_validators.contains_key(&validator_address) { - total_yay_stacked_tokens += amount; + for (_, vote_map) in yay_delegators.iter() { + for (validator_address, vote_power) in vote_map.into_iter() { + if !yay_validators.contains_key(&validator_address) { + total_yay_stacked_tokens += vote_power; + } } } // NAY: Remove delegator amount whose validator validator vote yay - for (validator_address, amount) in nay_delegators.into_iter() { - if yay_validators.contains_key(&validator_address) { - total_yay_stacked_tokens -= amount; + for (_, vote_map) in nay_delegators.iter() { + for (validator_address, vote_power) in vote_map.into_iter() { + if yay_validators.contains_key(&validator_address) { + total_yay_stacked_tokens -= vote_power; + } } } - if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { + if total_yay_stacked_tokens >= (total_stacked_tokens / 3) * 2 { ProposalResult { result: TallyResult::Passed, total_voting_power: total_stacked_tokens, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index feb5194340..1d68780aa3 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -767,6 +767,8 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { .await; } + println!("{:?}", delegation_addresses); + let tx_data = VoteProposalData { id: proposal_id, vote: args.vote, diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index aeb75c53d0..790ce3ec1f 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -21,9 +21,9 @@ pub struct Votes { /// Map from validators who votes yay to their total stake amount pub yay_validators: HashMap, /// Map from delegation who votes yay to their bond amount - pub yay_delegators: HashMap, + pub yay_delegators: HashMap>, /// Map from delegation who votes nay to their bond amount - pub nay_delegators: HashMap, + pub nay_delegators: HashMap>, } /// Proposal errors @@ -98,21 +98,26 @@ where total_yay_stacked_tokens += amount; } + // YAY: Add delegator amount whose validator didn't vote / voted nay - for (validator_address, amount) in yay_delegators.into_iter() { - if !yay_validators.contains_key(&validator_address) { - total_yay_stacked_tokens += amount; + for (_, vote_map) in yay_delegators.iter() { + for (validator_address, vote_power) in vote_map.into_iter() { + if !yay_validators.contains_key(&validator_address) { + total_yay_stacked_tokens += vote_power; + } } } // NAY: Remove delegator amount whose validator validator vote yay - for (validator_address, amount) in nay_delegators.into_iter() { - if yay_validators.contains_key(&validator_address) { - total_yay_stacked_tokens -= amount; + for (_, vote_map) in nay_delegators.iter() { + for (validator_address, vote_power) in vote_map.into_iter() { + if yay_validators.contains_key(&validator_address) { + total_yay_stacked_tokens -= vote_power; + } } } - if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { + if total_yay_stacked_tokens >= (total_stacked_tokens / 3) * 2 { TallyResult::Passed } else { TallyResult::Rejected @@ -205,9 +210,9 @@ where gov_storage::get_proposal_vote_prefix_key(proposal_id); let (vote_iter, _) = storage.iter_prefix(&vote_prefix_key); - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_validators= HashMap::new(); + let mut yay_delegators: HashMap> = HashMap::new(); + let mut nay_delegators: HashMap> = HashMap::new(); for (key, vote_bytes, _) in vote_iter { let vote_key = Key::from_str(key.as_str()).ok(); @@ -216,33 +221,43 @@ where (Some(key), Some(vote)) => { let voter_address = gov_storage::get_voter_address(&key); match voter_address { - Some(address) => { - if vote.is_yay() && validators.contains(address) { + Some(voter_address) => { + if vote.is_yay() && validators.contains(voter_address) { let amount = - get_validator_stake(storage, epoch, address); - yay_validators.insert(address.clone(), amount); - } else if !validators.contains(address) { + get_validator_stake(storage, epoch, voter_address); + yay_validators.insert(voter_address.clone(), amount); + } else if !validators.contains(voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key); match validator_address { Some(validator_address) => { let amount = get_bond_amount_at( storage, - address, + voter_address, validator_address, epoch, ); if let Some(amount) = amount { if vote.is_yay() { - yay_delegators.insert( - address.clone(), - VotePower::from(amount), - ); + match yay_delegators.get_mut(&voter_address) { + Some(map) => { + map.insert(validator_address.clone(), VotePower::from(amount)); + }, + None => { + let delegations_map = HashMap::from([(validator_address.clone(), VotePower::from(amount))]); + yay_delegators.insert(voter_address.clone(), delegations_map); + } + } } else { - nay_delegators.insert( - address.clone(), - VotePower::from(amount), - ); + match nay_delegators.get_mut(&voter_address.clone()) { + Some(map) => { + map.insert(validator_address.clone(), VotePower::from(amount)); + }, + None => { + let delegations_map = HashMap::from([(validator_address.clone(), VotePower::from(amount))]); + nay_delegators.insert(voter_address.clone(), delegations_map); + } + } } } } diff --git a/shared/src/types/governance.rs b/shared/src/types/governance.rs index abe65f9d37..8a8577abdc 100644 --- a/shared/src/types/governance.rs +++ b/shared/src/types/governance.rs @@ -13,6 +13,7 @@ use super::hash::Hash; use super::key::common::{self, Signature}; use super::key::SigScheme; use super::storage::Epoch; +use super::token::SCALE; use super::transaction::governance::InitProposalData; /// Type alias for vote power @@ -104,8 +105,8 @@ impl Display for ProposalResult { f, "{} with {} yay votes over {} ({:.2}%)", self.result, - self.total_yay_power, - self.total_voting_power, + self.total_yay_power / SCALE as u128, + self.total_voting_power / SCALE as u128, (self.total_yay_power / self.total_voting_power) * 100 ) } From 20de724b8416d0863a927aea7a52e6e650331b6d Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Mon, 13 Jun 2022 16:33:24 +0200 Subject: [PATCH 092/197] [misc]: remove logs --- apps/src/lib/client/rpc.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d31059ff35..4e83841890 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -232,8 +232,6 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { println!("{:4}Status: on-going", ""); } else { let votes = get_proposal_votes(client, start_epoch, id).await; - println!("{:?}", votes.yay_delegators); - println!("{:?}", votes.yay_validators); let proposal_result = compute_tally(client, start_epoch, votes).await; println!("{:4}Status: done", ""); From 2423a0217ad7a184e345c52a13dc356ce32005d6 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 14 Jun 2022 15:32:02 +0200 Subject: [PATCH 093/197] Fixes `safe_exit` call only if `force` is not set --- apps/src/lib/client/tx.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 1d68780aa3..b5978b4824 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -560,7 +560,9 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { current_epoch, goverance_parameters.min_proposal_period ); - safe_exit(1) + if !args.tx.force { + safe_exit(1) + } } else if proposal.voting_end_epoch <= proposal.voting_start_epoch || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 < goverance_parameters.min_proposal_period @@ -573,7 +575,9 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { goverance_parameters.min_proposal_period, goverance_parameters.min_proposal_period ); - safe_exit(1) + if !args.tx.force { + safe_exit(1) + } } else if proposal.grace_epoch <= proposal.voting_end_epoch || proposal.grace_epoch.0 - proposal.voting_end_epoch.0 < goverance_parameters.min_proposal_grace_epochs @@ -583,7 +587,9 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { and end epoch must be at least {}", goverance_parameters.min_proposal_grace_epochs ); - safe_exit(1) + if !args.tx.force { + safe_exit(1) + } } if args.offline { @@ -734,7 +740,10 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { epoch {}", current_epoch, epoch ); - safe_exit(1) + + if !args.tx.force { + safe_exit(1) + } } let mut delegation_addresses = rpc::get_delegators_delegation( &client, @@ -786,11 +795,13 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } None => { eprintln!( - "Proposal start epoch is for proposal id {} is not \ + "Proposal start epoch for proposal id {} is not \ definied.", proposal_id ); - safe_exit(1) + if !args.tx.force { + safe_exit(1) + } } } } From a6bf03ac3c85fc1673acd54a79440dfd4a401b39 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 14 Jun 2022 18:16:16 +0200 Subject: [PATCH 094/197] Speeds up testing --- tests/src/e2e/ledger_tests.rs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index feefeb9d46..46e6bfca75 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -991,7 +991,19 @@ fn ledger_many_txs_in_a_block() -> Result<()> { /// 13. Check governance address funds are 0 #[test] fn proposal_submission() -> Result<()> { - let test = setup::network(|genesis| genesis, None)?; + let test = setup::network(|genesis| { + let parameters = ParametersConfig { + min_num_of_blocks: 1, + min_duration: 1, + max_expected_time_per_block: 1, + ..genesis.parameters + }; + + GenesisConfig { + parameters, + ..genesis + } + }, None)?; let anomac_help = vec!["--help"]; @@ -1049,9 +1061,9 @@ fn proposal_submission() -> Result<()> { "requires": "2" }, "author": albert, - "voting_start_epoch": 6, - "voting_end_epoch": 18, - "grace_epoch": 24, + "voting_start_epoch": 12, + "voting_end_epoch": 24, + "grace_epoch": 30, "proposal_code_path": proposal_code.to_str().unwrap() } ); @@ -1205,7 +1217,7 @@ fn proposal_submission() -> Result<()> { // 9. Send a yay vote from a validator let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 7 { + while epoch.0 <= 13 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } @@ -1270,7 +1282,7 @@ fn proposal_submission() -> Result<()> { // 11. Query the proposal and check the result let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 19 { + while epoch.0 <= 25 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } @@ -1289,7 +1301,7 @@ fn proposal_submission() -> Result<()> { // 12. Wait proposal grace and check proposal author funds let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 < 26 { + while epoch.0 < 31 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } @@ -1424,7 +1436,7 @@ fn proposal_offline() -> Result<()> { // 3. Generate an offline yay vote let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 5 { + while epoch.0 <= 2 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } From 0a7f0f57f7480fe7852935aca36bfb1c4543072e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 1 Jul 2022 15:43:47 +0200 Subject: [PATCH 095/197] fmt and fix clippy --- apps/src/lib/client/rpc.rs | 96 +++++++++++++++----- apps/src/lib/client/tx.rs | 3 +- apps/src/lib/node/ledger/shell/governance.rs | 10 +- shared/src/ledger/governance/utils.rs | 79 +++++++++++----- tests/src/e2e/ledger_tests.rs | 27 +++--- 5 files changed, 151 insertions(+), 64 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 4e83841890..5f9aa6385d 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1534,8 +1534,10 @@ pub async fn get_proposal_votes( .await; let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap> = HashMap::new(); - let mut nay_delegators: HashMap> = HashMap::new(); + let mut yay_delegators: HashMap> = + HashMap::new(); + let mut nay_delegators: HashMap> = + HashMap::new(); if let Some(vote_iter) = vote_iter { for (key, vote) in vote_iter { @@ -1564,21 +1566,41 @@ pub async fn get_proposal_votes( if vote.is_yay() { match yay_delegators.get_mut(&voter_address) { Some(map) => { - map.insert(validator_address, VotePower::from(amount)); - }, + map.insert( + validator_address, + VotePower::from(amount), + ); + } None => { - let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); - yay_delegators.insert(voter_address, delegations_map); + let delegations_map: HashMap< + Address, + VotePower, + > = HashMap::from([( + validator_address, + VotePower::from(amount), + )]); + yay_delegators + .insert(voter_address, delegations_map); } } } else { match nay_delegators.get_mut(&voter_address) { Some(map) => { - map.insert(validator_address, VotePower::from(amount)); - }, + map.insert( + validator_address, + VotePower::from(amount), + ); + } None => { - let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); - nay_delegators.insert(voter_address, delegations_map); + let delegations_map: HashMap< + Address, + VotePower, + > = HashMap::from([( + validator_address, + VotePower::from(amount), + )]); + nay_delegators + .insert(voter_address, delegations_map); } } } @@ -1604,8 +1626,10 @@ pub async fn get_proposal_offline_votes( let proposal_hash = proposal.compute_hash(); let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap> = HashMap::new(); - let mut nay_delegators: HashMap> = HashMap::new(); + let mut yay_delegators: HashMap> = + HashMap::new(); + let mut nay_delegators: HashMap> = + HashMap::new(); for path in files { let file = File::open(&path).expect("Proposal file must exist."); @@ -1662,21 +1686,45 @@ pub async fn get_proposal_offline_votes( if proposal_vote.vote.is_yay() { match yay_delegators.get_mut(&proposal_vote.address) { Some(map) => { - map.insert(validator_address, VotePower::from(amount)); - }, + map.insert( + validator_address, + VotePower::from(amount), + ); + } None => { - let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); - yay_delegators.insert(proposal_vote.address.clone(), delegations_map); + let delegations_map: HashMap< + Address, + VotePower, + > = HashMap::from([( + validator_address, + VotePower::from(amount), + )]); + yay_delegators.insert( + proposal_vote.address.clone(), + delegations_map, + ); } } } else { match nay_delegators.get_mut(&proposal_vote.address) { Some(map) => { - map.insert(validator_address, VotePower::from(amount)); - }, + map.insert( + validator_address, + VotePower::from(amount), + ); + } None => { - let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); - nay_delegators.insert(proposal_vote.address.clone(), delegations_map); + let delegations_map: HashMap< + Address, + VotePower, + > = HashMap::from([( + validator_address, + VotePower::from(amount), + )]); + nay_delegators.insert( + proposal_vote.address.clone(), + delegations_map, + ); } } } @@ -1715,8 +1763,8 @@ pub async fn compute_tally( // YAY: Add delegator amount whose validator didn't vote / voted nay for (_, vote_map) in yay_delegators.iter() { - for (validator_address, vote_power) in vote_map.into_iter() { - if !yay_validators.contains_key(&validator_address) { + for (validator_address, vote_power) in vote_map.iter() { + if !yay_validators.contains_key(validator_address) { total_yay_stacked_tokens += vote_power; } } @@ -1724,8 +1772,8 @@ pub async fn compute_tally( // NAY: Remove delegator amount whose validator validator vote yay for (_, vote_map) in nay_delegators.iter() { - for (validator_address, vote_power) in vote_map.into_iter() { - if yay_validators.contains_key(&validator_address) { + for (validator_address, vote_power) in vote_map.iter() { + if yay_validators.contains_key(validator_address) { total_yay_stacked_tokens -= vote_power; } } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index b5978b4824..8df5869727 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -795,8 +795,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } None => { eprintln!( - "Proposal start epoch for proposal id {} is not \ - definied.", + "Proposal start epoch for proposal id {} is not definied.", proposal_id ); if !args.tx.force { diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index e65ede6c07..6c88ffd591 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -115,7 +115,7 @@ where true, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.passed.push(id); proposal_author @@ -130,7 +130,7 @@ where false, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.rejected.push(id); treasury_address @@ -146,7 +146,7 @@ where false, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.rejected.push(id); treasury_address @@ -162,7 +162,7 @@ where false, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.passed.push(id); proposal_author @@ -178,7 +178,7 @@ where false, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.rejected.push(id); treasury_address diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 790ce3ec1f..3e75f99e93 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -98,11 +98,10 @@ where total_yay_stacked_tokens += amount; } - // YAY: Add delegator amount whose validator didn't vote / voted nay for (_, vote_map) in yay_delegators.iter() { - for (validator_address, vote_power) in vote_map.into_iter() { - if !yay_validators.contains_key(&validator_address) { + for (validator_address, vote_power) in vote_map.iter() { + if !yay_validators.contains_key(validator_address) { total_yay_stacked_tokens += vote_power; } } @@ -110,8 +109,8 @@ where // NAY: Remove delegator amount whose validator validator vote yay for (_, vote_map) in nay_delegators.iter() { - for (validator_address, vote_power) in vote_map.into_iter() { - if yay_validators.contains_key(&validator_address) { + for (validator_address, vote_power) in vote_map.iter() { + if yay_validators.contains_key(validator_address) { total_yay_stacked_tokens -= vote_power; } } @@ -210,9 +209,11 @@ where gov_storage::get_proposal_vote_prefix_key(proposal_id); let (vote_iter, _) = storage.iter_prefix(&vote_prefix_key); - let mut yay_validators= HashMap::new(); - let mut yay_delegators: HashMap> = HashMap::new(); - let mut nay_delegators: HashMap> = HashMap::new(); + let mut yay_validators = HashMap::new(); + let mut yay_delegators: HashMap> = + HashMap::new(); + let mut nay_delegators: HashMap> = + HashMap::new(); for (key, vote_bytes, _) in vote_iter { let vote_key = Key::from_str(key.as_str()).ok(); @@ -223,9 +224,13 @@ where match voter_address { Some(voter_address) => { if vote.is_yay() && validators.contains(voter_address) { - let amount = - get_validator_stake(storage, epoch, voter_address); - yay_validators.insert(voter_address.clone(), amount); + let amount = get_validator_stake( + storage, + epoch, + voter_address, + ); + yay_validators + .insert(voter_address.clone(), amount); } else if !validators.contains(voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key); @@ -239,23 +244,55 @@ where ); if let Some(amount) = amount { if vote.is_yay() { - match yay_delegators.get_mut(&voter_address) { + match yay_delegators + .get_mut(voter_address) + { Some(map) => { - map.insert(validator_address.clone(), VotePower::from(amount)); - }, + map.insert( + validator_address + .clone(), + VotePower::from(amount), + ); + } None => { - let delegations_map = HashMap::from([(validator_address.clone(), VotePower::from(amount))]); - yay_delegators.insert(voter_address.clone(), delegations_map); + let delegations_map = + HashMap::from([( + validator_address + .clone(), + VotePower::from( + amount, + ), + )]); + yay_delegators.insert( + voter_address.clone(), + delegations_map, + ); } } } else { - match nay_delegators.get_mut(&voter_address.clone()) { + match nay_delegators + .get_mut(&voter_address.clone()) + { Some(map) => { - map.insert(validator_address.clone(), VotePower::from(amount)); - }, + map.insert( + validator_address + .clone(), + VotePower::from(amount), + ); + } None => { - let delegations_map = HashMap::from([(validator_address.clone(), VotePower::from(amount))]); - nay_delegators.insert(voter_address.clone(), delegations_map); + let delegations_map = + HashMap::from([( + validator_address + .clone(), + VotePower::from( + amount, + ), + )]); + nay_delegators.insert( + voter_address.clone(), + delegations_map, + ); } } } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 46e6bfca75..037bdb64d8 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -991,19 +991,22 @@ fn ledger_many_txs_in_a_block() -> Result<()> { /// 13. Check governance address funds are 0 #[test] fn proposal_submission() -> Result<()> { - let test = setup::network(|genesis| { - let parameters = ParametersConfig { - min_num_of_blocks: 1, - min_duration: 1, - max_expected_time_per_block: 1, - ..genesis.parameters - }; + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + min_num_of_blocks: 1, + min_duration: 1, + max_expected_time_per_block: 1, + ..genesis.parameters + }; - GenesisConfig { - parameters, - ..genesis - } - }, None)?; + GenesisConfig { + parameters, + ..genesis + } + }, + None, + )?; let anomac_help = vec!["--help"]; From a8f6c641dd29f21478a8649bee69a7afde17d4b0 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 20 Sep 2022 12:41:52 +0200 Subject: [PATCH 096/197] Rename `Treasury` to `SlashFund` --- apps/src/lib/client/rpc.rs | 6 ++-- apps/src/lib/config/genesis.rs | 20 +++++------ apps/src/lib/node/ledger/protocol/mod.rs | 16 ++++----- apps/src/lib/node/ledger/shell/governance.rs | 8 ++--- apps/src/lib/node/ledger/shell/init_chain.rs | 2 +- .../src/explore/design/ledger/governance.md | 8 ++--- genesis/dev.toml | 2 +- genesis/e2e-tests-single-node.toml | 2 +- shared/src/ledger/mod.rs | 2 +- .../ledger/{treasury => slash_fund}/mod.rs | 34 +++++++++---------- .../{treasury => slash_fund}/parameters.rs | 12 +++---- .../{treasury => slash_fund}/storage.rs | 4 +-- shared/src/types/address.rs | 20 +++++------ vm_env/src/lib.rs | 2 +- wasm_for_tests/wasm_source/src/lib.rs | 4 +-- 15 files changed, 71 insertions(+), 71 deletions(-) rename shared/src/ledger/{treasury => slash_fund}/mod.rs (90%) rename shared/src/ledger/{treasury => slash_fund}/parameters.rs (79%) rename shared/src/ledger/{treasury => slash_fund}/storage.rs (91%) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 5f9aa6385d..4434e92d18 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -23,7 +23,7 @@ use namada::ledger::governance::parameters::GovParams; use namada::ledger::pos::{ self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, }; -use namada::ledger::treasury::storage as treasury_storage; +use namada::ledger::slash_fund::storage as slash_fund_storage; use namada::types::address::Address; use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalVote, TallyResult, ProposalResult, @@ -474,8 +474,8 @@ pub async fn query_protocol_parameters( .expect("Parameter should be definied."); println!("{:4}Transactions whitelist: {:?}", "", tx_whitelist); - println!("Treasury parameters"); - let key = treasury_storage::get_max_transferable_fund_key(); + println!("Slash Fund parameters"); + let key = slash_fund_storage::get_max_transferable_fund_key(); let max_transferable_amount = query_storage_value::(&client, &key) .await .expect("Parameter should be definied."); diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 5bd3dc803f..e13e9bf970 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -9,7 +9,7 @@ use derivative::Derivative; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::Parameters; use namada::ledger::pos::{GenesisValidator, PosParams}; -use namada::ledger::treasury::parameters::TreasuryParams; +use namada::ledger::slash_fund::parameters::SlashFundParams; use namada::types::address::Address; #[cfg(not(feature = "dev"))] use namada::types::chain::ChainId; @@ -31,7 +31,7 @@ pub mod genesis_config { use namada::ledger::parameters::{EpochDuration, Parameters}; use namada::ledger::pos::types::BasisPoints; use namada::ledger::pos::{GenesisValidator, PosParams}; - use namada::ledger::treasury::parameters::TreasuryParams; + use namada::ledger::slash_fund::parameters::SlashFundParams; use namada::types::address::Address; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; @@ -119,8 +119,8 @@ pub mod genesis_config { pub pos_params: PosParamsConfig, // Governance parameters pub gov_params: GovernanceParamsConfig, - // Treasury parameters - pub treasury_params: TreasuryParamasConfig, + // Slash Fund parameters + pub slash_fund_params: SlashFundParamasConfig, // Wasm definitions pub wasm: HashMap, } @@ -145,8 +145,8 @@ pub mod genesis_config { } #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct TreasuryParamasConfig { - // Maximum funds that can be moved from treasury in a single transfer + pub struct SlashFundParamasConfig { + // Maximum funds that can be moved from slash fund in a single transfer // XXX: u64 doesn't work with toml-rs! pub max_proposal_fund_transfer: u64, } @@ -558,7 +558,7 @@ pub mod genesis_config { .min_proposal_grace_epochs, }; - let treasury_params = TreasuryParams { + let slash_fund_params = SlashFundParams { max_proposal_fund_transfer: 10_000, }; @@ -588,7 +588,7 @@ pub mod genesis_config { parameters, pos_params, gov_params, - treasury_params, + slash_fund_params, }; genesis.init(); genesis @@ -623,7 +623,7 @@ pub struct Genesis { pub parameters: Parameters, pub pos_params: PosParams, pub gov_params: GovParams, - pub treasury_params: TreasuryParams, + pub slash_fund_params: SlashFundParams, } impl Genesis { @@ -859,7 +859,7 @@ pub fn genesis() -> Genesis { parameters, pos_params: PosParams::default(), gov_params: GovParams::default(), - treasury_params: TreasuryParams::default(), + slash_fund_params: SlashFundParams::default(), } } diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index e5f191d6e7..c8069d5a9c 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -11,7 +11,7 @@ use namada::ledger::parameters::{self, ParametersVp}; use namada::ledger::pos::{self, PosVP}; use namada::ledger::storage::write_log::WriteLog; use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; -use namada::ledger::treasury::TreasuryVp; +use namada::ledger::slash_fund::SlashFundVp; use namada::proto::{self, Tx}; use namada::types::address::{Address, InternalAddress}; use namada::types::storage; @@ -49,8 +49,8 @@ pub enum Error { IbcTokenNativeVpError(namada::ledger::ibc::vp::IbcTokenError), #[error("Governance native VP error: {0}")] GovernanceNativeVpError(namada::ledger::governance::vp::Error), - #[error("Treasury native VP error: {0}")] - TreasuryNativeVpError(namada::ledger::treasury::Error), + #[error("SlashFund native VP error: {0}")] + SlashFundNativeVpError(namada::ledger::slash_fund::Error), #[error("Ethereum bridge native VP error: {0}")] EthBridgeNativeVpError(namada::ledger::eth_bridge::vp::Error), #[error("Access to an internal address {0} is forbidden")] @@ -325,12 +325,12 @@ where gas_meter = governance.ctx.gas_meter.into_inner(); result } - InternalAddress::Treasury => { - let treasury = TreasuryVp { ctx }; - let result = treasury + InternalAddress::SlashFund => { + let slash_fund = SlashFundVp { ctx }; + let result = slash_fund .validate_tx(tx_data, &keys_changed, &verifiers) - .map_err(Error::TreasuryNativeVpError); - gas_meter = treasury.ctx.gas_meter.into_inner(); + .map_err(Error::SlashFundNativeVpError); + gas_meter = slash_fund.ctx.gas_meter.into_inner(); result } InternalAddress::IbcEscrow(_) diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 6c88ffd591..75e021c0ee 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -5,7 +5,7 @@ use anoma::ledger::governance::utils::{ use anoma::ledger::governance::vp::ADDRESS as gov_address; use anoma::ledger::storage::types::encode; use anoma::ledger::storage::{DBIter, StorageHasher, DB}; -use anoma::ledger::treasury::ADDRESS as treasury_address; +use anoma::ledger::slash_fund::ADDRESS as slash_fund_address; use anoma::types::address::{xan as m1t, Address}; use anoma::types::governance::TallyResult; use anoma::types::storage::Epoch; @@ -133,7 +133,7 @@ where response.events.push(proposal_event); proposals_result.rejected.push(id); - treasury_address + slash_fund_address } } Err(_e) => { @@ -149,7 +149,7 @@ where response.events.push(proposal_event); proposals_result.rejected.push(id); - treasury_address + slash_fund_address } } } @@ -181,7 +181,7 @@ where response.events.push(proposal_event); proposals_result.rejected.push(id); - treasury_address + slash_fund_address } }; diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index a989338751..904f509324 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -60,7 +60,7 @@ where genesis.parameters.init_storage(&mut self.storage); genesis.gov_params.init_storage(&mut self.storage); - genesis.treasury_params.init_storage(&mut self.storage); + genesis.slash_fund_params.init_storage(&mut self.storage); // Depends on parameters being initialized self.storage diff --git a/documentation/dev/src/explore/design/ledger/governance.md b/documentation/dev/src/explore/design/ledger/governance.md index 21fdc55da1..2f3734c8c5 100644 --- a/documentation/dev/src/explore/design/ledger/governance.md +++ b/documentation/dev/src/explore/design/ledger/governance.md @@ -2,12 +2,12 @@ Namada introduce a governance mechanism to propose and apply protocol changes with and without the need for an hard fork. Anyone holding some M1T will be able to prosose some changes to which delegators and validator will cast their yay or nay votes. Governance on Namada supports both signaling and voting mechanism. The difference between the the two, is that the former is needed when the changes require an hard fork. In cases where the chain is not able to produce blocks anymore, Namada relies an off chain signaling mechanism to agree on a common strategy. -## Governance & Treasury addresses +## Governance & SlashFund addresses Governance introduce two internal address with their corresponding native vps: - Governance address, which is in charge of validating on-chain proposals and votes -- Treasury address, which is in charge of holding treasury funds +- SlashFund address, which is in charge of holding slashed funds Also, it introduces some protocol parameters: @@ -60,7 +60,7 @@ and follow these rules: - `content` should follow the `Namada Improvement Proposal schema` and must be less than `max_proposal_content_size` kibibytes. - `author` must be a valid address on-chain -A proposal gets accepted if, at least 2/3 of the total voting power (computed at the epoch definied in the `startEpoch` field) vote `yay`. If the proposal is accepted, the locked funds are returned to the address definied in the `proposal_author` field, otherwise are moved to the treasury address. +A proposal gets accepted if, at least 2/3 of the total voting power (computed at the epoch definied in the `startEpoch` field) vote `yay`. If the proposal is accepted, the locked funds are returned to the address definied in the `proposal_author` field, otherwise are moved to the slash fund address. The `proposal_code` field can execute arbitrary code in the form of a wasm transaction. If the proposal gets accepted, the code is executed in the first block of the epoch following the `graceEpoch`. @@ -98,7 +98,7 @@ Vote is valid if it follow this rules: The outcome of a proposal is compute at the epoch specific in the `endEpoch` field and executed at `graceEpoch` field (if it contains a non-empty `proposalCode` field). A proposal is accepted only if more than 2/3 of the voting power vote `yay`. -If a proposal gets accepted, the locked funds will be reimbursed to the author. In case it gets rejected, the locked funds will be moved to treasury. +If a proposal gets accepted, the locked funds will be reimbursed to the author. In case it gets rejected, the locked funds will be moved to slash fund. ## Off-chain proposal diff --git a/genesis/dev.toml b/genesis/dev.toml index 4c48bcc279..729d7a93bb 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -184,5 +184,5 @@ max_proposal_content_size = 5000 # minimum epochs between end and grace epoch min_proposal_grace_epochs = 6 -[treasury_params] +[slash_fund_params] max_proposal_fund_transfer = 10000 \ No newline at end of file diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 96fd787ca2..962502201a 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -192,5 +192,5 @@ max_proposal_content_size = 10000 # minimum epochs between end and grace epoch min_proposal_grace_epochs = 6 -[treasury_params] +[slash_fund_params] max_proposal_fund_transfer = 10000 diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 2d545f96f2..8f0f049683 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -8,5 +8,5 @@ pub mod native_vp; pub mod parameters; pub mod pos; pub mod storage; -pub mod treasury; +pub mod slash_fund; pub mod vp_env; diff --git a/shared/src/ledger/treasury/mod.rs b/shared/src/ledger/slash_fund/mod.rs similarity index 90% rename from shared/src/ledger/treasury/mod.rs rename to shared/src/ledger/slash_fund/mod.rs index 071019059b..f276b6f984 100644 --- a/shared/src/ledger/treasury/mod.rs +++ b/shared/src/ledger/slash_fund/mod.rs @@ -1,15 +1,15 @@ -//! Treasury VP +//! SlashFund VP use std::collections::BTreeSet; -/// treasury parameters +/// SlashFund parameters pub mod parameters; -/// treasury storage +/// SlashFund storage pub mod storage; use borsh::BorshDeserialize; use thiserror::Error; -use self::storage as treasury_storage; +use self::storage as slash_fund_storage; use super::governance::vp::is_proposal_accepted; use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; @@ -18,8 +18,8 @@ use crate::types::storage::Key; use crate::types::token; use crate::vm::WasmCacheAccess; -/// Internal treasury address -pub const ADDRESS: Address = Address::Internal(InternalAddress::Treasury); +/// Internal SlashFund address +pub const ADDRESS: Address = Address::Internal(InternalAddress::SlashFund); #[allow(missing_docs)] #[derive(Error, Debug)] @@ -28,11 +28,11 @@ pub enum Error { NativeVpError(native_vp::Error), } -/// Treasury functions result +/// SlashFund functions result pub type Result = std::result::Result; -/// Treasury VP -pub struct TreasuryVp<'a, DB, H, CA> +/// SlashFund VP +pub struct SlashFundVp<'a, DB, H, CA> where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: StorageHasher, @@ -42,7 +42,7 @@ where pub ctx: Ctx<'a, DB, H, CA>, } -impl<'a, DB, H, CA> NativeVp for TreasuryVp<'a, DB, H, CA> +impl<'a, DB, H, CA> NativeVp for SlashFundVp<'a, DB, H, CA> where DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: 'static + StorageHasher, @@ -50,7 +50,7 @@ where { type Error = Error; - const ADDR: InternalAddress = InternalAddress::Treasury; + const ADDR: InternalAddress = InternalAddress::SlashFund; fn validate_tx( &self, @@ -78,7 +78,7 @@ where return false; }; let is_max_funds_transfer_key = - treasury_storage::get_max_transferable_fund_key(); + slash_fund_storage::get_max_transferable_fund_key(); let balance_key = token::balance_key(&nam(), &ADDRESS); let max_transfer_amount = self.ctx.read_pre(&is_max_funds_transfer_key); @@ -141,7 +141,7 @@ where _ => false, } } - KeyType::UNKNOWN_TREASURY => false, + KeyType::UNKNOWN_SLASH_FUND => false, KeyType::UNKNOWN => true, } }); @@ -157,17 +157,17 @@ enum KeyType { PARAMETER, #[allow(clippy::upper_case_acronyms)] #[allow(non_camel_case_types)] - UNKNOWN_TREASURY, + UNKNOWN_SLASH_FUND, #[allow(clippy::upper_case_acronyms)] UNKNOWN, } impl From<&Key> for KeyType { fn from(value: &Key) -> Self { - if treasury_storage::is_parameter_key(value) { + if slash_fund_storage::is_parameter_key(value) { KeyType::PARAMETER - } else if treasury_storage::is_treasury_key(value) { - KeyType::UNKNOWN_TREASURY + } else if slash_fund_storage::is_slash_fund_key(value) { + KeyType::UNKNOWN_SLASH_FUND } else if token::is_any_token_balance_key(value).is_some() { match token::is_balance_key(&nam(), value) { Some(addr) => KeyType::BALANCE(addr.clone()), diff --git a/shared/src/ledger/treasury/parameters.rs b/shared/src/ledger/slash_fund/parameters.rs similarity index 79% rename from shared/src/ledger/treasury/parameters.rs rename to shared/src/ledger/slash_fund/parameters.rs index 2ccb142d09..07e3a027a3 100644 --- a/shared/src/ledger/treasury/parameters.rs +++ b/shared/src/ledger/slash_fund/parameters.rs @@ -1,6 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use super::storage as treasury_storage; +use super::storage as slash_fund_storage; use crate::ledger::storage::types::encode; use crate::ledger::storage::{self, Storage}; use crate::types::token::Amount; @@ -17,12 +17,12 @@ use crate::types::token::Amount; BorshDeserialize, )] /// Governance parameter structure -pub struct TreasuryParams { +pub struct SlashFundParams { /// Maximum amount of token that can be moved in a single transfer pub max_proposal_fund_transfer: u64, } -impl Default for TreasuryParams { +impl Default for SlashFundParams { fn default() -> Self { Self { max_proposal_fund_transfer: 10_000, @@ -30,15 +30,15 @@ impl Default for TreasuryParams { } } -impl TreasuryParams { - /// Initialize treasury parameters into storage +impl SlashFundParams { + /// Initialize slash fund parameters into storage pub fn init_storage(&self, storage: &mut Storage) where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: storage::StorageHasher, { let max_proposal_fund_transfer_key = - treasury_storage::get_max_transferable_fund_key(); + slash_fund_storage::get_max_transferable_fund_key(); let amount = Amount::whole(self.max_proposal_fund_transfer); storage .write(&max_proposal_fund_transfer_key, encode(&amount)) diff --git a/shared/src/ledger/treasury/storage.rs b/shared/src/ledger/slash_fund/storage.rs similarity index 91% rename from shared/src/ledger/treasury/storage.rs rename to shared/src/ledger/slash_fund/storage.rs index e4a8733240..0884fdf687 100644 --- a/shared/src/ledger/treasury/storage.rs +++ b/shared/src/ledger/slash_fund/storage.rs @@ -3,8 +3,8 @@ use crate::types::storage::{DbKeySeg, Key, KeySeg}; const MAX_TRANSFERABLE_FUND_KEY: &str = "max_fund"; -/// Check if a key is a treasury key -pub fn is_treasury_key(key: &Key) -> bool { +/// Check if a key is a slash fund key +pub fn is_slash_fund_key(key: &Key) -> bool { matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &ADDRESS) } diff --git a/shared/src/types/address.rs b/shared/src/types/address.rs index 704dcdbdb2..a1ff9702ea 100644 --- a/shared/src/types/address.rs +++ b/shared/src/types/address.rs @@ -59,8 +59,8 @@ mod internal { "ano::Protocol Parameters "; pub const GOVERNANCE: &str = "ano::Governance "; - pub const TREASURY: &str = - "ano::Treasury "; + pub const SLASH_FUND: &str = + "ano::Slash Fund "; pub const IBC_BURN: &str = "ano::IBC Burn Address "; pub const IBC_MINT: &str = @@ -185,7 +185,7 @@ impl Address { InternalAddress::Governance => { internal::GOVERNANCE.to_string() } - InternalAddress::Treasury => internal::TREASURY.to_string(), + InternalAddress::SlashFund => internal::SLASH_FUND.to_string(), InternalAddress::IbcEscrow(hash) => { format!("{}::{}", PREFIX_INTERNAL, hash) } @@ -245,8 +245,8 @@ impl Address { internal::GOVERNANCE => { Ok(Address::Internal(InternalAddress::Governance)) } - internal::TREASURY => { - Ok(Address::Internal(InternalAddress::Treasury)) + internal::SLASH_FUND => { + Ok(Address::Internal(InternalAddress::SlashFund)) } internal::IBC_MINT => { Ok(Address::Internal(InternalAddress::IbcMint)) @@ -447,8 +447,8 @@ pub enum InternalAddress { IbcMint, /// Governance address Governance, - /// Treasury address - Treasury, + /// SlashFund address for governance + SlashFund, /// Bridge to Ethereum EthBridge, } @@ -475,7 +475,7 @@ impl Display for InternalAddress { Self::Ibc => "IBC".to_string(), Self::Parameters => "Parameters".to_string(), Self::Governance => "Governance".to_string(), - Self::Treasury => "Treasury".to_string(), + Self::SlashFund => "SlashFund".to_string(), Self::IbcEscrow(hash) => format!("IbcEscrow: {}", hash), Self::IbcBurn => "IbcBurn".to_string(), Self::IbcMint => "IbcMint".to_string(), @@ -712,7 +712,7 @@ pub mod testing { InternalAddress::PosSlashPool => {} InternalAddress::Ibc => {} InternalAddress::Governance => {} - InternalAddress::Treasury => {} + InternalAddress::SlashFund => {} InternalAddress::Parameters => {} InternalAddress::IbcEscrow(_) => {} InternalAddress::IbcBurn => {} @@ -730,7 +730,7 @@ pub mod testing { Just(InternalAddress::IbcBurn), Just(InternalAddress::IbcMint), Just(InternalAddress::Governance), - Just(InternalAddress::Treasury), + Just(InternalAddress::SlashFund), Just(InternalAddress::EthBridge), ] } diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 578079d695..2d3cab9e8c 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -19,7 +19,7 @@ pub mod tx_prelude { pub use namada::ledger::governance::storage; pub use namada::ledger::parameters::storage as parameters_storage; pub use namada::ledger::storage::types::encode; - pub use namada::ledger::treasury::storage as treasury_storage; + pub use namada::ledger::slash_fund::storage as slash_fund_storage; pub use namada::proto::{Signed, SignedTxData}; pub use namada::types::address::Address; pub use namada::types::storage::Key; diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 674fb2a2d9..f622e9c9f7 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -33,8 +33,8 @@ pub mod main { let target_key = storage::get_min_proposal_grace_epoch_key(); write(&target_key.to_string(), 9_u64); - // treasury - let target_key = treasury_storage::get_max_transferable_fund_key(); + // slash fund + let target_key = slash_fund_storage::get_max_transferable_fund_key(); write(&target_key.to_string(), token::Amount::whole(20_000)); // parameters From ff951ffabcb9c78bd8f56bf970b6ae88bdd5443e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 21 Sep 2022 10:50:41 +0200 Subject: [PATCH 097/197] Skip tx whitelist for proposal code --- shared/src/ledger/governance/vp.rs | 2 +- vp_prelude/src/lib.rs | 21 +++++++++++++++++++++ wasm/wasm_source/src/vp_nft.rs | 2 +- wasm/wasm_source/src/vp_testnet_faucet.rs | 2 +- wasm/wasm_source/src/vp_token.rs | 4 ++-- wasm/wasm_source/src/vp_user.rs | 2 +- 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index 300787fba5..a1f7457ecc 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -479,7 +479,7 @@ pub enum ReadType { POST, } -/// Check if a proposal id is beign executed +/// Check if a proposal id is being executed pub fn is_proposal_accepted( context: &Ctx, proposal_id: u64, diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 957354d848..daf35b6d06 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -33,6 +33,27 @@ pub fn is_vp_whitelisted(vp_bytes: &[u8]) -> bool { whitelist.is_empty() || whitelist.contains(&vp_hash.to_string()) } +/// Checks if a proposal id is being executed +pub fn is_proposal_accepted(proposal_id: u64) -> bool { + let proposal_execution_key = + gov_storage::get_proposal_execution_key(proposal_id); + + has_key_pre(&proposal_execution_key.to_string()) +} + +/// Checks whether a transaction is valid, which happens in two cases: +/// - tx is whitelisted, or +/// - tx is executed by an approved governance proposal (no need to be whitelisted) +pub fn is_valid_tx(tx_data: &[u8]) -> bool { + if is_tx_whitelisted() { + return true + } else { + let proposal_id = u64::try_from_slice(tx_data).ok(); + + proposal_id.map_or(false, |id| is_proposal_accepted(id)) + } +} + /// Log a string in a debug build. The message will be printed at the /// `tracing::Level::Info`. Any `debug_log!` statements are only enabled in /// non optimized builds by default. An optimized build will not execute diff --git a/wasm/wasm_source/src/vp_nft.rs b/wasm/wasm_source/src/vp_nft.rs index f1e6dd587b..4f57d543d6 100644 --- a/wasm/wasm_source/src/vp_nft.rs +++ b/wasm/wasm_source/src/vp_nft.rs @@ -15,7 +15,7 @@ fn validate_tx( addr, keys_changed, verifiers )); - if !is_tx_whitelisted() { + if !is_valid_tx(&tx_data) { return false; } diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 553288926e..962027c0f4 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -40,7 +40,7 @@ fn validate_tx( _ => false, }); - if !is_tx_whitelisted() { + if !is_valid_tx(&tx_data) { return false; } diff --git a/wasm/wasm_source/src/vp_token.rs b/wasm/wasm_source/src/vp_token.rs index 60513ce808..3f13cf0aaf 100644 --- a/wasm/wasm_source/src/vp_token.rs +++ b/wasm/wasm_source/src/vp_token.rs @@ -5,7 +5,7 @@ use namada_vp_prelude::*; #[validity_predicate] fn validate_tx( - _tx_data: Vec, + tx_data: Vec, addr: Address, keys_changed: BTreeSet, verifiers: BTreeSet
, @@ -18,7 +18,7 @@ fn validate_tx( verifiers ); - if !is_tx_whitelisted() { + if !is_valid_tx(&tx_data) { return false; } diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index a222a344ef..e412ffec97 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -88,7 +88,7 @@ fn validate_tx( _ => false, }); - if !is_tx_whitelisted() { + if !is_valid_tx(&tx_data) { return false; } From 4a3d0970e059074e8d547bec53285d07d896cea9 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 21 Sep 2022 11:49:13 +0200 Subject: [PATCH 098/197] Use proposal `end_epoch` instead of `start_epoch` for voting power --- apps/src/lib/node/ledger/shell/governance.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 75e021c0ee..180c0a9af4 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -39,27 +39,26 @@ where for id in std::mem::take(&mut shell.proposal_data) { let proposal_funds_key = gov_storage::get_funds_key(id); - let proposal_start_epoch_key = - gov_storage::get_voting_start_epoch_key(id); + let proposal_end_epoch_key = gov_storage::get_voting_end_epoch_key(id); let funds = shell .read_storage_key::(&proposal_funds_key) .ok_or_else(|| { Error::BadProposal(id, "Invalid proposal funds.".to_string()) })?; - let proposal_start_epoch = shell - .read_storage_key::(&proposal_start_epoch_key) + let proposal_end_epoch = shell + .read_storage_key::(&proposal_end_epoch_key) .ok_or_else(|| { Error::BadProposal( id, - "Invalid proposal start_epoch.".to_string(), + "Invalid proposal end_epoch.".to_string(), ) })?; let votes = - get_proposal_votes(&shell.storage, proposal_start_epoch, id); + get_proposal_votes(&shell.storage, proposal_end_epoch, id); let tally_result = - compute_tally(&shell.storage, proposal_start_epoch, votes); + compute_tally(&shell.storage, proposal_end_epoch, votes); let transfer_address = match tally_result { TallyResult::Passed => { From de18d0fc57adf1679826ebc945beef92b0042415 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 21 Sep 2022 17:05:26 +0200 Subject: [PATCH 099/197] Removes `max_proposal_fund_transfer` parameter --- apps/src/lib/client/rpc.rs | 11 --- apps/src/lib/config/genesis.rs | 18 ---- apps/src/lib/node/ledger/shell/init_chain.rs | 1 - .../src/explore/design/ledger/governance.md | 2 - genesis/dev.toml | 3 - genesis/e2e-tests-single-node.toml | 3 - shared/src/ledger/slash_fund/mod.rs | 88 ++----------------- shared/src/ledger/slash_fund/parameters.rs | 47 ---------- shared/src/ledger/slash_fund/storage.rs | 28 +----- wasm_for_tests/wasm_source/src/lib.rs | 4 - 10 files changed, 8 insertions(+), 197 deletions(-) delete mode 100644 shared/src/ledger/slash_fund/parameters.rs diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 4434e92d18..afe399a2b4 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -23,7 +23,6 @@ use namada::ledger::governance::parameters::GovParams; use namada::ledger::pos::{ self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, }; -use namada::ledger::slash_fund::storage as slash_fund_storage; use namada::types::address::Address; use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalVote, TallyResult, ProposalResult, @@ -474,16 +473,6 @@ pub async fn query_protocol_parameters( .expect("Parameter should be definied."); println!("{:4}Transactions whitelist: {:?}", "", tx_whitelist); - println!("Slash Fund parameters"); - let key = slash_fund_storage::get_max_transferable_fund_key(); - let max_transferable_amount = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!( - "{:4}Max. transferable amount: {}", - "", max_transferable_amount - ); - println!("PoS parameters"); let key = pos::params_key(); let pos_params = query_storage_value::(&client, &key) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index e13e9bf970..2a74dbc52a 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -9,7 +9,6 @@ use derivative::Derivative; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::Parameters; use namada::ledger::pos::{GenesisValidator, PosParams}; -use namada::ledger::slash_fund::parameters::SlashFundParams; use namada::types::address::Address; #[cfg(not(feature = "dev"))] use namada::types::chain::ChainId; @@ -31,7 +30,6 @@ pub mod genesis_config { use namada::ledger::parameters::{EpochDuration, Parameters}; use namada::ledger::pos::types::BasisPoints; use namada::ledger::pos::{GenesisValidator, PosParams}; - use namada::ledger::slash_fund::parameters::SlashFundParams; use namada::types::address::Address; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; @@ -119,8 +117,6 @@ pub mod genesis_config { pub pos_params: PosParamsConfig, // Governance parameters pub gov_params: GovernanceParamsConfig, - // Slash Fund parameters - pub slash_fund_params: SlashFundParamasConfig, // Wasm definitions pub wasm: HashMap, } @@ -144,13 +140,6 @@ pub mod genesis_config { pub min_proposal_grace_epochs: u64, } - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct SlashFundParamasConfig { - // Maximum funds that can be moved from slash fund in a single transfer - // XXX: u64 doesn't work with toml-rs! - pub max_proposal_fund_transfer: u64, - } - /// Validator pre-genesis configuration can be created with client utils /// `init-genesis-validator` command and added to a genesis for /// `init-network` cmd and that can be subsequently read by `join-network` @@ -558,10 +547,6 @@ pub mod genesis_config { .min_proposal_grace_epochs, }; - let slash_fund_params = SlashFundParams { - max_proposal_fund_transfer: 10_000, - }; - let pos_params = PosParams { max_validator_slots: config.pos_params.max_validator_slots, pipeline_len: config.pos_params.pipeline_len, @@ -588,7 +573,6 @@ pub mod genesis_config { parameters, pos_params, gov_params, - slash_fund_params, }; genesis.init(); genesis @@ -623,7 +607,6 @@ pub struct Genesis { pub parameters: Parameters, pub pos_params: PosParams, pub gov_params: GovParams, - pub slash_fund_params: SlashFundParams, } impl Genesis { @@ -859,7 +842,6 @@ pub fn genesis() -> Genesis { parameters, pos_params: PosParams::default(), gov_params: GovParams::default(), - slash_fund_params: SlashFundParams::default(), } } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 904f509324..207f83fd0b 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -60,7 +60,6 @@ where genesis.parameters.init_storage(&mut self.storage); genesis.gov_params.init_storage(&mut self.storage); - genesis.slash_fund_params.init_storage(&mut self.storage); // Depends on parameters being initialized self.storage diff --git a/documentation/dev/src/explore/design/ledger/governance.md b/documentation/dev/src/explore/design/ledger/governance.md index 2f3734c8c5..1583ba4ccf 100644 --- a/documentation/dev/src/explore/design/ledger/governance.md +++ b/documentation/dev/src/explore/design/ledger/governance.md @@ -16,7 +16,6 @@ Also, it introduces some protocol parameters: - `min_proposal_period` - `max_proposal_content_size` - `min_proposal_grace_epochs` -- `max_proposal_fund_transfer` ## On-chain proposals @@ -29,7 +28,6 @@ On-chain proposals are created under the `governance_address` storage space and, /$GovernanceAddress/min_proposal_period: u64 /$GovernanceAddress/max_proposal_content_size: u64 /$GovernanceAddress/min_proposal_grace_epochs: u64 -/$GovernanceAddress/max_proposal_fund_transfer: u64 ``` In order to create a valid proposal, a transaction need to modify these storage keys: diff --git a/genesis/dev.toml b/genesis/dev.toml index 729d7a93bb..9af9c73e28 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -183,6 +183,3 @@ min_proposal_period = 3 max_proposal_content_size = 5000 # minimum epochs between end and grace epoch min_proposal_grace_epochs = 6 - -[slash_fund_params] -max_proposal_fund_transfer = 10000 \ No newline at end of file diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 962502201a..e2dcc80e9d 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -191,6 +191,3 @@ min_proposal_period = 3 max_proposal_content_size = 10000 # minimum epochs between end and grace epoch min_proposal_grace_epochs = 6 - -[slash_fund_params] -max_proposal_fund_transfer = 10000 diff --git a/shared/src/ledger/slash_fund/mod.rs b/shared/src/ledger/slash_fund/mod.rs index f276b6f984..0d452af2e5 100644 --- a/shared/src/ledger/slash_fund/mod.rs +++ b/shared/src/ledger/slash_fund/mod.rs @@ -1,8 +1,7 @@ //! SlashFund VP use std::collections::BTreeSet; -/// SlashFund parameters -pub mod parameters; + /// SlashFund storage pub mod storage; @@ -61,84 +60,15 @@ where let result = keys_changed.iter().all(|key| { let key_type: KeyType = key.into(); match key_type { - KeyType::PARAMETER => { - let proposal_id = u64::try_from_slice(tx_data).ok(); - match proposal_id { - Some(id) => is_proposal_accepted(&self.ctx, id), - _ => false, - } - } KeyType::BALANCE(addr) => { - let proposal_id = u64::try_from_slice(tx_data).ok(); - if let Some(id) = proposal_id { - if !is_proposal_accepted(&self.ctx, id) { - return false; - } - } else { - return false; - }; - let is_max_funds_transfer_key = - slash_fund_storage::get_max_transferable_fund_key(); - let balance_key = token::balance_key(&nam(), &ADDRESS); - let max_transfer_amount = - self.ctx.read_pre(&is_max_funds_transfer_key); - let pre_balance = self.ctx.read_pre(&balance_key); - let post_balance = self.ctx.read_post(&balance_key); if addr.ne(&ADDRESS) { return true; } - match (max_transfer_amount, pre_balance, post_balance) { - ( - Ok(max_transfer_amount), - Ok(pre_balance), - Ok(post_balance), - ) => { - match ( - max_transfer_amount, - pre_balance, - post_balance, - ) { - ( - Some(max_transfer_amount), - Some(pre_balance), - Some(post_balance), - ) => { - let max_transfer_amount = - token::Amount::try_from_slice( - &max_transfer_amount[..], - ) - .ok(); - let pre_balance = - token::Amount::try_from_slice( - &pre_balance[..], - ) - .ok(); - let post_balance = - token::Amount::try_from_slice( - &post_balance[..], - ) - .ok(); - match ( - max_transfer_amount, - pre_balance, - post_balance, - ) { - ( - Some(max_transfer_amount), - Some(pre_balance), - Some(post_balance), - ) => { - post_balance > pre_balance - || (pre_balance - post_balance - <= max_transfer_amount) - } - _ => false, - } - } - _ => false, - } - } - _ => false, + + let proposal_id = u64::try_from_slice(tx_data).ok(); + match proposal_id { + Some(id) => is_proposal_accepted(&self.ctx, id), + None => false, } } KeyType::UNKNOWN_SLASH_FUND => false, @@ -154,8 +84,6 @@ enum KeyType { #[allow(clippy::upper_case_acronyms)] BALANCE(Address), #[allow(clippy::upper_case_acronyms)] - PARAMETER, - #[allow(clippy::upper_case_acronyms)] #[allow(non_camel_case_types)] UNKNOWN_SLASH_FUND, #[allow(clippy::upper_case_acronyms)] @@ -164,9 +92,7 @@ enum KeyType { impl From<&Key> for KeyType { fn from(value: &Key) -> Self { - if slash_fund_storage::is_parameter_key(value) { - KeyType::PARAMETER - } else if slash_fund_storage::is_slash_fund_key(value) { + if slash_fund_storage::is_slash_fund_key(value) { KeyType::UNKNOWN_SLASH_FUND } else if token::is_any_token_balance_key(value).is_some() { match token::is_balance_key(&nam(), value) { diff --git a/shared/src/ledger/slash_fund/parameters.rs b/shared/src/ledger/slash_fund/parameters.rs deleted file mode 100644 index 07e3a027a3..0000000000 --- a/shared/src/ledger/slash_fund/parameters.rs +++ /dev/null @@ -1,47 +0,0 @@ -use borsh::{BorshDeserialize, BorshSerialize}; - -use super::storage as slash_fund_storage; -use crate::ledger::storage::types::encode; -use crate::ledger::storage::{self, Storage}; -use crate::types::token::Amount; - -#[derive( - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - BorshSerialize, - BorshDeserialize, -)] -/// Governance parameter structure -pub struct SlashFundParams { - /// Maximum amount of token that can be moved in a single transfer - pub max_proposal_fund_transfer: u64, -} - -impl Default for SlashFundParams { - fn default() -> Self { - Self { - max_proposal_fund_transfer: 10_000, - } - } -} - -impl SlashFundParams { - /// Initialize slash fund parameters into storage - pub fn init_storage(&self, storage: &mut Storage) - where - DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, - { - let max_proposal_fund_transfer_key = - slash_fund_storage::get_max_transferable_fund_key(); - let amount = Amount::whole(self.max_proposal_fund_transfer); - storage - .write(&max_proposal_fund_transfer_key, encode(&amount)) - .unwrap(); - } -} diff --git a/shared/src/ledger/slash_fund/storage.rs b/shared/src/ledger/slash_fund/storage.rs index 0884fdf687..60d29f0f48 100644 --- a/shared/src/ledger/slash_fund/storage.rs +++ b/shared/src/ledger/slash_fund/storage.rs @@ -1,33 +1,7 @@ use super::ADDRESS; -use crate::types::storage::{DbKeySeg, Key, KeySeg}; - -const MAX_TRANSFERABLE_FUND_KEY: &str = "max_fund"; +use crate::types::storage::{DbKeySeg, Key}; /// Check if a key is a slash fund key pub fn is_slash_fund_key(key: &Key) -> bool { matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &ADDRESS) } - -/// Check if key is max funds transfer key -pub fn is_max_funds_transfer_key(key: &Key) -> bool { - match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(max_fund)] - if addr == &ADDRESS && max_fund == MAX_TRANSFERABLE_FUND_KEY => - { - true - } - _ => false, - } -} - -/// Check if key is any parameter key -pub fn is_parameter_key(key: &Key) -> bool { - is_max_funds_transfer_key(key) -} - -/// Get key of max funds transfer parameter -pub fn get_max_transferable_fund_key() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&MAX_TRANSFERABLE_FUND_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index f622e9c9f7..de82229d8f 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -33,10 +33,6 @@ pub mod main { let target_key = storage::get_min_proposal_grace_epoch_key(); write(&target_key.to_string(), 9_u64); - // slash fund - let target_key = slash_fund_storage::get_max_transferable_fund_key(); - write(&target_key.to_string(), token::Amount::whole(20_000)); - // parameters let target_key = parameters_storage::get_tx_whitelist_storage_key(); write(&target_key.to_string(), vec!["hash"]); From 172a93091598c389a9d64239f0c5a2211fb13b21 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 21 Sep 2022 18:02:39 +0200 Subject: [PATCH 100/197] Adds `max_proposal_period` governance parameter --- apps/src/lib/client/rpc.rs | 6 ++++ apps/src/lib/client/tx.rs | 29 ++++++++++--------- apps/src/lib/config/genesis.rs | 6 +++- .../src/explore/design/ledger/governance.md | 7 +++-- genesis/dev.toml | 4 ++- genesis/e2e-tests-single-node.toml | 4 ++- shared/src/ledger/governance/parameters.rs | 17 +++++++++-- shared/src/ledger/governance/storage.rs | 24 +++++++++++++++ shared/src/ledger/governance/vp.rs | 6 ++++ 9 files changed, 82 insertions(+), 21 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index afe399a2b4..ea86cfd314 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1924,10 +1924,16 @@ pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { .await .expect("Parameter should be definied."); + let key = gov_storage::get_max_proposal_period_key(); + let max_proposal_period = query_storage_value::(client, &key) + .await + .expect("Parameter should be definied."); + GovParams { min_proposal_fund: u64::from(min_proposal_fund), max_proposal_code_size, min_proposal_period, + max_proposal_period, max_proposal_content_size, min_proposal_grace_epochs, } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8df5869727..ab900fc732 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -535,7 +535,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let signer = WalletAddress::new(proposal.clone().author.to_string()); - let goverance_parameters = rpc::get_governance_parameters(&client).await; + let governance_parameters = rpc::get_governance_parameters(&client).await; let current_epoch = rpc::query_epoch(args::Query { ledger_address: args.tx.ledger_address.clone(), }) @@ -543,14 +543,14 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { if proposal.voting_start_epoch <= current_epoch || proposal.voting_start_epoch.0 - % goverance_parameters.min_proposal_period + % governance_parameters.min_proposal_period != 0 { println!("{}", proposal.voting_start_epoch <= current_epoch); println!( "{}", proposal.voting_start_epoch.0 - % goverance_parameters.min_proposal_period + % governance_parameters.min_proposal_period == 0 ); eprintln!( @@ -558,34 +558,37 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { epoch {} and a multiple of {}", proposal.voting_start_epoch, current_epoch, - goverance_parameters.min_proposal_period + governance_parameters.min_proposal_period ); if !args.tx.force { safe_exit(1) } } else if proposal.voting_end_epoch <= proposal.voting_start_epoch || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 - < goverance_parameters.min_proposal_period + < governance_parameters.min_proposal_period + || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 + > governance_parameters.max_proposal_period || proposal.voting_end_epoch.0 % 3 != 0 { eprintln!( "Invalid proposal end epoch: difference between proposal start \ - and end epoch must be at least {} and end epoch must be a \ - multiple of {}", - goverance_parameters.min_proposal_period, - goverance_parameters.min_proposal_period + and end epoch must be at least {} and at max {} and end epoch must \ + be a multiple of {}", + governance_parameters.min_proposal_period, + governance_parameters.max_proposal_period, + governance_parameters.min_proposal_period ); if !args.tx.force { safe_exit(1) } } else if proposal.grace_epoch <= proposal.voting_end_epoch || proposal.grace_epoch.0 - proposal.voting_end_epoch.0 - < goverance_parameters.min_proposal_grace_epochs + < governance_parameters.min_proposal_grace_epochs { eprintln!( "Invalid proposal grace epoch: difference between proposal grace \ and end epoch must be at least {}", - goverance_parameters.min_proposal_grace_epochs + governance_parameters.min_proposal_grace_epochs ); if !args.tx.force { safe_exit(1) @@ -632,7 +635,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let balance = rpc::get_token_balance(&client, &m1t(), &proposal.author) .await .unwrap_or_default(); - if balance < token::Amount::from(goverance_parameters.min_proposal_fund) + if balance < token::Amount::from(governance_parameters.min_proposal_fund) { eprintln!( "Address {} doesn't have enough funds.", @@ -642,7 +645,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { } if init_proposal_data.content.len() - > goverance_parameters.max_proposal_content_size as usize + > governance_parameters.max_proposal_content_size as usize { eprintln!("Proposal content size too big.",); safe_exit(1); diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 2a74dbc52a..45a7d7bab0 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -129,9 +129,12 @@ pub mod genesis_config { // Maximum size of proposal in kibibytes (KiB) // XXX: u64 doesn't work with toml-rs! pub max_proposal_code_size: u64, - // Proposal period length in epoch + // Minimum proposal period length in epochs // XXX: u64 doesn't work with toml-rs! pub min_proposal_period: u64, + // Maximum proposal period length in epochs + // XXX: u64 doesn't work with toml-rs! + pub max_proposal_period: u64, // Maximum number of characters in the proposal content // XXX: u64 doesn't work with toml-rs! pub max_proposal_content_size: u64, @@ -539,6 +542,7 @@ pub mod genesis_config { min_proposal_fund: config.gov_params.min_proposal_fund, max_proposal_code_size: config.gov_params.max_proposal_code_size, min_proposal_period: config.gov_params.min_proposal_period, + max_proposal_period: config.gov_params.max_proposal_period, max_proposal_content_size: config .gov_params .max_proposal_content_size, diff --git a/documentation/dev/src/explore/design/ledger/governance.md b/documentation/dev/src/explore/design/ledger/governance.md index 1583ba4ccf..63b466b23e 100644 --- a/documentation/dev/src/explore/design/ledger/governance.md +++ b/documentation/dev/src/explore/design/ledger/governance.md @@ -14,6 +14,7 @@ Also, it introduces some protocol parameters: - `min_proposal_fund` - `max_proposal_code_size` - `min_proposal_period` +- `max_proposal_period` - `max_proposal_content_size` - `min_proposal_grace_epochs` @@ -26,6 +27,7 @@ On-chain proposals are created under the `governance_address` storage space and, /$GovernanceAddress/min_proposal_fund: u64 /$GovernanceAddress/max_proposal_code_size: u64 /$GovernanceAddress/min_proposal_period: u64 +/$GovernanceAddress/max_proposal_period: u64 /$GovernanceAddress/max_proposal_content_size: u64 /$GovernanceAddress/min_proposal_grace_epochs: u64 ``` @@ -49,8 +51,9 @@ and follow these rules: - be grater than `currentEpoch`, where current epoch is the epoch in which the transaction is executed and included in a block - be a multiple of `min_proposal_period`. - `endEpoch` must: - - be at least `min_proposal_period` epoch greater than `startEpoch` - - be a multiple of `min_proposal_period` + - be at least `min_proposal_period` epochs greater than `startEpoch` + - be at most `max_proposal_period` epochs greater than `startEpoch` + - be a multiple of `min_proposal_period` - `graceEpoch` must: - be at least `min_grace_epoch` epochs greater than `endEpoch` - `proposalCode` can be empty and must be a valid transaction with size less than `max_proposal_code_size` kibibytes. diff --git a/genesis/dev.toml b/genesis/dev.toml index 9af9c73e28..cb3f9bbcb2 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -177,8 +177,10 @@ light_client_attack_slash_rate = 500 min_proposal_fund = 500 # proposal code size in bytes max_proposal_code_size = 300000 -# proposal period length in epoch +# min proposal period length in epochs min_proposal_period = 3 +# max proposal period length in epochs +max_proposal_period = 27 # maximum number of characters in the proposal content max_proposal_content_size = 5000 # minimum epochs between end and grace epoch diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index e2dcc80e9d..3933279ba0 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -185,8 +185,10 @@ light_client_attack_slash_rate = 500 min_proposal_fund = 500 # proposal code size in bytes max_proposal_code_size = 300000 -# proposal period length in epoch +# min proposal period length in epochs min_proposal_period = 3 +# max proposal period length in epochs +max_proposal_period = 27 # maximum number of characters in the proposal content max_proposal_content_size = 10000 # minimum epochs between end and grace epoch diff --git a/shared/src/ledger/governance/parameters.rs b/shared/src/ledger/governance/parameters.rs index 5b280491b9..71dca8c91b 100644 --- a/shared/src/ledger/governance/parameters.rs +++ b/shared/src/ledger/governance/parameters.rs @@ -26,7 +26,9 @@ pub struct GovParams { pub max_proposal_code_size: u64, /// Minimum proposal voting period in epochs pub min_proposal_period: u64, - /// Maximimum number of characters for proposal content + /// Maximum proposal voting period in epochs + pub max_proposal_period: u64, + /// Maximum number of characters for proposal content pub max_proposal_content_size: u64, /// Minimum epochs between end and grace epochs pub min_proposal_grace_epochs: u64, @@ -37,11 +39,12 @@ impl Display for GovParams { write!( f, "Min. proposal fund: {}\nMax. proposal code size: {}\nMin. \ - proposal period: {}\nMax. proposal content size: {}\nMin. \ - proposal grace epochs: {}", + proposal period: {}\nMax. proposal period: {}\nMax. proposal \ + content size: {}\nMin. proposal grace epochs: {}", self.min_proposal_fund, self.max_proposal_code_size, self.min_proposal_period, + self.max_proposal_period, self.max_proposal_content_size, self.min_proposal_grace_epochs ) @@ -54,6 +57,7 @@ impl Default for GovParams { min_proposal_fund: 500, max_proposal_code_size: 300_000, min_proposal_period: 3, + max_proposal_period: 27, max_proposal_content_size: 10_000, min_proposal_grace_epochs: 6, } @@ -71,6 +75,7 @@ impl GovParams { min_proposal_fund, max_proposal_code_size, min_proposal_period, + max_proposal_period, max_proposal_content_size, min_proposal_grace_epochs, } = self; @@ -93,6 +98,12 @@ impl GovParams { .write(&min_proposal_period_key, encode(min_proposal_period)) .unwrap(); + let max_proposal_period_key = + gov_storage::get_max_proposal_period_key(); + storage + .write(&max_proposal_period_key, encode(max_proposal_period)) + .unwrap(); + let max_proposal_content_size_key = gov_storage::get_max_proposal_content_key(); storage diff --git a/shared/src/ledger/governance/storage.rs b/shared/src/ledger/governance/storage.rs index 50e9dc05cb..9d2f0a4e4a 100644 --- a/shared/src/ledger/governance/storage.rs +++ b/shared/src/ledger/governance/storage.rs @@ -16,6 +16,7 @@ const PROPOSAL_COMMITTING_EPOCH: &str = "epoch"; const MIN_PROPOSAL_FUND_KEY: &str = "min_fund"; const MAX_PROPOSAL_CODE_SIZE_KEY: &str = "max_code_size"; const MIN_PROPOSAL_PERIOD_KEY: &str = "min_period"; +const MAX_PROPOSAL_PERIOD_KEY: &str = "max_period"; const MAX_PROPOSAL_CONTENT_SIZE_KEY: &str = "max_content"; const MIN_GRACE_EPOCH_KEY: &str = "min_grace_epoch"; const COUNTER_KEY: &str = "counter"; @@ -242,6 +243,21 @@ pub fn is_min_proposal_period_key(key: &Key) -> bool { } } +/// Check if key is a max proposal period param key +pub fn is_max_proposal_period_key(key: &Key) -> bool { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(max_proposal_period_param), + ] if addr == &ADDRESS + && max_proposal_period_param == MAX_PROPOSAL_PERIOD_KEY => + { + true + } + _ => false, + } +} + /// Check if key is a min grace epoch key pub fn is_commit_proposal_key(key: &Key) -> bool { match &key.segments[..] { @@ -282,6 +298,7 @@ pub fn is_parameter_key(key: &Key) -> bool { || is_max_content_size_key(key) || is_max_proposal_code_size_key(key) || is_min_proposal_period_key(key) + || is_max_proposal_period_key(key) || is_min_grace_epoch_key(key) } @@ -318,6 +335,13 @@ pub fn get_min_proposal_period_key() -> Key { .expect("Cannot obtain a storage key") } +/// Get maximum proposal period key +pub fn get_max_proposal_period_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&MAX_PROPOSAL_PERIOD_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + /// Get maximum proposal content key pub fn get_max_proposal_content_key() -> Key { Key::from(ADDRESS.to_db_key()) diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index a1f7457ecc..3251f5d2d9 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -263,12 +263,16 @@ where let min_period_parameter_key = gov_storage::get_min_proposal_period_key(); let min_period: Option = read(ctx, &min_period_parameter_key, ReadType::PRE).ok(); + let max_period_parameter_key = gov_storage::get_max_proposal_period_key(); + let max_period: Option = + read(ctx, &max_period_parameter_key, ReadType::PRE).ok(); let has_pre_start_epoch = ctx.has_key_pre(&start_epoch_key).ok(); let has_pre_end_epoch = ctx.has_key_pre(&end_epoch_key).ok(); match ( has_pre_start_epoch, has_pre_end_epoch, min_period, + max_period, start_epoch, end_epoch, current_epoch, @@ -277,6 +281,7 @@ where Some(has_pre_start_epoch), Some(has_pre_end_epoch), Some(min_period), + Some(max_period), Some(start_epoch), Some(end_epoch), Some(current_epoch), @@ -288,6 +293,7 @@ where && !has_pre_end_epoch && (end_epoch - start_epoch) % min_period == 0 && (end_epoch - start_epoch).0 >= min_period + && (end_epoch - start_epoch).0 <= max_period } _ => false, } From da57772607dc508dbedf87df4c1aabee92f59487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Sep 2022 10:48:00 +0200 Subject: [PATCH 101/197] remove intent gossiper, matchmaker and their deps --- CONTRIBUTING.md | 2 +- Cargo.lock | 1591 +---------------- Cargo.toml | 2 - Makefile | 2 +- README.md | 18 +- apps/Cargo.toml | 6 - apps/build.rs | 45 +- apps/src/bin/anoma-client/cli.rs | 9 +- apps/src/bin/anoma-node/cli.rs | 53 +- apps/src/bin/anoma/cli.rs | 10 +- apps/src/lib/cli.rs | 450 +---- apps/src/lib/cli/context.rs | 2 +- apps/src/lib/client/gossip.rs | 116 -- apps/src/lib/client/mod.rs | 1 - apps/src/lib/client/utils.rs | 164 +- apps/src/lib/config/genesis.rs | 23 +- apps/src/lib/config/mod.rs | 162 +- apps/src/lib/mod.rs | 1 - apps/src/lib/node/gossip/intent_gossiper.rs | 123 -- apps/src/lib/node/gossip/mempool.rs | 26 - apps/src/lib/node/gossip/mod.rs | 116 -- .../node/gossip/p2p/behaviour/discovery.rs | 517 ------ apps/src/lib/node/gossip/p2p/behaviour/mod.rs | 412 ----- apps/src/lib/node/gossip/p2p/identity.rs | 123 -- apps/src/lib/node/gossip/p2p/mod.rs | 139 -- apps/src/lib/node/gossip/rpc/client.rs | 166 -- apps/src/lib/node/gossip/rpc/matchmakers.rs | 847 --------- apps/src/lib/node/gossip/rpc/mod.rs | 2 - apps/src/lib/node/ledger/shell/init_chain.rs | 1 - apps/src/lib/node/matchmaker.rs | 364 ---- apps/src/lib/node/mod.rs | 2 - apps/src/lib/proto/README.md | 3 - apps/src/lib/proto/generated.rs | 1 - apps/src/lib/proto/generated/.gitignore | 1 - apps/src/lib/proto/mod.rs | 5 - apps/src/lib/proto/types.rs | 148 -- apps/src/lib/wallet/defaults.rs | 22 +- .../docs/src/testnets/internal-testnet-1.md | 2 +- .../docs/src/user-guide/getting-started.md | 2 +- .../intent-gossiper-and-matchmaker.md | 124 -- genesis/dev.toml | 5 - genesis/e2e-tests-single-node.toml | 15 +- macros/src/lib.rs | 117 +- proto/services.proto | 30 - proto/types.proto | 22 +- shared/build.rs | 3 - shared/src/proto/mod.rs | 4 +- shared/src/proto/types.rs | 146 -- shared/src/types/intent.rs | 475 ----- shared/src/types/matchmaker.rs | 30 - shared/src/types/mod.rs | 2 - shared/src/vm/mod.rs | 3 +- shared/src/vm/types.rs | 3 - tests/Cargo.toml | 1 - tests/src/e2e.rs | 1 - tests/src/e2e/gossip_tests.rs | 348 ---- tests/src/e2e/helpers.rs | 14 +- tests/src/e2e/setup.rs | 10 - vm_env/src/intent.rs | 39 - vm_env/src/lib.rs | 3 - wasm/wasm_source/src/lib.rs | 2 - wasm/wasm_source/src/tx_from_intent.rs | 35 - wasm/wasm_source/src/vp_user.rs | 186 +- wasm_for_tests/wasm_source/Cargo.lock | 24 +- 64 files changed, 163 insertions(+), 7158 deletions(-) delete mode 100644 apps/src/lib/client/gossip.rs delete mode 100644 apps/src/lib/node/gossip/intent_gossiper.rs delete mode 100644 apps/src/lib/node/gossip/mempool.rs delete mode 100644 apps/src/lib/node/gossip/mod.rs delete mode 100644 apps/src/lib/node/gossip/p2p/behaviour/discovery.rs delete mode 100644 apps/src/lib/node/gossip/p2p/behaviour/mod.rs delete mode 100644 apps/src/lib/node/gossip/p2p/identity.rs delete mode 100644 apps/src/lib/node/gossip/p2p/mod.rs delete mode 100644 apps/src/lib/node/gossip/rpc/client.rs delete mode 100644 apps/src/lib/node/gossip/rpc/matchmakers.rs delete mode 100644 apps/src/lib/node/gossip/rpc/mod.rs delete mode 100644 apps/src/lib/node/matchmaker.rs delete mode 100644 apps/src/lib/proto/README.md delete mode 100644 apps/src/lib/proto/generated.rs delete mode 100644 apps/src/lib/proto/generated/.gitignore delete mode 100644 apps/src/lib/proto/mod.rs delete mode 100644 apps/src/lib/proto/types.rs delete mode 100644 documentation/docs/src/user-guide/intent-gossiper-and-matchmaker.md delete mode 100644 proto/services.proto delete mode 100644 shared/src/types/intent.rs delete mode 100644 shared/src/types/matchmaker.rs delete mode 100644 tests/src/e2e/gossip_tests.rs delete mode 100644 vm_env/src/intent.rs delete mode 100644 wasm/wasm_source/src/tx_from_intent.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 42e29950d8..c00d3f08ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,4 +47,4 @@ for i in $(ls -d .changelog/*/*/); do basename "$i"; done ## Development priorities -If you’d like to follow the development or contribute with new or unimplemented features, we recommend to check [the pinned issues](https://github.com/anoma/anoma/issues) that are set to tracking issues in current focus of the ledger, intent gossiper and matchmaker team. +If you’d like to follow the development or contribute with new or unimplemented features, we recommend to check [the issues](https://github.com/anoma/namada/issues) that are in current focus of the ledger team. diff --git a/Cargo.lock b/Cargo.lock index eafd05b2ff..0874744e39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,41 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aead" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" -dependencies = [ - "generic-array 0.14.5", -] - -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if 1.0.0", - "cipher", - "cpufeatures 0.2.2", - "opaque-debug 0.3.0", -] - -[[package]] -name = "aes-gcm" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle 2.4.1", -] - [[package]] name = "ahash" version = "0.7.6" @@ -233,12 +198,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" -[[package]] -name = "asn1_der" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22d1f4b888c298a027c99dc9048015fac177587de20fc30232a057dfbe24a21" - [[package]] name = "assert_cmd" version = "1.0.8" @@ -314,7 +273,7 @@ dependencies = [ "parking", "polling", "slab", - "socket2 0.4.4", + "socket2", "waker-fn", "winapi 0.3.9", ] @@ -376,26 +335,12 @@ dependencies = [ "memchr", "num_cpus", "once_cell", - "pin-project-lite 0.2.9", + "pin-project-lite", "pin-utils", "slab", "wasm-bindgen-futures", ] -[[package]] -name = "async-std-resolver" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf3e776afdf3a2477ef4854b85ba0dff3bd85792f685fb3c68948b4d304e4f0" -dependencies = [ - "async-std", - "async-trait", - "futures-io", - "futures-util", - "pin-utils", - "trust-dns-resolver", -] - [[package]] name = "async-stream" version = "0.3.3" @@ -443,35 +388,13 @@ dependencies = [ "futures-io", "futures-util", "log 0.4.17", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", "tokio-rustls", - "tungstenite 0.12.0", + "tungstenite", "webpki-roots", ] -[[package]] -name = "asynchronous-codec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" -dependencies = [ - "bytes 1.1.0", - "futures-sink", - "futures-util", - "memchr", - "pin-project-lite 0.2.9", -] - -[[package]] -name = "atomic" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg 1.1.0", -] - [[package]] name = "atomic-waker" version = "1.0.0" @@ -544,12 +467,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.0" @@ -611,17 +528,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -[[package]] -name = "blake2" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" -dependencies = [ - "crypto-mac 0.8.0", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - [[package]] name = "blake2" version = "0.10.4" @@ -742,7 +648,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate 0.1.5", + "proc-macro-crate", "proc-macro2", "syn", ] @@ -767,12 +673,6 @@ dependencies = [ "syn", ] -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" - [[package]] name = "bstr" version = "0.2.17" @@ -842,12 +742,6 @@ dependencies = [ "iovec", ] -[[package]] -name = "bytes" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" - [[package]] name = "bytes" version = "1.1.0" @@ -914,18 +808,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chacha20" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" -dependencies = [ - "cfg-if 1.0.0", - "cipher", - "cpufeatures 0.1.5", - "zeroize", -] - [[package]] name = "chacha20" version = "0.8.1" @@ -934,20 +816,7 @@ checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.2.2", -] - -[[package]] -name = "chacha20poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" -dependencies = [ - "aead", - "chacha20 0.7.1", - "cipher", - "poly1305", - "zeroize", + "cpufeatures", ] [[package]] @@ -1135,15 +1004,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" -[[package]] -name = "cpufeatures" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" -dependencies = [ - "libc", -] - [[package]] name = "cpufeatures" version = "0.2.2" @@ -1292,7 +1152,7 @@ checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ "generic-array 0.14.5", "rand_core 0.6.3", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -1306,16 +1166,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -dependencies = [ - "generic-array 0.12.4", - "subtle 1.0.0", -] - [[package]] name = "crypto-mac" version = "0.8.0" @@ -1323,7 +1173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ "generic-array 0.14.5", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -1333,7 +1183,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ "generic-array 0.14.5", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -1361,32 +1211,12 @@ dependencies = [ "syn", ] -[[package]] -name = "ctr" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" -dependencies = [ - "cipher", -] - [[package]] name = "cty" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" -[[package]] -name = "cuckoofilter" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b810a8449931679f64cd7eef1bbd0fa315801b6d5d9cdc1ace2804d6529eee18" -dependencies = [ - "byteorder", - "fnv", - "rand 0.7.3", -] - [[package]] name = "curl" version = "0.4.43" @@ -1398,7 +1228,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "socket2 0.4.4", + "socket2", "winapi 0.3.9", ] @@ -1426,7 +1256,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -1512,12 +1342,6 @@ dependencies = [ "syn", ] -[[package]] -name = "data-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" - [[package]] name = "der" version = "0.5.1" @@ -1618,37 +1442,7 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer 0.10.2", "crypto-common", - "subtle 2.4.1", -] - -[[package]] -name = "directories" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi 0.3.9", -] - -[[package]] -name = "dns-parser" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" -dependencies = [ - "byteorder", - "quick-error 1.2.3", + "subtle", ] [[package]] @@ -1762,7 +1556,7 @@ dependencies = [ "group", "rand_core 0.6.3", "sec1", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -1776,7 +1570,7 @@ dependencies = [ "rustc_version 0.4.0", "toml", "vswhom", - "winreg 0.10.1", + "winreg", ] [[package]] @@ -1788,18 +1582,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "enum-as-inner" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4" -dependencies = [ - "heck 0.4.0", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "enum-iterator" version = "0.7.0" @@ -1925,7 +1707,7 @@ dependencies = [ "ark-serialize", "ark-std", "bincode", - "blake2 0.10.4", + "blake2", "blake2b_simd", "borsh", "digest 0.10.3", @@ -1934,7 +1716,7 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "itertools 0.10.3", + "itertools", "measure_time", "miracl_core", "num", @@ -1944,7 +1726,7 @@ dependencies = [ "serde_bytes", "serde_json", "subproductdomain", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -1968,7 +1750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ "rand_core 0.6.3", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -2006,12 +1788,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "fixedbitset" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" - [[package]] name = "fixedbitset" version = "0.4.1" @@ -2025,7 +1801,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ "crc32fast", - "libz-sys", "miniz_oxide", ] @@ -2163,7 +1938,6 @@ dependencies = [ "futures-core", "futures-task", "futures-util", - "num_cpus", ] [[package]] @@ -2183,7 +1957,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.9", + "pin-project-lite", "waker-fn", ] @@ -2198,17 +1972,6 @@ dependencies = [ "syn", ] -[[package]] -name = "futures-rustls" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1387e07917c711fb4ee4f48ea0adb04a3c9739e53ef85bf43ae1edc2937a8b" -dependencies = [ - "futures-io", - "rustls", - "webpki", -] - [[package]] name = "futures-sink" version = "0.3.21" @@ -2221,12 +1984,6 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" - [[package]] name = "futures-util" version = "0.3.21" @@ -2240,7 +1997,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.9", + "pin-project-lite", "pin-utils", "slab", ] @@ -2286,16 +2043,6 @@ dependencies = [ "wasi 0.10.0+wasi-snapshot-preview1", ] -[[package]] -name = "ghash" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" -dependencies = [ - "opaque-debug 0.3.0", - "polyval", -] - [[package]] name = "gimli" version = "0.25.0" @@ -2367,7 +2114,7 @@ checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "ff", "rand_core 0.6.3", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -2383,9 +2130,9 @@ dependencies = [ "ark-serialize", "ark-std", "blake2b_simd", - "chacha20 0.8.1", + "chacha20", "hex", - "itertools 0.10.3", + "itertools", "miracl_core", "rand 0.8.5", "rand_core 0.6.3", @@ -2499,12 +2246,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - [[package]] name = "hermit-abi" version = "0.1.19" @@ -2520,22 +2261,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex_fmt" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" - -[[package]] -name = "hmac" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" -dependencies = [ - "crypto-mac 0.7.0", - "digest 0.8.1", -] - [[package]] name = "hmac" version = "0.8.1" @@ -2556,17 +2281,6 @@ dependencies = [ "digest 0.9.0", ] -[[package]] -name = "hmac-drbg" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" -dependencies = [ - "digest 0.8.1", - "generic-array 0.12.4", - "hmac 0.7.1", -] - [[package]] name = "hmac-drbg" version = "0.3.0" @@ -2578,17 +2292,6 @@ dependencies = [ "hmac 0.8.1", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi 0.3.9", -] - [[package]] name = "http" version = "0.2.8" @@ -2608,7 +2311,7 @@ checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes 1.1.0", "http", - "pin-project-lite 0.2.9", + "pin-project-lite", ] [[package]] @@ -2658,8 +2361,8 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite 0.2.9", - "socket2 0.4.4", + "pin-project-lite", + "socket2", "tokio", "tower-service", "tracing 0.1.35", @@ -2710,7 +2413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ "hyper 0.14.19", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", "tokio-io-timeout", ] @@ -2739,8 +2442,8 @@ dependencies = [ "ibc-proto", "ics23", "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "safe-regex", "serde 1.0.137", "serde_derive", @@ -2761,8 +2464,8 @@ version = "0.16.0" source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ "bytes 1.1.0", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.137", "tendermint-proto", "tonic", @@ -2777,7 +2480,7 @@ dependencies = [ "anyhow", "bytes 1.1.0", "hex", - "prost 0.9.0", + "prost", "ripemd160", "sha2 0.9.9", "sha3", @@ -2812,43 +2515,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "if-addrs" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2273e421f7c4f0fc99e1934fe4776f59d8df2972f4199d703fc0da9f2a9f73de" -dependencies = [ - "if-addrs-sys", - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "if-addrs-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de74b9dd780476e837e5eb5ab7c88b49ed304126e412030a0adba99c8efe79ea" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "if-watch" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8ab7f67bad3240049cb24fb9cb0b4c2c6af4c245840917fbbdededeee91179" -dependencies = [ - "async-io", - "futures 0.3.21", - "futures-lite", - "if-addrs", - "ipnet", - "libc", - "log 0.4.17", - "winapi 0.3.9", -] - [[package]] name = "indenter" version = "0.3.3" @@ -2907,12 +2573,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "integer-encoding" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e85a1509a128c855368e135cffcde7eac17d8e1083f41e2b98c58bc1a5074be" - [[package]] name = "iovec" version = "0.1.4" @@ -2923,32 +2583,11 @@ dependencies = [ ] [[package]] -name = "ipconfig" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" -dependencies = [ - "socket2 0.3.19", - "widestring", - "winapi 0.3.9", - "winreg 0.6.2", -] - -[[package]] -name = "ipnet" -version = "2.5.0" +name = "ipnet" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" -[[package]] -name = "itertools" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.10.3" @@ -3104,417 +2743,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "libp2p" -version = "0.38.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "atomic", - "bytes 1.1.0", - "futures 0.3.21", - "lazy_static 1.4.0", - "libp2p-core", - "libp2p-deflate", - "libp2p-dns", - "libp2p-floodsub", - "libp2p-gossipsub", - "libp2p-identify", - "libp2p-kad", - "libp2p-mdns", - "libp2p-mplex", - "libp2p-noise", - "libp2p-ping", - "libp2p-plaintext", - "libp2p-pnet", - "libp2p-relay", - "libp2p-request-response", - "libp2p-swarm", - "libp2p-swarm-derive", - "libp2p-tcp", - "libp2p-uds", - "libp2p-wasm-ext", - "libp2p-websocket", - "libp2p-yamux", - "parity-multiaddr", - "parking_lot 0.11.2", - "pin-project 1.0.10", - "smallvec 1.8.0", - "wasm-timer", -] - -[[package]] -name = "libp2p-core" -version = "0.28.3" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asn1_der", - "bs58", - "ed25519-dalek", - "either", - "fnv", - "futures 0.3.21", - "futures-timer", - "lazy_static 1.4.0", - "libsecp256k1 0.3.5", - "log 0.4.17", - "multihash", - "multistream-select", - "parity-multiaddr", - "parking_lot 0.11.2", - "pin-project 1.0.10", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "ring", - "rw-stream-sink", - "sha2 0.9.9", - "smallvec 1.8.0", - "thiserror", - "unsigned-varint 0.7.1", - "void", - "zeroize", -] - -[[package]] -name = "libp2p-deflate" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "flate2", - "futures 0.3.21", - "libp2p-core", -] - -[[package]] -name = "libp2p-dns" -version = "0.28.1" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-std-resolver", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", - "smallvec 1.8.0", - "trust-dns-resolver", -] - -[[package]] -name = "libp2p-floodsub" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "cuckoofilter", - "fnv", - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "smallvec 1.8.0", -] - -[[package]] -name = "libp2p-gossipsub" -version = "0.31.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asynchronous-codec", - "base64 0.13.0", - "byteorder", - "bytes 1.1.0", - "fnv", - "futures 0.3.21", - "hex_fmt", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "regex", - "sha2 0.9.9", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", - "wasm-timer", -] - -[[package]] -name = "libp2p-identify" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "smallvec 1.8.0", - "wasm-timer", -] - -[[package]] -name = "libp2p-kad" -version = "0.30.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "arrayvec 0.5.2", - "asynchronous-codec", - "bytes 1.1.0", - "either", - "fnv", - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "sha2 0.9.9", - "smallvec 1.8.0", - "uint", - "unsigned-varint 0.7.1", - "void", - "wasm-timer", -] - -[[package]] -name = "libp2p-mdns" -version = "0.30.2" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-io", - "data-encoding", - "dns-parser", - "futures 0.3.21", - "if-watch", - "lazy_static 1.4.0", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "rand 0.8.5", - "smallvec 1.8.0", - "socket2 0.4.4", - "void", -] - -[[package]] -name = "libp2p-mplex" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", - "nohash-hasher", - "parking_lot 0.11.2", - "rand 0.7.3", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", -] - -[[package]] -name = "libp2p-noise" -version = "0.31.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "bytes 1.1.0", - "curve25519-dalek", - "futures 0.3.21", - "lazy_static 1.4.0", - "libp2p-core", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.8.5", - "sha2 0.9.9", - "snow", - "static_assertions", - "x25519-dalek", - "zeroize", -] - -[[package]] -name = "libp2p-ping" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "rand 0.7.3", - "void", - "wasm-timer", -] - -[[package]] -name = "libp2p-plaintext" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "unsigned-varint 0.7.1", - "void", -] - -[[package]] -name = "libp2p-pnet" -version = "0.21.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "log 0.4.17", - "pin-project 1.0.10", - "rand 0.7.3", - "salsa20", - "sha3", -] - -[[package]] -name = "libp2p-relay" -version = "0.2.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", - "futures-timer", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "pin-project 1.0.10", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", - "void", - "wasm-timer", -] - -[[package]] -name = "libp2p-request-response" -version = "0.11.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-trait", - "bytes 1.1.0", - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "lru", - "minicbor", - "rand 0.7.3", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", - "wasm-timer", -] - -[[package]] -name = "libp2p-swarm" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "either", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", - "rand 0.7.3", - "smallvec 1.8.0", - "void", - "wasm-timer", -] - -[[package]] -name = "libp2p-swarm-derive" -version = "0.23.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "libp2p-tcp" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-io", - "futures 0.3.21", - "futures-timer", - "if-watch", - "ipnet", - "libc", - "libp2p-core", - "log 0.4.17", - "socket2 0.4.4", -] - -[[package]] -name = "libp2p-uds" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-std", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", -] - -[[package]] -name = "libp2p-wasm-ext" -version = "0.28.2" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "js-sys", - "libp2p-core", - "parity-send-wrapper", - "wasm-bindgen", - "wasm-bindgen-futures", -] - -[[package]] -name = "libp2p-websocket" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "either", - "futures 0.3.21", - "futures-rustls", - "libp2p-core", - "log 0.4.17", - "quicksink", - "rw-stream-sink", - "soketto", - "url 2.2.2", - "webpki-roots", -] - -[[package]] -name = "libp2p-yamux" -version = "0.32.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "libp2p-core", - "parking_lot 0.11.2", - "thiserror", - "yamux", -] - [[package]] name = "librocksdb-sys" version = "0.6.1+6.28.2" @@ -3530,22 +2758,6 @@ dependencies = [ "zstd-sys", ] -[[package]] -name = "libsecp256k1" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" -dependencies = [ - "arrayref", - "crunchy", - "digest 0.8.1", - "hmac-drbg 0.2.0", - "rand 0.7.3", - "sha2 0.8.2", - "subtle 2.4.1", - "typenum", -] - [[package]] name = "libsecp256k1" version = "0.7.0" @@ -3554,7 +2766,7 @@ dependencies = [ "arrayref", "base64 0.13.0", "digest 0.9.0", - "hmac-drbg 0.3.0", + "hmac-drbg", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", @@ -3571,7 +2783,7 @@ source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d dependencies = [ "crunchy", "digest 0.9.0", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -3685,24 +2897,6 @@ dependencies = [ "syn", ] -[[package]] -name = "lru" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ea2d928b485416e8908cff2d97d621db22b27f7b3b6729e438bcf42c671ba91" -dependencies = [ - "hashbrown 0.11.2", -] - -[[package]] -name = "lru-cache" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "mach" version = "0.3.2" @@ -3726,12 +2920,6 @@ dependencies = [ "serde_yaml", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matchers" version = "0.1.0" @@ -3799,24 +2987,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "message-io" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" -dependencies = [ - "crossbeam-channel", - "crossbeam-utils 0.8.8", - "integer-encoding", - "lazy_static 1.4.0", - "log 0.4.17", - "mio 0.7.14", - "serde 1.0.137", - "strum", - "tungstenite 0.16.0", - "url 2.2.2", -] - [[package]] name = "mime" version = "0.2.6" @@ -3842,26 +3012,6 @@ dependencies = [ "unicase 2.6.0", ] -[[package]] -name = "minicbor" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51aa5bb0ca22415daca596a227b507f880ad1b2318a87fa9325312a5d285ca0d" -dependencies = [ - "minicbor-derive", -] - -[[package]] -name = "minicbor-derive" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54999f917cd092b13904737e26631aa2b2b88d625db68e4bab461dcd8006c788" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3890,25 +3040,12 @@ dependencies = [ "kernel32-sys", "libc", "log 0.4.17", - "miow 0.2.2", + "miow", "net2", "slab", "winapi 0.2.8", ] -[[package]] -name = "mio" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" -dependencies = [ - "libc", - "log 0.4.17", - "miow 0.3.7", - "ntapi", - "winapi 0.3.9", -] - [[package]] name = "mio" version = "0.8.3" @@ -3945,15 +3082,6 @@ dependencies = [ "ws2_32-sys", ] -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "miracl_core" version = "2.3.0" @@ -3975,52 +3103,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" -[[package]] -name = "multihash" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dac63698b887d2d929306ea48b63760431ff8a24fac40ddb22f9c7f49fb7cab" -dependencies = [ - "digest 0.9.0", - "generic-array 0.14.5", - "multihash-derive", - "sha2 0.9.9", - "unsigned-varint 0.5.1", -] - -[[package]] -name = "multihash-derive" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" -dependencies = [ - "proc-macro-crate 1.1.3", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "multimap" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "multistream-select" -version = "0.10.3" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", - "log 0.4.17", - "pin-project 1.0.10", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", -] - [[package]] name = "namada" version = "0.7.1" @@ -4043,15 +3131,15 @@ dependencies = [ "ibc", "ibc-proto", "ics23", - "itertools 0.10.3", - "libsecp256k1 0.7.0", + "itertools", + "libsecp256k1", "loupe", "namada_proof_of_stake", "parity-wasm", "pretty_assertions", "proptest", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "pwasm-utils", "rand 0.8.5", "rand_core 0.6.3", @@ -4099,7 +3187,6 @@ dependencies = [ "config", "curl", "derivative", - "directories", "ed25519-consensus", "eyre", "ferveo", @@ -4109,22 +3196,19 @@ dependencies = [ "futures 0.3.21", "git2", "hex", - "itertools 0.10.3", + "itertools", "jsonpath_lib", "libc", "libloading", - "libp2p", - "message-io", "namada", "num-derive", "num-traits 0.2.15", "num_cpus", "once_cell", "orion", - "pathdiff", "proptest", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "rand 0.8.5", "rand_core 0.6.3", "rayon", @@ -4136,7 +3220,6 @@ dependencies = [ "serde 1.0.137", "serde_bytes", "serde_json", - "serde_regex", "sha2 0.9.9", "signal-hook", "sparse-merkle-tree", @@ -4153,7 +3236,6 @@ dependencies = [ "tokio-test", "toml", "tonic", - "tonic-build", "tower", "tower-abci", "tracing 0.1.35", @@ -4168,7 +3250,7 @@ name = "namada_encoding_spec" version = "0.7.1" dependencies = [ "borsh", - "itertools 0.10.3", + "itertools", "lazy_static 1.4.0", "madato", "namada", @@ -4207,14 +3289,13 @@ dependencies = [ "file-serve", "fs_extra", "hex", - "itertools 0.10.3", - "libp2p", + "itertools", "namada", "namada_apps", "namada_vm_env", "pretty_assertions", "proptest", - "prost 0.9.0", + "prost", "rand 0.8.5", "serde_json", "sha2 0.9.9", @@ -4330,12 +3411,6 @@ dependencies = [ "libc", ] -[[package]] -name = "nohash-hasher" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" - [[package]] name = "nom" version = "5.1.2" @@ -4588,7 +3663,7 @@ checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", "getrandom 0.2.6", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -4613,29 +3688,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" -[[package]] -name = "parity-multiaddr" -version = "0.11.2" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "arrayref", - "bs58", - "byteorder", - "data-encoding", - "multihash", - "percent-encoding 2.1.0", - "serde 1.0.137", - "static_assertions", - "unsigned-varint 0.7.1", - "url 2.2.2", -] - -[[package]] -name = "parity-send-wrapper" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" - [[package]] name = "parity-wasm" version = "0.42.2" @@ -4659,17 +3711,6 @@ dependencies = [ "rustc_version 0.2.3", ] -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api 0.4.7", - "parking_lot_core 0.8.5", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -4695,20 +3736,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if 1.0.0", - "instant", - "libc", - "redox_syscall 0.2.13", - "smallvec 1.8.0", - "winapi 0.3.9", -] - [[package]] name = "parking_lot_core" version = "0.9.3" @@ -4728,12 +3755,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - [[package]] name = "peeking_take_while" version = "0.1.2" @@ -4788,53 +3809,23 @@ dependencies = [ "ucd-trie", ] -[[package]] -name = "petgraph" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" -dependencies = [ - "fixedbitset 0.2.0", - "indexmap", -] - [[package]] name = "petgraph" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ - "fixedbitset 0.4.1", + "fixedbitset", "indexmap", ] -[[package]] -name = "pin-project" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9615c18d31137579e9ff063499264ddc1278e7b1982757ebc111028c4d1dc909" -dependencies = [ - "pin-project-internal 0.4.29", -] - [[package]] name = "pin-project" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" dependencies = [ - "pin-project-internal 1.0.10", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "044964427019eed9d49d9d5bbce6047ef18f37100ea400912a9fa4a3523ab12a" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "pin-project-internal", ] [[package]] @@ -4848,12 +3839,6 @@ dependencies = [ "syn", ] -[[package]] -name = "pin-project-lite" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" - [[package]] name = "pin-project-lite" version = "0.2.9" @@ -4885,29 +3870,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "poly1305" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" -dependencies = [ - "cpufeatures 0.2.2", - "opaque-debug 0.3.0", - "universal-hash", -] - -[[package]] -name = "polyval" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures 0.2.2", - "opaque-debug 0.3.0", - "universal-hash", -] - [[package]] name = "ppv-lite86" version = "0.2.16" @@ -4921,7 +3883,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" dependencies = [ "difflib", - "itertools 0.10.3", + "itertools", "predicates-core", ] @@ -4962,16 +3924,6 @@ dependencies = [ "toml", ] -[[package]] -name = "proc-macro-crate" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" -dependencies = [ - "thiserror", - "toml", -] - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -5025,41 +3977,13 @@ dependencies = [ ] [[package]] -name = "prost" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2" -dependencies = [ - "bytes 1.1.0", - "prost-derive 0.7.0", -] - -[[package]] -name = "prost" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" -dependencies = [ - "bytes 1.1.0", - "prost-derive 0.9.0", -] - -[[package]] -name = "prost-build" -version = "0.7.0" +name = "prost" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d3ebd75ac2679c2af3a92246639f9fcc8a442ee420719cc4fe195b98dd5fa3" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes 1.1.0", - "heck 0.3.3", - "itertools 0.9.0", - "log 0.4.17", - "multimap", - "petgraph 0.5.1", - "prost 0.7.0", - "prost-types 0.7.0", - "tempfile", - "which", + "prost-derive", ] [[package]] @@ -5069,32 +3993,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes 1.1.0", - "heck 0.3.3", - "itertools 0.10.3", + "heck", + "itertools", "lazy_static 1.4.0", "log 0.4.17", "multimap", - "petgraph 0.6.2", - "prost 0.9.0", - "prost-types 0.9.0", + "petgraph", + "prost", + "prost-types", "regex", "tempfile", "which", ] -[[package]] -name = "prost-derive" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4" -dependencies = [ - "anyhow", - "itertools 0.9.0", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "prost-derive" version = "0.9.0" @@ -5102,22 +4013,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools 0.10.3", + "itertools", "proc-macro2", "quote", "syn", ] -[[package]] -name = "prost-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b518d7cdd93dab1d1122cf07fa9a60771836c668dde9d9e2a139f957f0d9f1bb" -dependencies = [ - "bytes 1.1.0", - "prost 0.7.0", -] - [[package]] name = "prost-types" version = "0.9.0" @@ -5125,7 +4026,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ "bytes 1.1.0", - "prost 0.9.0", + "prost", ] [[package]] @@ -5180,17 +4081,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" -[[package]] -name = "quicksink" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77de3c815e5a160b1539c6592796801df2043ae35e123b46d73380cfa57af858" -dependencies = [ - "futures-core", - "futures-sink", - "pin-project-lite 0.1.12", -] - [[package]] name = "quote" version = "1.0.18" @@ -5443,17 +4333,6 @@ dependencies = [ "redox_syscall 0.2.13", ] -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom 0.2.6", - "redox_syscall 0.2.13", - "thiserror", -] - [[package]] name = "regalloc" version = "0.0.31" @@ -5544,7 +4423,7 @@ dependencies = [ "mime 0.3.16", "native-tls", "percent-encoding 2.1.0", - "pin-project-lite 0.2.9", + "pin-project-lite", "serde 1.0.137", "serde_json", "serde_urlencoded", @@ -5554,17 +4433,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg 0.10.1", -] - -[[package]] -name = "resolv-conf" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error 1.2.3", + "winreg", ] [[package]] @@ -5757,17 +4626,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "rw-stream-sink" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" -dependencies = [ - "futures 0.3.21", - "pin-project 0.4.29", - "static_assertions", -] - [[package]] name = "ryu" version = "1.0.10" @@ -5827,15 +4685,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" -[[package]] -name = "salsa20" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecbd2eb639fd7cab5804a0837fe373cc2172d15437e804c054a9fb885cb923b0" -dependencies = [ - "cipher", -] - [[package]] name = "same-file" version = "1.0.6" @@ -5885,7 +4734,7 @@ checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", "generic-array 0.14.5", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -6010,16 +4859,6 @@ dependencies = [ "serde 1.0.137", ] -[[package]] -name = "serde_regex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" -dependencies = [ - "regex", - "serde 1.0.137", -] - [[package]] name = "serde_repr" version = "0.1.8" @@ -6084,7 +4923,7 @@ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -6096,22 +4935,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures", "digest 0.10.3", ] -[[package]] -name = "sha2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", -] - [[package]] name = "sha2" version = "0.9.9" @@ -6120,7 +4947,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -6132,7 +4959,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures", "digest 0.10.3", ] @@ -6225,35 +5052,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" -[[package]] -name = "snow" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6142f7c25e94f6fd25a32c3348ec230df9109b463f59c8c7acc4bd34936babb7" -dependencies = [ - "aes-gcm", - "blake2 0.9.2", - "chacha20poly1305", - "rand 0.8.5", - "rand_core 0.6.3", - "ring", - "rustc_version 0.3.3", - "sha2 0.9.9", - "subtle 2.4.1", - "x25519-dalek", -] - -[[package]] -name = "socket2" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "winapi 0.3.9", -] - [[package]] name = "socket2" version = "0.4.4" @@ -6264,22 +5062,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "soketto" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c71ed3d54db0a699f4948e1bb3e45b450fa31fe602621dee6680361d569c88" -dependencies = [ - "base64 0.12.3", - "bytes 0.5.6", - "flate2", - "futures 0.3.21", - "httparse", - "log 0.4.17", - "rand 0.7.3", - "sha-1 0.9.8", -] - [[package]] name = "sp-std" version = "3.0.0" @@ -6341,28 +5123,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "strum" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" -dependencies = [ - "heck 0.4.0", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "subproductdomain" version = "0.1.0" @@ -6376,12 +5136,6 @@ dependencies = [ "ark-std", ] -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" - [[package]] name = "subtle" version = "2.4.1" @@ -6485,8 +5239,8 @@ dependencies = [ "k256", "num-traits 0.2.15", "once_cell", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "ripemd160", "serde 1.0.137", "serde_bytes", @@ -6494,7 +5248,7 @@ dependencies = [ "serde_repr", "sha2 0.9.9", "signature", - "subtle 2.4.1", + "subtle", "subtle-encoding", "tendermint-proto", "time 0.3.9", @@ -6536,8 +5290,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.137", "serde_bytes", "subtle-encoding", @@ -6560,7 +5314,7 @@ dependencies = [ "hyper-proxy", "hyper-rustls", "peg", - "pin-project 1.0.10", + "pin-project", "serde 1.0.137", "serde_bytes", "serde_json", @@ -6768,9 +5522,9 @@ dependencies = [ "num_cpus", "once_cell", "parking_lot 0.12.1", - "pin-project-lite 0.2.9", + "pin-project-lite", "signal-hook-registry", - "socket2 0.4.4", + "socket2", "tokio-macros", "winapi 0.3.9", ] @@ -6813,7 +5567,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", ] @@ -6875,7 +5629,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" dependencies = [ "futures-core", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", ] @@ -6937,7 +5691,7 @@ dependencies = [ "futures-core", "futures-sink", "log 0.4.17", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", ] @@ -6950,7 +5704,7 @@ dependencies = [ "bytes 1.1.0", "futures-core", "futures-sink", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", "tracing 0.1.35", ] @@ -6982,9 +5736,9 @@ dependencies = [ "hyper 0.14.19", "hyper-timeout", "percent-encoding 2.1.0", - "pin-project 1.0.10", - "prost 0.9.0", - "prost-derive 0.9.0", + "pin-project", + "prost", + "prost-derive", "tokio", "tokio-stream", "tokio-util 0.6.10", @@ -7002,7 +5756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" dependencies = [ "proc-macro2", - "prost-build 0.9.0", + "prost-build", "quote", "syn", ] @@ -7017,8 +5771,8 @@ dependencies = [ "futures-util", "hdrhistogram", "indexmap", - "pin-project 1.0.10", - "pin-project-lite 0.2.9", + "pin-project", + "pin-project-lite", "rand 0.8.5", "slab", "tokio", @@ -7035,8 +5789,8 @@ source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503 dependencies = [ "bytes 1.1.0", "futures 0.3.21", - "pin-project 1.0.10", - "prost 0.9.0", + "pin-project", + "prost", "tendermint-proto", "tokio", "tokio-stream", @@ -7073,7 +5827,7 @@ version = "0.1.30" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ "cfg-if 1.0.0", - "pin-project-lite 0.2.9", + "pin-project-lite", "tracing-attributes 0.1.19", "tracing-core 0.1.22", ] @@ -7086,7 +5840,7 @@ checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if 1.0.0", "log 0.4.17", - "pin-project-lite 0.2.9", + "pin-project-lite", "tracing-attributes 0.1.21", "tracing-core 0.1.27", ] @@ -7146,7 +5900,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project 1.0.10", + "pin-project", "tracing 0.1.35", ] @@ -7155,7 +5909,7 @@ name = "tracing-futures" version = "0.2.5" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "pin-project-lite 0.2.9", + "pin-project-lite", "tracing 0.1.30", ] @@ -7205,7 +5959,7 @@ version = "0.1.0" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ "futures 0.3.21", - "pin-project-lite 0.2.9", + "pin-project-lite", "tower-layer", "tower-make", "tower-service", @@ -7219,49 +5973,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" -[[package]] -name = "trust-dns-proto" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca94d4e9feb6a181c690c4040d7a24ef34018d8313ac5044a61d21222ae24e31" -dependencies = [ - "async-trait", - "cfg-if 1.0.0", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.2.3", - "ipnet", - "lazy_static 1.4.0", - "log 0.4.17", - "rand 0.8.5", - "smallvec 1.8.0", - "thiserror", - "tinyvec", - "url 2.2.2", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecae383baad9995efaa34ce8e57d12c3f305e545887472a492b838f4b5cfb77a" -dependencies = [ - "cfg-if 1.0.0", - "futures-util", - "ipconfig", - "lazy_static 1.4.0", - "log 0.4.17", - "lru-cache", - "parking_lot 0.11.2", - "resolv-conf", - "smallvec 1.8.0", - "thiserror", - "trust-dns-proto", -] - [[package]] name = "try-lock" version = "0.2.3" @@ -7287,25 +5998,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "tungstenite" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" -dependencies = [ - "base64 0.13.0", - "byteorder", - "bytes 1.1.0", - "http", - "httparse", - "log 0.4.17", - "rand 0.8.5", - "sha-1 0.9.8", - "thiserror", - "url 2.2.2", - "utf-8", -] - [[package]] name = "typeable" version = "0.1.2" @@ -7324,18 +6016,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" -[[package]] -name = "uint" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unicase" version = "1.4.2" @@ -7393,16 +6073,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" -[[package]] -name = "universal-hash" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" -dependencies = [ - "generic-array 0.14.5", - "subtle 2.4.1", -] - [[package]] name = "unreachable" version = "1.0.0" @@ -7412,24 +6082,6 @@ dependencies = [ "void", ] -[[package]] -name = "unsigned-varint" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fdeedbf205afadfe39ae559b75c3240f24e257d0ca27e85f85cb82aa19ac35" - -[[package]] -name = "unsigned-varint" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" -dependencies = [ - "asynchronous-codec", - "bytes 1.1.0", - "futures-io", - "futures-util", -] - [[package]] name = "untrusted" version = "0.7.1" @@ -7675,21 +6327,6 @@ dependencies = [ "leb128", ] -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures 0.3.21", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasmer" version = "2.2.0" @@ -8057,12 +6694,6 @@ dependencies = [ "libc", ] -[[package]] -name = "widestring" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" - [[package]] name = "winapi" version = "0.2.8" @@ -8192,15 +6823,6 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" -[[package]] -name = "winreg" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "winreg" version = "0.10.1" @@ -8220,17 +6842,6 @@ dependencies = [ "winapi-build", ] -[[package]] -name = "x25519-dalek" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" -dependencies = [ - "curve25519-dalek", - "rand_core 0.5.1", - "zeroize", -] - [[package]] name = "xattr" version = "0.2.3" @@ -8249,20 +6860,6 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "yamux" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" -dependencies = [ - "futures 0.3.21", - "log 0.4.17", - "nohash-hasher", - "parking_lot 0.11.2", - "rand 0.8.5", - "static_assertions", -] - [[package]] name = "zeroize" version = "1.5.5" diff --git a/Cargo.toml b/Cargo.toml index 2bb0d8ad10..0acc6646da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,6 @@ exclude = [ ] [patch.crates-io] -# TODO backported patch in the noise protocl for , blocked on libp2p upgrade -libp2p = {git = "https://github.com/heliaxdev/rust-libp2p.git", rev = "1abe349c231eb307d3dbe03f3ffffc6cf5e9084d"} # TODO temp patch for , and more tba. borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} diff --git a/Makefile b/Makefile index 6bcb62c75f..0330a569e7 100644 --- a/Makefile +++ b/Makefile @@ -140,7 +140,7 @@ build-wasm-image-docker: build-wasm-scripts-docker: build-wasm-image-docker docker run --rm -v ${PWD}:/__w/namada/namada namada-wasm make build-wasm-scripts -# Build the validity predicate, transactions, matchmaker and matchmaker filter wasm +# Build the validity predicate and transactions wasm build-wasm-scripts: make -C $(wasms) make opt-wasm diff --git a/README.md b/README.md index 323bc5e9b8..b93113ea5d 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ interaction with the protocol. ## 📓 Docs -- user docs: built from [docs mdBook](./documentation/docs/) -- dev docs: built from [dev mdBook](./documentation/dev/) -- specifications: built from [specs mdBook](./documentation/specs/) +* user docs: built from [docs mdBook](./documentation/docs/) +* dev docs: built from [dev mdBook](./documentation/dev/) +* specifications: built from [specs mdBook](./documentation/specs/) ## Warning @@ -47,7 +47,7 @@ Guide. ## ⚙️ Development ```shell -# Build the provided validity predicate, transaction and matchmaker wasm modules +# Build the provided validity predicate and transaction wasm modules make build-wasm-scripts-docker # Development (debug) build Anoma, which includes a validator and some default @@ -69,11 +69,11 @@ make clippy To change the log level, set `ANOMA_LOG` environment variable to one of: -- `error` -- `warn` -- `info` -- `debug` -- `trace` +* `error` +* `warn` +* `info` +* `debug` +* `trace` The default is set to `info` for all the modules, expect for Tendermint ABCI, which has a lot of `debug` logging. diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 65d7d84eed..23a27a65fc 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -63,7 +63,6 @@ color-eyre = "0.5.10" config = "0.11.0" curl = "0.4.43" derivative = "2.2.0" -directories = "4.0.1" ed25519-consensus = "1.2.0" ferveo = {git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} @@ -76,14 +75,11 @@ itertools = "0.10.1" jsonpath_lib = "0.3.0" libc = "0.2.97" libloading = "0.7.2" -libp2p = "0.38.0" -message-io = {version = "0.14.3", default-features = false, features = ["websocket"]} num-derive = "0.3.3" num-traits = "0.2.14" num_cpus = "1.13.0" once_cell = "1.8.0" orion = "0.16.0" -pathdiff = "0.2.1" prost = "0.9.0" prost-types = "0.9.0" rand = {version = "0.8", default-features = false} @@ -97,7 +93,6 @@ rpassword = "5.0.1" serde = {version = "1.0.125", features = ["derive"]} serde_bytes = "0.11.5" serde_json = {version = "1.0.62", features = ["raw_value"]} -serde_regex = "1.1.0" sha2 = "0.9.3" signal-hook = "0.3.9" sparse-merkle-tree = {git = "https://github.com/heliaxdev/sparse-merkle-tree", branch = "yuji/prost-0.9", features = ["borsh"]} @@ -135,4 +130,3 @@ tokio-test = "0.4.2" [build-dependencies] git2 = "0.13.25" -tonic-build = "0.6.0" diff --git a/apps/build.rs b/apps/build.rs index ae49503e78..32f7c57e87 100644 --- a/apps/build.rs +++ b/apps/build.rs @@ -1,6 +1,5 @@ -use std::fs::{read_to_string, File}; +use std::fs::File; use std::io::Write; -use std::process::Command; use std::{env, str}; use git2::{DescribeFormatOptions, DescribeOptions, Repository}; @@ -8,9 +7,6 @@ use git2::{DescribeFormatOptions, DescribeOptions, Repository}; /// Path to the .proto source files, relative to `apps` directory const PROTO_SRC: &str = "./proto"; -/// The version should match the one we use in the `Makefile` -const RUSTFMT_TOOLCHAIN_SRC: &str = "../rust-nightly-version"; - fn main() { // Discover the repository version, if it exists println!("cargo:rerun-if-changed=../.git"); @@ -66,43 +62,4 @@ fn main() { println!("cargo:rustc-cfg=feature=\"dev\""); } } - - let mut use_rustfmt = false; - - // The version should match the one we use in the `Makefile` - if let Ok(rustfmt_toolchain) = read_to_string(RUSTFMT_TOOLCHAIN_SRC) { - // Try to find the path to rustfmt. - if let Ok(output) = Command::new("rustup") - .args(&[ - "which", - "rustfmt", - "--toolchain", - rustfmt_toolchain.trim(), - ]) - .output() - { - if let Ok(rustfmt) = str::from_utf8(&output.stdout) { - // Set the command to be used by tonic_build below to format the - // generated files - let rustfmt = rustfmt.trim(); - if !rustfmt.is_empty() { - println!("using rustfmt from path \"{}\"", rustfmt); - env::set_var("RUSTFMT", rustfmt); - use_rustfmt = true - } - } - } - } - - tonic_build::configure() - .out_dir("src/lib/proto/generated") - .format(use_rustfmt) - .extern_path(".types", "::namada::proto::generated::types") - // This warning appears in tonic generated code - .server_mod_attribute(".", "#[allow(clippy::unit_arg)]") - // TODO try to add json encoding to simplify use for user - // .type_attribute("types.Intent", "#[derive(serde::Serialize, - // serde::Deserialize)]") - .compile(&[format!("{}/services.proto", PROTO_SRC)], &[PROTO_SRC]) - .unwrap(); } diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index d04eeff9ab..1da669e8ae 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -3,7 +3,7 @@ use color_eyre::eyre::Result; use namada_apps::cli; use namada_apps::cli::cmds::*; -use namada_apps::client::{gossip, rpc, tx, utils}; +use namada_apps::client::{rpc, tx, utils}; pub async fn main() -> Result<()> { match cli::anoma_client_cli() { @@ -80,13 +80,6 @@ pub async fn main() -> Result<()> { Sub::QueryProtocolParameters(QueryProtocolParameters(args)) => { rpc::query_protocol_parameters(ctx, args).await; } - // Gossip cmds - Sub::Intent(Intent(args)) => { - gossip::gossip_intent(ctx, args).await; - } - Sub::SubscribeTopic(SubscribeTopic(args)) => { - gossip::subscribe_topic(ctx, args).await; - } } } cli::AnomaClient::WithoutContext(cmd, global_args) => match cmd { diff --git a/apps/src/bin/anoma-node/cli.rs b/apps/src/bin/anoma-node/cli.rs index 407a6b7378..65ed7d59bf 100644 --- a/apps/src/bin/anoma-node/cli.rs +++ b/apps/src/bin/anoma-node/cli.rs @@ -1,8 +1,8 @@ //! Anoma node CLI. use eyre::{Context, Result}; -use namada_apps::cli::{self, args, cmds}; -use namada_apps::node::{gossip, ledger, matchmaker}; +use namada_apps::cli::{self, cmds}; +use namada_apps::node::ledger; pub fn main() -> Result<()> { let (cmd, mut ctx) = cli::anoma_node_cli(); @@ -20,55 +20,6 @@ pub fn main() -> Result<()> { .wrap_err("Failed to reset Anoma node")?; } }, - cmds::AnomaNode::Gossip(sub) => match sub { - cmds::Gossip::Run(cmds::GossipRun(args::GossipRun { - addr, - rpc, - })) => { - let config = ctx.config; - let mut gossip_cfg = config.intent_gossiper; - gossip_cfg.update(addr, rpc); - gossip::run( - gossip_cfg, - &config - .ledger - .shell - .base_dir - .join(ctx.global_config.default_chain_id.as_str()), - ) - .wrap_err("Failed to run gossip service")?; - } - }, - cmds::AnomaNode::Matchmaker(cmds::Matchmaker(args::Matchmaker { - intent_gossiper_addr, - matchmaker_path, - tx_code_path, - ledger_addr, - tx_signing_key, - tx_source_address, - })) => { - let tx_signing_key = ctx.get_cached(&tx_signing_key); - let tx_source_address = ctx.get(&tx_source_address); - - let wasm_dir = ctx.wasm_dir(); - let config = ctx.config; - let mut mm_config = config.matchmaker; - if matchmaker_path.is_some() { - mm_config.matchmaker_path = matchmaker_path; - } - if tx_code_path.is_some() { - mm_config.tx_code_path = tx_code_path; - } - - matchmaker::run( - mm_config, - intent_gossiper_addr, - ledger_addr, - tx_signing_key, - tx_source_address, - wasm_dir, - ); - } cmds::AnomaNode::Config(sub) => match sub { cmds::Config::Gen(cmds::ConfigGen) => { // If the config doesn't exit, it gets generated in the context. diff --git a/apps/src/bin/anoma/cli.rs b/apps/src/bin/anoma/cli.rs index b0ed783276..ccde0c3618 100644 --- a/apps/src/bin/anoma/cli.rs +++ b/apps/src/bin/anoma/cli.rs @@ -39,10 +39,7 @@ fn handle_command(cmd: cli::cmds::Anoma, raw_sub_cmd: String) -> Result<()> { } match cmd { - cli::cmds::Anoma::Node(_) - | cli::cmds::Anoma::Ledger(_) - | cli::cmds::Anoma::Gossip(_) - | cli::cmds::Anoma::Matchmaker(_) => { + cli::cmds::Anoma::Node(_) | cli::cmds::Anoma::Ledger(_) => { handle_subcommand("namadan", sub_args) } cli::cmds::Anoma::Client(_) @@ -52,8 +49,9 @@ fn handle_command(cmd: cli::cmds::Anoma, raw_sub_cmd: String) -> Result<()> { | cli::cmds::Anoma::TxInitNft(_) | cli::cmds::Anoma::TxMintNft(_) | cli::cmds::Anoma::TxInitProposal(_) - | cli::cmds::Anoma::TxVoteProposal(_) - | cli::cmds::Anoma::Intent(_) => handle_subcommand("namadac", sub_args), + | cli::cmds::Anoma::TxVoteProposal(_) => { + handle_subcommand("namadac", sub_args) + } cli::cmds::Anoma::Wallet(_) => handle_subcommand("namadaw", sub_args), } } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index f546860cfd..1b014cc21b 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -41,8 +41,6 @@ pub mod cmds { // Inlined commands from the node. Ledger(Ledger), - Gossip(Gossip), - Matchmaker(Matchmaker), // Inlined commands from the client. TxCustom(TxCustom), @@ -52,7 +50,6 @@ pub mod cmds { TxMintNft(TxMintNft), TxInitProposal(TxInitProposal), TxVoteProposal(TxVoteProposal), - Intent(Intent), } impl Cmd for Anoma { @@ -61,8 +58,6 @@ pub mod cmds { .subcommand(AnomaClient::def()) .subcommand(AnomaWallet::def()) .subcommand(Ledger::def()) - .subcommand(Gossip::def()) - .subcommand(Matchmaker::def()) .subcommand(TxCustom::def()) .subcommand(TxTransfer::def()) .subcommand(TxUpdateVp::def()) @@ -70,7 +65,6 @@ pub mod cmds { .subcommand(TxMintNft::def()) .subcommand(TxInitProposal::def()) .subcommand(TxVoteProposal::def()) - .subcommand(Intent::def()) } fn parse(matches: &ArgMatches) -> Option { @@ -78,8 +72,6 @@ pub mod cmds { let client = SubCmd::parse(matches).map(Self::Client); let wallet = SubCmd::parse(matches).map(Self::Wallet); let ledger = SubCmd::parse(matches).map(Self::Ledger); - let gossip = SubCmd::parse(matches).map(Self::Gossip); - let matchmaker = SubCmd::parse(matches).map(Self::Matchmaker); let tx_custom = SubCmd::parse(matches).map(Self::TxCustom); let tx_transfer = SubCmd::parse(matches).map(Self::TxTransfer); let tx_update_vp = SubCmd::parse(matches).map(Self::TxUpdateVp); @@ -89,12 +81,9 @@ pub mod cmds { SubCmd::parse(matches).map(Self::TxInitProposal); let tx_vote_proposal = SubCmd::parse(matches).map(Self::TxVoteProposal); - let intent = SubCmd::parse(matches).map(Self::Intent); node.or(client) .or(wallet) .or(ledger) - .or(gossip) - .or(matchmaker) .or(tx_custom) .or(tx_transfer) .or(tx_update_vp) @@ -102,7 +91,6 @@ pub mod cmds { .or(tx_nft_mint) .or(tx_init_proposal) .or(tx_vote_proposal) - .or(intent) } } @@ -112,25 +100,18 @@ pub mod cmds { #[allow(clippy::large_enum_variant)] pub enum AnomaNode { Ledger(Ledger), - Gossip(Gossip), - Matchmaker(Matchmaker), Config(Config), } impl Cmd for AnomaNode { fn add_sub(app: App) -> App { - app.subcommand(Ledger::def()) - .subcommand(Gossip::def()) - .subcommand(Matchmaker::def()) - .subcommand(Config::def()) + app.subcommand(Ledger::def()).subcommand(Config::def()) } fn parse(matches: &ArgMatches) -> Option { let ledger = SubCmd::parse(matches).map(Self::Ledger); - let gossip = SubCmd::parse(matches).map(Self::Gossip); - let matchmaker = SubCmd::parse(matches).map(Self::Matchmaker); let config = SubCmd::parse(matches).map(Self::Config); - ledger.or(gossip).or(matchmaker).or(config) + ledger.or(config) } } impl SubCmd for AnomaNode { @@ -194,9 +175,6 @@ pub mod cmds { .subcommand(QueryProposal::def().display_order(3)) .subcommand(QueryProposalResult::def().display_order(3)) .subcommand(QueryProtocolParameters::def().display_order(3)) - // Intents - .subcommand(Intent::def().display_order(4)) - .subcommand(SubscribeTopic::def().display_order(4)) // Utils .subcommand(Utils::def().display_order(5)) } @@ -231,8 +209,6 @@ pub mod cmds { Self::parse_with_ctx(matches, QueryProposalResult); let query_protocol_parameters = Self::parse_with_ctx(matches, QueryProtocolParameters); - let intent = Self::parse_with_ctx(matches, Intent); - let subscribe_topic = Self::parse_with_ctx(matches, SubscribeTopic); let utils = SubCmd::parse(matches).map(Self::WithoutContext); tx_custom .or(tx_transfer) @@ -256,8 +232,6 @@ pub mod cmds { .or(query_proposal) .or(query_proposal_result) .or(query_protocol_parameters) - .or(intent) - .or(subscribe_topic) .or(utils) } } @@ -316,9 +290,6 @@ pub mod cmds { QueryProposal(QueryProposal), QueryProposalResult(QueryProposalResult), QueryProtocolParameters(QueryProtocolParameters), - // Gossip cmds - Intent(Intent), - SubscribeTopic(SubscribeTopic), } #[derive(Clone, Debug)] @@ -657,76 +628,6 @@ pub mod cmds { } } - #[derive(Clone, Debug)] - pub enum Gossip { - Run(GossipRun), - } - - impl SubCmd for Gossip { - const CMD: &'static str = "gossip"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).and_then(|matches| { - let run = SubCmd::parse(matches).map(Gossip::Run); - run - // The `run` command is the default if no sub-command given - .or_else(|| { - Some(Gossip::Run(GossipRun(args::GossipRun::parse( - matches, - )))) - }) - }) - } - - fn def() -> App { - App::new(Self::CMD) - .about( - "Gossip node sub-commands. If no sub-command specified, \ - defaults to run the node.", - ) - .subcommand(GossipRun::def()) - .add_args::() - } - } - - #[derive(Clone, Debug)] - pub struct Matchmaker(pub args::Matchmaker); - - impl SubCmd for Matchmaker { - const CMD: &'static str = "matchmaker"; - - fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| Matchmaker(args::Matchmaker::parse(matches))) - } - - fn def() -> App { - App::new(Self::CMD) - .about("Run a matchmaker.") - .add_args::() - } - } - - #[derive(Clone, Debug)] - pub struct GossipRun(pub args::GossipRun); - - impl SubCmd for GossipRun { - const CMD: &'static str = "run"; - - fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| GossipRun(args::GossipRun::parse(matches))) - } - - fn def() -> App { - App::new(Self::CMD) - .about("Run a gossip node.") - .add_args::() - } - } - #[derive(Clone, Debug)] pub enum Config { Gen(ConfigGen), @@ -1218,47 +1119,6 @@ pub mod cmds { } } - #[derive(Clone, Debug)] - pub struct Intent(pub args::Intent); - - impl SubCmd for Intent { - const CMD: &'static str = "intent"; - - fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| Intent(args::Intent::parse(matches))) - } - - fn def() -> App { - App::new(Self::CMD) - .about("Send an intent.") - .add_args::() - } - } - - #[derive(Clone, Debug)] - pub struct SubscribeTopic(pub args::SubscribeTopic); - - impl SubCmd for SubscribeTopic { - const CMD: &'static str = "subscribe-topic"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).map(|matches| { - SubscribeTopic(args::SubscribeTopic::parse(matches)) - }) - } - - fn def() -> App { - App::new(Self::CMD) - .about( - "Subscribe intent gossip node with a matchmaker to a \ - topic.", - ) - .add_args::() - } - } - #[derive(Clone, Debug)] pub enum Utils { JoinNetwork(JoinNetwork), @@ -1355,23 +1215,18 @@ pub mod cmds { pub mod args { - use std::convert::TryFrom; use std::env; - use std::fs::File; use std::net::SocketAddr; use std::path::PathBuf; use std::str::FromStr; - use libp2p::Multiaddr; use namada::types::address::Address; use namada::types::chain::{ChainId, ChainIdPrefix}; use namada::types::governance::ProposalVote; - use namada::types::intent::{DecimalWrapper, Exchange}; use namada::types::key::*; use namada::types::storage::{self, Epoch}; use namada::types::token; use namada::types::transaction::GasLimit; - use serde::Deserialize; use tendermint::Timeout; use tendermint_config::net::Address as TendermintAddress; @@ -1419,13 +1274,6 @@ pub mod args { arg_default("gas-limit", DefaultFn(|| token::Amount::from(0))); const GENESIS_PATH: Arg = arg("genesis-path"); const GENESIS_VALIDATOR: ArgOpt = arg("genesis-validator").opt(); - const INTENT_GOSSIPER_ADDR: ArgDefault = arg_default( - "intent-gossiper", - DefaultFn(|| { - let raw = "127.0.0.1:26661"; - SocketAddr::from_str(raw).unwrap() - }), - ); const LEDGER_ADDRESS_ABOUT: &str = "Address of a ledger node as \"{scheme}://{host}:{port}\". If the \ scheme is not supplied, it is assumed to be TCP."; @@ -1437,12 +1285,8 @@ pub mod args { const LEDGER_ADDRESS: Arg = arg("ledger-address"); const LOCALHOST: ArgFlag = flag("localhost"); - const MATCHMAKER_PATH: ArgOpt = arg_opt("matchmaker-path"); const MODE: ArgOpt = arg_opt("mode"); - const MULTIADDR_OPT: ArgOpt = arg_opt("address"); const NET_ADDRESS: Arg = arg("net-address"); - const NODE_OPT: ArgOpt = arg_opt("node"); - const NODE: Arg = arg("node"); const NFT_ADDRESS: Arg
= arg("nft-address"); const OWNER: ArgOpt = arg_opt("owner"); const PROPOSAL_OFFLINE: ArgFlag = flag("offline"); @@ -1456,7 +1300,6 @@ pub mod args { const RAW_PUBLIC_KEY_OPT: ArgOpt = arg_opt("public-key"); const REWARDS_CODE_PATH: ArgOpt = arg_opt("rewards-code-path"); const REWARDS_KEY: ArgOpt = arg_opt("rewards-key"); - const RPC_SOCKET_ADDR: ArgOpt = arg_opt("rpc"); const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519)); const SIGNER: ArgOpt = arg_opt("signer"); @@ -1466,12 +1309,8 @@ pub mod args { const SOURCE_OPT: ArgOpt = SOURCE.opt(); const STORAGE_KEY: Arg = arg("storage-key"); const TARGET: Arg = arg("target"); - const TO_STDOUT: ArgFlag = flag("stdout"); const TOKEN_OPT: ArgOpt = TOKEN.opt(); const TOKEN: Arg = arg("token"); - const TOPIC_OPT: ArgOpt = arg_opt("topic"); - const TOPIC: Arg = arg("topic"); - const TX_CODE_PATH: ArgOpt = arg_opt("tx-code-path"); const TX_HASH: Arg = arg("tx-hash"); const UNSAFE_DONT_ENCRYPT: ArgFlag = flag("unsafe-dont-encrypt"); const UNSAFE_SHOW_SECRET: ArgFlag = flag("unsafe-show-secret"); @@ -1523,11 +1362,11 @@ pub mod args { )) .arg(WASM_DIR.def().about( "Directory with built WASM validity predicates, \ - transactions and matchmaker files. This must not be an \ - absolute path as the directory is nested inside the \ - chain directory. This value can also be set via \ - `ANOMA_WASM_DIR` environment variable, but the argument \ - takes precedence, if specified.", + transactions. This must not be an absolute path as the \ + directory is nested inside the chain directory. This \ + value can also be set via `ANOMA_WASM_DIR` environment \ + variable, but the argument takes precedence, if \ + specified.", )) .arg(MODE.def().about( "The mode in which to run Anoma. Options are \n\t * \ @@ -2214,67 +2053,6 @@ pub mod args { } } - /// Helper struct for generating intents - #[derive(Debug, Clone, Deserialize)] - pub struct ExchangeDefinition { - /// The source address - pub addr: String, - /// The token to be sold - pub token_sell: String, - /// The minimum rate - pub rate_min: String, - /// The maximum amount of token to be sold - pub max_sell: String, - /// The token to be bought - pub token_buy: String, - /// The amount of token to be bought - pub min_buy: String, - /// The path to the wasm vp code - pub vp_path: Option, - } - - impl TryFrom for Exchange { - type Error = &'static str; - - fn try_from( - value: ExchangeDefinition, - ) -> Result { - let vp = if let Some(path) = value.vp_path { - if let Ok(wasm) = std::fs::read(path.clone()) { - Some(wasm) - } else { - eprintln!("File {} was not found.", path); - None - } - } else { - None - }; - - let addr = Address::decode(value.addr) - .expect("Addr should be a valid address"); - let token_buy = Address::decode(value.token_buy) - .expect("Token_buy should be a valid address"); - let token_sell = Address::decode(value.token_sell) - .expect("Token_sell should be a valid address"); - let min_buy = token::Amount::from_str(&value.min_buy) - .expect("Min_buy must be convertible to number"); - let max_sell = token::Amount::from_str(&value.max_sell) - .expect("Max_sell must be convertible to number"); - let rate_min = DecimalWrapper::from_str(&value.rate_min) - .expect("Max_sell must be convertible to decimal."); - - Ok(Exchange { - addr, - token_sell, - rate_min, - max_sell, - token_buy, - min_buy, - vp, - }) - } - } - /// Query PoS bond(s) #[derive(Clone, Debug)] pub struct QueryBonds { @@ -2393,218 +2171,6 @@ pub mod args { .arg(STORAGE_KEY.def().about("Storage key")) } } - /// Intent arguments - #[derive(Clone, Debug)] - pub struct Intent { - /// Gossip node address - pub node_addr: Option, - /// Intent topic - pub topic: Option, - /// Source address - pub source: Option, - /// Signing key - pub signing_key: Option, - /// Exchanges description - pub exchanges: Vec, - /// The address of the ledger node as host:port - pub ledger_address: TendermintAddress, - /// Print output to stdout - pub to_stdout: bool, - } - - impl Args for Intent { - fn parse(matches: &ArgMatches) -> Self { - let node_addr = NODE_OPT.parse(matches); - let data_path = DATA_PATH.parse(matches); - let source = SOURCE_OPT.parse(matches); - let signing_key = SIGNING_KEY_OPT.parse(matches); - let to_stdout = TO_STDOUT.parse(matches); - let topic = TOPIC_OPT.parse(matches); - - let file = File::open(&data_path).expect("File must exist."); - let exchange_definitions: Vec = - serde_json::from_reader(file) - .expect("JSON was not well-formatted"); - - let exchanges: Vec = exchange_definitions - .iter() - .map(|item| { - Exchange::try_from(item.clone()).expect( - "Conversion from ExchangeDefinition to Exchange \ - should not fail.", - ) - }) - .collect(); - let ledger_address = LEDGER_ADDRESS_DEFAULT.parse(matches); - - Self { - node_addr, - topic, - source, - signing_key, - exchanges, - ledger_address, - to_stdout, - } - } - - fn def(app: App) -> App { - app.arg( - NODE_OPT - .def() - .about("The gossip node address.") - .conflicts_with(TO_STDOUT.name), - ) - .arg(DATA_PATH.def().about( - "The data of the intent, that contains all value necessary \ - for the matchmaker.", - )) - .arg( - SOURCE_OPT - .def() - .about( - "Sign the intent with the key of a given address or \ - address alias from your wallet.", - ) - .conflicts_with(SIGNING_KEY_OPT.name), - ) - .arg( - SIGNING_KEY_OPT - .def() - .about( - "Sign the intent with the key for the given public \ - key, public key hash or alias from your wallet.", - ) - .conflicts_with(SOURCE_OPT.name), - ) - .arg(LEDGER_ADDRESS_DEFAULT.def().about(LEDGER_ADDRESS_ABOUT)) - .arg( - TOPIC_OPT - .def() - .about("The subnetwork where the intent should be sent to.") - .conflicts_with(TO_STDOUT.name), - ) - .arg( - TO_STDOUT - .def() - .about( - "Echo the serialized intent to stdout. Note that with \ - this option, the intent won't be submitted to the \ - intent gossiper RPC.", - ) - .conflicts_with_all(&[NODE_OPT.name, TOPIC.name]), - ) - } - } - - /// Subscribe intent topic arguments - #[derive(Clone, Debug)] - pub struct SubscribeTopic { - /// Gossip node address - pub node_addr: String, - /// Intent topic - pub topic: String, - } - - impl Args for SubscribeTopic { - fn parse(matches: &ArgMatches) -> Self { - let node_addr = NODE.parse(matches); - let topic = TOPIC.parse(matches); - Self { node_addr, topic } - } - - fn def(app: App) -> App { - app.arg(NODE.def().about("The gossip node address.")).arg( - TOPIC - .def() - .about("The new topic of interest for that node."), - ) - } - } - - #[derive(Clone, Debug)] - pub struct GossipRun { - pub addr: Option, - pub rpc: Option, - } - - impl Args for GossipRun { - fn parse(matches: &ArgMatches) -> Self { - let addr = MULTIADDR_OPT.parse(matches); - let rpc = RPC_SOCKET_ADDR.parse(matches); - Self { addr, rpc } - } - - fn def(app: App) -> App { - app.arg( - MULTIADDR_OPT - .def() - .about("Gossip service address as host:port."), - ) - .arg(RPC_SOCKET_ADDR.def().about("Enable RPC service.")) - } - } - - #[derive(Clone, Debug)] - pub struct Matchmaker { - pub matchmaker_path: Option, - pub tx_code_path: Option, - pub intent_gossiper_addr: SocketAddr, - pub ledger_addr: TendermintAddress, - pub tx_signing_key: WalletKeypair, - pub tx_source_address: WalletAddress, - } - - impl Args for Matchmaker { - fn parse(matches: &ArgMatches) -> Self { - let intent_gossiper_addr = INTENT_GOSSIPER_ADDR.parse(matches); - let matchmaker_path = MATCHMAKER_PATH.parse(matches); - let tx_code_path = TX_CODE_PATH.parse(matches); - let ledger_addr = LEDGER_ADDRESS_DEFAULT.parse(matches); - let tx_signing_key = SIGNING_KEY.parse(matches); - let tx_source_address = SOURCE.parse(matches); - Self { - intent_gossiper_addr, - matchmaker_path, - tx_code_path, - ledger_addr, - tx_signing_key, - tx_source_address, - } - } - - fn def(app: App) -> App { - app.arg(INTENT_GOSSIPER_ADDR.def().about( - "Intent Gossiper endpoint for matchmaker connections as \ - \"{host}:{port}\".", - )) - .arg(MATCHMAKER_PATH.def().about( - "The file name of the matchmaker compiled to a dynamic \ - library (the filename extension is optional).", - )) - .arg( - TX_CODE_PATH - .def() - .about("The transaction code to use with the matchmaker."), - ) - .arg(LEDGER_ADDRESS_DEFAULT.def().about( - "The address of the ledger as \"{scheme}://{host}:{port}\" \ - that the matchmaker must send transactions to. If the scheme \ - is not supplied, it is assumed to be TCP.", - )) - .arg(SIGNING_KEY.def().about( - "Sign the transactions created by the matchmaker with the key \ - for the given public key, public key hash or alias from your \ - wallet.", - )) - .arg(SOURCE.def().about( - "Source address or alias of an address of the transactions \ - created by the matchmaker. This must be matching the signing \ - key.", - )) - } - } - /// Common transaction arguments #[derive(Clone, Debug)] pub struct Tx { @@ -3004,7 +2570,7 @@ pub mod args { )) .arg(LOCALHOST.def().about( "Use localhost address for P2P and RPC connections for the \ - validators ledger and intent gossip nodes", + validators ledger", )) .arg(ALLOW_DUPLICATE_IP.def().about( "Toggle to disable guard against peers connecting from the \ diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 8189b633bf..ca5daff8fe 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -44,7 +44,7 @@ pub struct Context { pub wallet: Wallet, /// The global configuration pub global_config: GlobalConfig, - /// The ledger & intent gossip configuration for a specific chain ID + /// The ledger configuration for a specific chain ID pub config: Config, } diff --git a/apps/src/lib/client/gossip.rs b/apps/src/lib/client/gossip.rs deleted file mode 100644 index 2225898ff4..0000000000 --- a/apps/src/lib/client/gossip.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::collections::HashSet; -use std::io::Write; - -use borsh::BorshSerialize; -use namada::proto::Signed; -use namada::types::intent::{Exchange, FungibleTokenIntent}; -use tendermint_config::net::Address as TendermintAddress; - -use super::signing; -use crate::cli::{self, args, Context}; -use crate::proto::services::rpc_service_client::RpcServiceClient; -use crate::proto::{services, RpcMessage}; -use crate::wallet::Wallet; - -/// Create an intent, sign it and submit it to the gossip node (unless -/// `to_stdout` is `true`). -pub async fn gossip_intent( - mut ctx: Context, - args::Intent { - node_addr, - topic, - source, - signing_key, - exchanges, - ledger_address, - to_stdout, - }: args::Intent, -) { - let mut signed_exchanges: HashSet> = - HashSet::with_capacity(exchanges.len()); - for exchange in exchanges { - let signed = - sign_exchange(&mut ctx.wallet, exchange, ledger_address.clone()) - .await; - signed_exchanges.insert(signed); - } - - let source_keypair = match ctx.get_opt_cached(&signing_key) { - Some(key) => key, - None => { - let source = ctx.get_opt(&source).unwrap_or_else(|| { - eprintln!("A source or a signing key is required."); - cli::safe_exit(1) - }); - signing::find_keypair( - &mut ctx.wallet, - &source, - ledger_address.clone(), - ) - .await - } - }; - let signed_ft: Signed = Signed::new( - &*source_keypair, - FungibleTokenIntent { - exchange: signed_exchanges, - }, - ); - let data_bytes = signed_ft.try_to_vec().unwrap(); - - if to_stdout { - let mut out = std::io::stdout(); - out.write_all(&data_bytes).unwrap(); - out.flush().unwrap(); - } else { - let node_addr = node_addr.expect( - "Gossip node address must be defined to submit the intent to it.", - ); - let topic = topic.expect( - "The topic must be defined to submit the intent to a gossip node.", - ); - - match RpcServiceClient::connect(node_addr.clone()).await { - Ok(mut client) => { - let intent = namada::proto::Intent::new(data_bytes); - let message: services::RpcMessage = - RpcMessage::new_intent(intent, topic).into(); - let response = client.send_message(message).await.expect( - "Failed to send message and/or receive rpc response", - ); - println!("{:#?}", response); - } - Err(e) => { - eprintln!( - "Error connecting RPC client to {}: {}", - node_addr, e - ); - } - }; - } -} - -/// Request an intent gossip node with a matchmaker to subscribe to a given -/// topic. -pub async fn subscribe_topic( - _ctx: Context, - args::SubscribeTopic { node_addr, topic }: args::SubscribeTopic, -) { - let mut client = RpcServiceClient::connect(node_addr).await.unwrap(); - let message: services::RpcMessage = RpcMessage::new_topic(topic).into(); - let response = client - .send_message(message) - .await - .expect("failed to send message and/or receive rpc response"); - println!("{:#?}", response); -} - -async fn sign_exchange( - wallet: &mut Wallet, - exchange: Exchange, - ledger_address: TendermintAddress, -) -> Signed { - let source_keypair = - signing::find_keypair(wallet, &exchange.addr, ledger_address).await; - Signed::new(&*source_keypair, exchange.clone()) -} diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index a3d2ddece9..e27e575ce0 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -1,4 +1,3 @@ -pub mod gossip; pub mod rpc; pub mod signing; pub mod tendermint_rpc_types; diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 9043a14db7..d2d49341fe 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::env; use std::fs::{self, File, OpenOptions}; use std::io::Write; @@ -27,10 +27,7 @@ use crate::config::genesis::genesis_config::{ self, HexString, ValidatorPreGenesisConfig, }; use crate::config::global::GlobalConfig; -use crate::config::{ - self, Config, IntentGossiper, PeerAddress, TendermintMode, -}; -use crate::node::gossip; +use crate::config::{self, Config, TendermintMode}; use crate::node::ledger::tendermint_node; use crate::wallet::{pre_genesis, Wallet}; use crate::wasm_loader; @@ -422,25 +419,10 @@ pub fn init_network( let mut rng: ThreadRng = thread_rng(); + // Accumulator of validators' Tendermint P2P addresses let mut persistent_peers: Vec = Vec::with_capacity(config.validator.len()); - // Intent gossiper config bootstrap peers where we'll add the address for - // each validator's node - let mut seed_peers: HashSet = - HashSet::with_capacity(config.validator.len()); - let mut gossiper_configs: HashMap = - HashMap::with_capacity(config.validator.len()); - let mut matchmaker_configs: HashMap = - HashMap::with_capacity(config.validator.len()); - // Other accounts owned by one of the validators - let mut validator_owned_accounts: HashMap< - String, - genesis_config::EstablishedAccountConfig, - > = HashMap::default(); - - // We need a temporary copy to be able to use this inside the validator - // loop, which has mutable borrow on the config. - let established_accounts = config.established.clone(); + // Iterate over each validator, generating keys and addresses config.validator.iter_mut().for_each(|(name, config)| { let validator_dir = accounts_dir.join(name); @@ -477,36 +459,6 @@ pub fn init_network( )) .expect("Validator address must be valid"); persistent_peers.push(peer); - // Add a Intent gossiper bootstrap peer from the validator's IP - let mut gossiper_config = IntentGossiper::default(); - // Generate P2P identity - let p2p_identity = gossip::p2p::Identity::gen(&chain_dir); - let peer_id = p2p_identity.peer_id(); - let ledger_addr = - SocketAddr::from_str(config.net_address.as_ref().unwrap()).unwrap(); - let ip = ledger_addr.ip().to_string(); - let first_port = ledger_addr.port(); - let intent_peer_address = libp2p::Multiaddr::from_str( - format!("/ip4/{}/tcp/{}", ip, first_port + 3).as_str(), - ) - .unwrap(); - - gossiper_config.address = if localhost { - intent_peer_address.clone() - } else { - libp2p::Multiaddr::from_str( - format!("/ip4/0.0.0.0/tcp/{}", first_port + 3).as_str(), - ) - .unwrap() - }; - if let Some(discover) = gossiper_config.discover_peer.as_mut() { - // Disable mDNS local network peer discovery on the validator nodes - discover.mdns = false; - } - let intent_peer = PeerAddress { - address: intent_peer_address, - peer_id, - }; // Generate account and reward addresses let address = address::gen_established_address("validator account"); @@ -634,93 +586,20 @@ pub fn init_network( wallet.add_address(name.clone(), address); wallet.add_address(format!("{}-reward", &name), reward_address); - // Check if there's a matchmaker configured for this validator node - match ( - &config.matchmaker_account, - &config.matchmaker_code, - &config.matchmaker_tx, - ) { - (Some(account), Some(mm_code), Some(tx_code)) => { - if config.intent_gossip_seed.unwrap_or_default() { - eprintln!("A bootstrap node cannot run matchmakers"); - cli::safe_exit(1) - } - match established_accounts.as_ref().and_then(|e| e.get(account)) - { - Some(matchmaker) => { - let mut matchmaker = matchmaker.clone(); - - init_established_account( - account, - &mut wallet, - &mut matchmaker, - unsafe_dont_encrypt, - ); - validator_owned_accounts - .insert(account.clone(), matchmaker); - - let matchmaker_config = config::Matchmaker { - matchmaker_path: Some(mm_code.clone().into()), - tx_code_path: Some(tx_code.clone().into()), - }; - matchmaker_configs - .insert(name.clone(), matchmaker_config); - } - None => { - eprintln!( - "Misconfigured validator's matchmaker. No \ - established account with alias {} found", - account - ); - cli::safe_exit(1) - } - } - } - (None, None, None) => {} - _ => { - eprintln!( - "Misconfigured validator's matchmaker. \ - `matchmaker_account`, `matchmaker_code` and \ - `matchmaker_tx` must be all or none present." - ); - cli::safe_exit(1) - } - } - - // Store the gossip config - gossiper_configs.insert(name.clone(), gossiper_config); - if config.intent_gossip_seed.unwrap_or_default() { - seed_peers.insert(intent_peer); - } - wallet.save().unwrap(); }); - if seed_peers.is_empty() && config.validator.len() > 1 { - tracing::warn!( - "At least 1 validator with `intent_gossip_seed = true` is needed \ - to established connection between the intent gossiper nodes" - ); - } - // Create a wallet for all accounts other than validators let mut wallet = Wallet::load_or_new(&accounts_dir.join(NET_OTHER_ACCOUNTS_DIR)); if let Some(established) = &mut config.established { established.iter_mut().for_each(|(name, config)| { - match validator_owned_accounts.get(name) { - Some(validator_owned) => { - *config = validator_owned.clone(); - } - None => { - init_established_account( - name, - &mut wallet, - config, - unsafe_dont_encrypt, - ); - } - } + init_established_account( + name, + &mut wallet, + config, + unsafe_dont_encrypt, + ); }) } @@ -857,7 +736,7 @@ pub fn init_network( wallet.save().unwrap(); }); - // Generate the validators' ledger and intent gossip config + // Generate the validators' ledger config config.validator.iter_mut().enumerate().for_each( |(ix, (name, validator_config))| { let accounts_dir = chain_dir.join(NET_ACCOUNTS_DIR); @@ -920,26 +799,6 @@ pub fn init_network( // Validator node should turned off peer exchange reactor config.ledger.tendermint.p2p_pex = false; - // Configure the intent gossiper, matchmaker (if any) and RPC - config.intent_gossiper = gossiper_configs.remove(name).unwrap(); - config.intent_gossiper.seed_peers = seed_peers.clone(); - config.matchmaker = - matchmaker_configs.remove(name).unwrap_or_default(); - config.intent_gossiper.rpc = Some(config::RpcServer { - address: SocketAddr::new( - IpAddr::V4(if localhost { - Ipv4Addr::new(127, 0, 0, 1) - } else { - Ipv4Addr::new(0, 0, 0, 0) - }), - first_port + 4, - ), - }); - config - .intent_gossiper - .matchmakers_server_addr - .set_port(first_port + 5); - config.write(&validator_dir, &chain_id, true).unwrap(); }, ); @@ -960,7 +819,6 @@ pub fn init_network( } config.ledger.tendermint.p2p_addr_book_strict = !localhost; config.ledger.genesis_time = genesis.genesis_time.into(); - config.intent_gossiper.seed_peers = seed_peers; config .write(&global_args.base_dir, &chain_id, true) .unwrap(); diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 5bd3dc803f..4462c62960 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -189,16 +189,6 @@ pub mod genesis_config { pub staking_reward_vp: Option, // IP:port of the validator. (used in generation only) pub net_address: Option, - /// Matchmaker account's alias, if any - pub matchmaker_account: Option, - /// Path to a matchmaker WASM program, if any - pub matchmaker_code: Option, - /// Path to a transaction WASM code used by the matchmaker, if any - pub matchmaker_tx: Option, - /// Is this validator running a seed intent gossip node? A seed node is - /// not part of the gossipsub where intents are being propagated and - /// hence cannot run matchmakers - pub intent_gossip_seed: Option, /// Tendermint node key is used to derive Tendermint node ID for node /// authentication pub tendermint_node_key: Option, @@ -803,13 +793,6 @@ pub fn genesis() -> Genesis { public_key: Some(wallet::defaults::christel_keypair().ref_to()), storage: HashMap::default(), }; - let matchmaker = EstablishedAccount { - address: wallet::defaults::matchmaker_address(), - vp_code_path: vp_user_path.into(), - vp_sha256: Default::default(), - public_key: Some(wallet::defaults::matchmaker_keypair().ref_to()), - storage: HashMap::default(), - }; let implicit_accounts = vec![ImplicitAccount { public_key: wallet::defaults::daewon_keypair().ref_to(), }]; @@ -836,10 +819,6 @@ pub fn genesis() -> Genesis { default_key_tokens, ), ((&validator.account_key).into(), default_key_tokens), - ( - matchmaker.public_key.as_ref().unwrap().into(), - default_key_tokens, - ), ]); let token_accounts = address::tokens() .into_iter() @@ -853,7 +832,7 @@ pub fn genesis() -> Genesis { Genesis { genesis_time: DateTimeUtc::now(), validators: vec![validator], - established_accounts: vec![albert, bertha, christel, matchmaker], + established_accounts: vec![albert, bertha, christel], implicit_accounts, token_accounts, parameters, diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 987077d5cd..17b9529684 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -4,21 +4,15 @@ pub mod genesis; pub mod global; pub mod utils; -use std::collections::HashSet; -use std::fmt::Display; use std::fs::{create_dir_all, File}; use std::io::Write; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::{Path, PathBuf}; use std::str::FromStr; -use libp2p::multiaddr::{Multiaddr, Protocol}; -use libp2p::multihash::Multihash; -use libp2p::PeerId; use namada::types::chain::ChainId; use namada::types::time::Rfc3339String; -use regex::Regex; -use serde::{de, Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use tendermint::Timeout; use tendermint_config::net::Address as TendermintAddress; use thiserror::Error; @@ -43,9 +37,6 @@ pub const DB_DIR: &str = "db"; pub struct Config { pub wasm_dir: PathBuf, pub ledger: Ledger, - pub intent_gossiper: IntentGossiper, - // TODO allow to configure multiple matchmakers - pub matchmaker: Matchmaker, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -126,32 +117,6 @@ pub struct Tendermint { pub instrumentation_namespace: String, } -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct IntentGossiper { - // Simple values - pub address: Multiaddr, - pub topics: HashSet, - /// The server address to which matchmakers can connect to receive intents - pub matchmakers_server_addr: SocketAddr, - - // Nested structures ⚠️ no simple values below any of these ⚠️ - pub subscription_filter: SubscriptionFilter, - pub seed_peers: HashSet, - pub rpc: Option, - pub discover_peer: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct RpcServer { - pub address: SocketAddr, -} - -#[derive(Default, Debug, Serialize, Deserialize, Clone)] -pub struct Matchmaker { - pub matchmaker_path: Option, - pub tx_code_path: Option, -} - impl Ledger { pub fn new( base_dir: impl AsRef, @@ -228,38 +193,6 @@ impl Shell { } } -// TODO maybe add also maxCount for a maximum number of subscription for a -// filter. - -// TODO toml failed to serialize without "untagged" because does not support -// enum with nested data, unless with the untagged flag. This might be a source -// of confusion in the future... Another approach would be to have multiple -// field for each filter possibility but it's less nice. -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(untagged)] -pub enum SubscriptionFilter { - RegexFilter(#[serde(with = "serde_regex")] Regex), - WhitelistFilter(Vec), -} - -// TODO peer_id can be part of Multiaddr, mayby this splitting is not useful ? -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] -pub struct PeerAddress { - pub address: Multiaddr, - pub peer_id: PeerId, -} - -// TODO add reserved_peers: explicit peers for gossipsub network, to not be -// added to kademlia -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct DiscoverPeer { - pub max_discovery_peers: u64, - /// Toggle Kademlia remote peer discovery, on by default - pub kademlia: bool, - /// Toggle local network mDNS peer discovery, off by default - pub mdns: bool, -} - #[derive(Error, Debug)] pub enum Error { #[error("Error while reading config: {0}")] @@ -302,8 +235,6 @@ impl Config { Self { wasm_dir: DEFAULT_WASM_DIR.into(), ledger: Ledger::new(base_dir, chain_id, mode), - intent_gossiper: IntentGossiper::default(), - matchmaker: Matchmaker::default(), } } @@ -412,97 +343,6 @@ impl Config { } } -impl Default for IntentGossiper { - fn default() -> Self { - Self { - address: Multiaddr::from_str("/ip4/0.0.0.0/tcp/26659").unwrap(), - topics: vec!["asset_v0"].into_iter().map(String::from).collect(), - matchmakers_server_addr: SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - 26661, - ), - subscription_filter: SubscriptionFilter::RegexFilter( - Regex::new("asset_v\\d{1,2}").unwrap(), - ), - seed_peers: HashSet::default(), - rpc: None, - discover_peer: Some(DiscoverPeer::default()), - } - } -} - -impl IntentGossiper { - pub fn update(&mut self, addr: Option, rpc: Option) { - if let Some(addr) = addr { - self.address = addr; - } - if let Some(address) = rpc { - self.rpc = Some(RpcServer { address }); - } - } -} - -impl Default for RpcServer { - fn default() -> Self { - Self { - address: SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - 26660, - ), - } - } -} - -impl Serialize for PeerAddress { - fn serialize( - &self, - serializer: S, - ) -> std::result::Result - where - S: serde::Serializer, - { - let mut address = self.address.clone(); - address.push(Protocol::P2p(Multihash::from(self.peer_id))); - address.serialize(serializer) - } -} - -impl de::Error for SerdeError { - fn custom(msg: T) -> Self { - SerdeError::Message(msg.to_string()) - } -} - -impl<'de> Deserialize<'de> for PeerAddress { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - use serde::de::Error; - - let mut address = Multiaddr::deserialize(deserializer) - .map_err(|err| SerdeError::BadBootstrapPeerFormat(err.to_string())) - .map_err(D::Error::custom)?; - if let Some(Protocol::P2p(mh)) = address.pop() { - let peer_id = PeerId::from_multihash(mh).unwrap(); - Ok(Self { address, peer_id }) - } else { - Err(SerdeError::BadBootstrapPeerFormat(address.to_string())) - .map_err(D::Error::custom) - } - } -} - -impl Default for DiscoverPeer { - fn default() -> Self { - Self { - max_discovery_peers: 16, - kademlia: true, - mdns: false, - } - } -} - pub const VALUE_AFTER_TABLE_ERROR_MSG: &str = r#" Error while serializing to toml. It means that some nested structure is followed by simple fields. diff --git a/apps/src/lib/mod.rs b/apps/src/lib/mod.rs index eca89896eb..b0455934bd 100644 --- a/apps/src/lib/mod.rs +++ b/apps/src/lib/mod.rs @@ -10,7 +10,6 @@ pub mod client; pub mod config; pub mod logging; pub mod node; -pub mod proto; pub mod wallet; pub mod wasm_loader; diff --git a/apps/src/lib/node/gossip/intent_gossiper.rs b/apps/src/lib/node/gossip/intent_gossiper.rs deleted file mode 100644 index 2c7816a5bf..0000000000 --- a/apps/src/lib/node/gossip/intent_gossiper.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::net::ToSocketAddrs; -use std::sync::{Arc, RwLock}; - -use namada::proto::{Intent, IntentId}; - -use super::mempool::IntentMempool; -use super::rpc::matchmakers::{ - MsgFromClient, MsgFromServer, ServerDialer, ServerListener, -}; - -/// A server for connected matchmakers that can receive intents from the intent -/// gossiper node and send back the results from their filter, if any, or from -/// trying to match them. -#[derive(Debug, Default)] -pub struct MatchmakersServer { - /// A node listener and its abort receiver. These are consumed once the - /// listener is started with [`MatchmakersServer::listen`]. - listener: Option, - /// Known intents mempool, shared with [`IntentGossiper`]. - mempool: Arc>, -} - -/// Intent gossiper handle can be cloned and is thread safe. -#[derive(Clone, Debug)] -pub struct IntentGossiper { - /// Known intents mempool, shared with [`MatchmakersServer`]. - mempool: Arc>, - /// A dialer can send messages to the connected matchmaker - dialer: ServerDialer, -} - -impl MatchmakersServer { - /// Create a new gossip intent app with a matchmaker, if enabled. - pub fn new_pair( - matchmakers_server_addr: impl ToSocketAddrs, - ) -> (Self, IntentGossiper) { - // Prepare a server for matchmakers connections - let (listener, dialer) = - ServerListener::new_pair(matchmakers_server_addr); - - let mempool = Arc::new(RwLock::new(IntentMempool::default())); - let intent_gossiper = IntentGossiper { - mempool: mempool.clone(), - dialer, - }; - ( - Self { - listener: Some(listener), - mempool, - }, - intent_gossiper, - ) - } - - pub async fn listen(mut self) { - self.listener - .take() - .unwrap() - .listen(|msg| match msg { - MsgFromClient::InvalidIntent { id } => { - let id = IntentId(id); - // Remove matched intents from mempool - tracing::info!("Removing matched intent ID {}", id); - let mut w_mempool = self.mempool.write().unwrap(); - w_mempool.remove(&id); - } - MsgFromClient::IntentConstraintsTooComplex { id } => { - let id = IntentId(id); - tracing::info!( - "Intent ID {} has constraints that are too complex \ - for a connected matchmaker", - id - ); - } - MsgFromClient::IgnoredIntent { id } => { - let id = IntentId(id); - tracing::info!( - "Intent ID {} ignored by a connected matchmaker", - id - ); - } - MsgFromClient::Matched { intent_ids } => { - // Remove matched intents from mempool - let mut w_mempool = self.mempool.write().unwrap(); - for id in intent_ids { - let id = IntentId(id); - tracing::info!("Removing matched intent ID {}", id); - w_mempool.remove(&id); - } - } - MsgFromClient::Unmatched { id } => { - let id = IntentId(id); - tracing::info!("No match found for intent ID {}", id); - } - }) - .await - } -} - -impl IntentGossiper { - // Apply the logic to a new intent. It only tries to apply the matchmaker if - // this one exists. If no matchmaker then returns true. - pub async fn add_intent(&mut self, intent: Intent) { - let id = intent.id(); - - let r_mempool = self.mempool.read().unwrap(); - let is_known = r_mempool.contains(&id); - drop(r_mempool); - if !is_known { - let mut w_mempool = self.mempool.write().unwrap(); - w_mempool.insert(intent.clone()); - } - - tracing::info!( - "Sending intent ID {} to connected matchmakers, if any", - id - ); - self.dialer.send(MsgFromServer::AddIntent { - id: id.0, - data: intent.data, - }) - } -} diff --git a/apps/src/lib/node/gossip/mempool.rs b/apps/src/lib/node/gossip/mempool.rs deleted file mode 100644 index fce66447c4..0000000000 --- a/apps/src/lib/node/gossip/mempool.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::collections::HashMap; - -use namada::proto::{Intent, IntentId}; - -/// In-memory intent mempool -#[derive(Clone, Debug, Default)] -pub struct IntentMempool(HashMap); - -impl IntentMempool { - /// Insert a new intent. If the mempool didn't have this intent present, - /// returns `true`. - pub fn insert(&mut self, intent: Intent) -> bool { - self.0.insert(intent.id(), intent).is_none() - } - - /// Remove an intent from mempool. If the mempool didn't have this intent - /// present, returns `true`. in the mempool. - pub fn remove(&mut self, intent_id: &IntentId) -> bool { - self.0.remove(intent_id).is_some() - } - - /// Returns `true` if the map contains intent with specified ID. - pub fn contains(&self, intent_id: &IntentId) -> bool { - self.0.contains_key(intent_id) - } -} diff --git a/apps/src/lib/node/gossip/mod.rs b/apps/src/lib/node/gossip/mod.rs deleted file mode 100644 index 03b14f14ef..0000000000 --- a/apps/src/lib/node/gossip/mod.rs +++ /dev/null @@ -1,116 +0,0 @@ -pub mod intent_gossiper; -mod mempool; -pub mod p2p; -pub mod rpc; - -use std::path::Path; - -use namada::proto::Intent; -use thiserror::Error; -use tokio::sync::mpsc; - -use self::intent_gossiper::IntentGossiper; -use self::p2p::P2P; -use crate::config; -use crate::proto::services::{rpc_message, RpcResponse}; - -#[derive(Error, Debug)] -pub enum Error { - #[error("Error initializing p2p: {0}")] - P2pInit(p2p::Error), -} - -type Result = std::result::Result; - -/// RPC async receiver end of the channel -pub type RpcReceiver = tokio::sync::mpsc::Receiver<( - rpc_message::Message, - tokio::sync::oneshot::Sender, -)>; - -#[tokio::main] -pub async fn run( - config: config::IntentGossiper, - base_dir: impl AsRef, -) -> Result<()> { - // Prepare matchmakers server and dialer - let (matchmakers_server, intent_gossiper) = - intent_gossiper::MatchmakersServer::new_pair( - &config.matchmakers_server_addr, - ); - - // Async channel for intents received from peer - let (peer_intent_send, peer_intent_recv) = tokio::sync::mpsc::channel(100); - - // Create the P2P gossip network, which can send messages directly to the - // matchmaker, if any - let p2p = p2p::P2P::new(&config, base_dir, peer_intent_send) - .await - .map_err(Error::P2pInit)?; - - // Run the matchmakers server - let mms_join_handle = tokio::task::spawn(async move { - matchmakers_server.listen().await; - }); - - // Start the RPC server, if enabled in the config - let rpc_receiver = config.rpc.map(|rpc_config| { - let (rpc_sender, rpc_receiver) = mpsc::channel(100); - tokio::spawn(async move { - rpc::client::start_rpc_server(&rpc_config, rpc_sender).await - }); - rpc_receiver - }); - - dispatcher( - p2p, - rpc_receiver, - peer_intent_recv, - intent_gossiper, - mms_join_handle, - ) - .await -} - -// loop over all possible event. The event can be from the rpc, a matchmaker -// program or the gossip network. The gossip network event are a special case -// that does not need to be handle as it's taking care of by the libp2p internal -// logic. -pub async fn dispatcher( - mut p2p: P2P, - mut rpc_receiver: Option, - mut peer_intent_recv: tokio::sync::mpsc::Receiver, - mut intent_gossiper: IntentGossiper, - _mms_join_handle: tokio::task::JoinHandle<()>, -) -> Result<()> { - loop { - tokio::select! { - Some((event, inject_response)) = recv_rpc_option(rpc_receiver.as_mut()), if rpc_receiver.is_some() => - { - let gossip_sub = &mut p2p.0.behaviour_mut().intent_gossip_behaviour; - let (response, maybe_intent) = rpc::client::handle_rpc_event(event, gossip_sub).await; - inject_response.send(response).expect("failed to send response to rpc server"); - - if let Some(intent) = maybe_intent { - intent_gossiper.add_intent(intent).await; - } - }, - Some(intent) = peer_intent_recv.recv() => { - intent_gossiper.add_intent(intent).await; - } - swarm_event = p2p.0.next() => { - // Never occurs, but call for the event must exists. - tracing::info!("event, {:?}", swarm_event); - }, - }; - } -} - -async fn recv_rpc_option( - x: Option<&mut RpcReceiver>, -) -> Option<( - rpc_message::Message, - tokio::sync::oneshot::Sender, -)> { - x?.recv().await -} diff --git a/apps/src/lib/node/gossip/p2p/behaviour/discovery.rs b/apps/src/lib/node/gossip/p2p/behaviour/discovery.rs deleted file mode 100644 index 57d99c7bc0..0000000000 --- a/apps/src/lib/node/gossip/p2p/behaviour/discovery.rs +++ /dev/null @@ -1,517 +0,0 @@ -// This file is almost identical to this -// https://github.com/webb-tools/anonima/blob/main/network/src/discovery.rs -// appropriate affiliation needs to be added here original header : -// -// Copyright 2020 ChainSafe Systems SPDX-License-Identifier: Apache-2.0, MIT - -use std::collections::{HashSet, VecDeque}; -use std::fmt::Display; -use std::task::{Context, Poll}; -use std::time::Duration; -use std::{cmp, io}; - -use async_std::stream::{self, Interval}; -use futures::StreamExt; -use libp2p::core::connection::{ConnectionId, ListenerId}; -use libp2p::core::ConnectedPoint; -use libp2p::kad::handler::KademliaHandlerProto; -use libp2p::kad::store::MemoryStore; -use libp2p::kad::{Kademlia, KademliaConfig, KademliaEvent, QueryId}; -use libp2p::mdns::{Mdns, MdnsConfig, MdnsEvent}; -use libp2p::swarm::toggle::{Toggle, ToggleIntoProtoHandler}; -use libp2p::swarm::{ - IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, - PollParameters, ProtocolsHandler, -}; -use libp2p::{Multiaddr, PeerId}; -use thiserror::Error; - -use crate::config::PeerAddress; - -#[derive(Error, Debug)] -pub enum Error { - // TODO, it seems that NoKnownPeer is not exposed, could not find it - #[error("Failed to bootstrap kademlia {0}")] - FailedBootstrap(String), - #[error("Failed to initialize mdns {0}")] - FailedMdns(std::io::Error), -} - -pub type Result = std::result::Result; - -/// Event generated by the `DiscoveryBehaviour`. -#[derive(Debug)] -pub enum DiscoveryEvent { - /// Event that notifies that we connected to the node with the given peer - /// id. - Connected(PeerId), - - /// Event that notifies that we disconnected with the node with the given - /// peer id. - Disconnected(PeerId), - - /// This case is only use to clean the code in the poll fct - KademliaEvent(KademliaEvent), -} - -/// `DiscoveryBehaviour` configuration. -#[derive(Clone)] -pub struct DiscoveryConfig { - /// user defined peer that are given to kad in order to connect to the - /// network - user_defined: Vec, - /// maximum number of peer to connect to - discovery_max: u64, - /// enable kademlia to find new peer - enable_kademlia: bool, - /// look for new peer over local network. - // TODO: it seems that kademlia must activated where it should not be - // mandatory - enable_mdns: bool, - // TODO: should this be optional? if not explain why - /// use the option from kademlia. Prevent some type of attacks against - /// kademlia. - kademlia_disjoint_query_paths: bool, -} - -impl Default for DiscoveryConfig { - fn default() -> Self { - Self { - user_defined: Vec::new(), - discovery_max: u64::MAX, - enable_kademlia: true, - enable_mdns: true, - kademlia_disjoint_query_paths: true, - } - } -} - -#[derive(Default)] -pub struct DiscoveryConfigBuilder { - config: DiscoveryConfig, -} - -impl DiscoveryConfigBuilder { - /// Set the number of active connections at which we pause discovery. - pub fn discovery_limit(&mut self, limit: u64) -> &mut Self { - self.config.discovery_max = limit; - self - } - - /// Set custom nodes which never expire, e.g. bootstrap or reserved nodes. - pub fn with_user_defined(&mut self, user_defined: I) -> &mut Self - where - I: IntoIterator, - { - self.config.user_defined.extend(user_defined); - self - } - - /// Configures if disjoint query path is enabled - pub fn use_kademlia_disjoint_query_paths( - &mut self, - value: bool, - ) -> &mut Self { - self.config.kademlia_disjoint_query_paths = value; - self - } - - /// Configures if mdns is enabled. - pub fn with_mdns(&mut self, value: bool) -> &mut Self { - self.config.enable_mdns = value; - self - } - - /// Configures if Kademlia is enabled. - pub fn with_kademlia(&mut self, value: bool) -> &mut Self { - self.config.enable_kademlia = value; - self - } - - /// Build the discovery config - pub fn build(&self) -> Result { - Ok(self.config.clone()) - } -} - -/// Implementation of `NetworkBehaviour` that discovers the nodes on the -/// network. -pub struct DiscoveryBehaviour { - /// User-defined list of nodes and their addresses. Typically includes - /// bootstrap nodes and reserved nodes. - user_defined: Vec, - /// Kademlia discovery. - pub kademlia: Toggle>, - /// Discovers nodes on the local network. - mdns: Toggle, - /// Stream that fires when we need to perform the next random Kademlia - /// query. - next_kad_random_query: Option, - /// After `next_kad_random_query` triggers, the next one triggers after - /// this duration. - duration_to_next_kad: Duration, - /// Events to return in priority when polled. - pending_events: VecDeque, - /// Number of nodes we're currently connected to. - num_connections: u64, - /// Keeps hash set of peers connected. - peers: HashSet, - /// Number of active connections to pause discovery on. - discovery_max: u64, -} - -impl Display for DiscoveryBehaviour { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str( - format!( - "{{ - user_defined {:?}, - kademlia: {:?}, - mdns: {:?}, - next_kad_random_query: {:?}, - duration_to_next_kad {:?}, - num_connection: {:?}, - peers: {:?}, - discovery_max: {:?} -}}", - self.user_defined, - self.kademlia.is_enabled(), - self.mdns.is_enabled(), - self.next_kad_random_query, - self.duration_to_next_kad, - self.num_connections, - self.peers, - self.discovery_max - ) - .as_str(), - ) - } -} - -impl DiscoveryBehaviour { - /// Create a `DiscoveryBehaviour` from a config. - pub async fn new( - local_peer_id: PeerId, - config: DiscoveryConfig, - ) -> Result { - let DiscoveryConfig { - user_defined, - discovery_max, - enable_kademlia, - enable_mdns, - kademlia_disjoint_query_paths, - } = config; - - let mut peers = HashSet::with_capacity(user_defined.len()); - - // Kademlia config - let kademlia_opt = if enable_kademlia { - let store = MemoryStore::new(local_peer_id.to_owned()); - let mut kad_config = KademliaConfig::default(); - kad_config.disjoint_query_paths(kademlia_disjoint_query_paths); - // TODO: choose a better protocol name - kad_config.set_protocol_name( - "/anoma/kad/anoma/kad/1.0.0".as_bytes().to_vec(), - ); - - let mut kademlia = - Kademlia::with_config(local_peer_id, store, kad_config); - - user_defined - .iter() - .for_each(|PeerAddress { address, peer_id }| { - kademlia.add_address(peer_id, address.clone()); - peers.insert(*peer_id); - }); - - // TODO: For production should node fail when kad failed to - // bootstrap? - if let Err(err) = kademlia.bootstrap() { - tracing::error!("failed to bootstrap kad : {:?}", err); - }; - Some(kademlia) - } else { - None - }; - - let mdns_opt = if enable_mdns { - Some( - Mdns::new(MdnsConfig::default()) - .await - .map_err(Error::FailedMdns)?, - ) - } else { - None - }; - - Ok(DiscoveryBehaviour { - user_defined, - kademlia: kademlia_opt.into(), - mdns: mdns_opt.into(), - next_kad_random_query: None, - duration_to_next_kad: Duration::from_secs(1), - pending_events: VecDeque::new(), - num_connections: 0, - peers, - discovery_max, - }) - } -} - -// Most function here are a wrapper around kad behaviour, -impl NetworkBehaviour for DiscoveryBehaviour { - type OutEvent = DiscoveryEvent; - type ProtocolsHandler = - ToggleIntoProtoHandler>; - - fn new_handler(&mut self) -> Self::ProtocolsHandler { - self.kademlia.new_handler() - } - - /// Look for the address of a peer first in the user defined list then in - /// kademlia then lastly in the local network. Sum all possible address and - /// returns. - fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec { - let mut list = self - .user_defined - .iter() - .filter_map(|peer_address| { - if &peer_address.peer_id == peer_id { - Some(peer_address.address.clone()) - } else { - None - } - }) - .collect::>(); - - list.extend(self.kademlia.addresses_of_peer(peer_id)); - - list.extend(self.mdns.addresses_of_peer(peer_id)); - - list - } - - fn inject_connected(&mut self, peer_id: &PeerId) { - tracing::debug!("Injecting connected peer {}", peer_id); - self.peers.insert(*peer_id); - self.pending_events - .push_back(DiscoveryEvent::Connected(*peer_id)); - - self.kademlia.inject_connected(peer_id) - } - - fn inject_disconnected(&mut self, peer_id: &PeerId) { - tracing::debug!("Injecting disconnected peer {}", peer_id); - self.peers.remove(peer_id); - self.pending_events - .push_back(DiscoveryEvent::Disconnected(*peer_id)); - - self.kademlia.inject_disconnected(peer_id) - } - - fn inject_connection_established( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - ) { - tracing::debug!( - "Injecting connection established for peer ID {} with endpoint \ - {:#?}", - peer_id, - endpoint - ); - self.num_connections += 1; - - self.kademlia - .inject_connection_established(peer_id, conn, endpoint) - } - - fn inject_connection_closed( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - ) { - tracing::debug!("Injecting connection closed for peer ID {}", peer_id); - self.num_connections -= 1; - - self.kademlia - .inject_connection_closed(peer_id, conn, endpoint) - } - - fn inject_address_change( - &mut self, - peer: &PeerId, - id: &ConnectionId, - old: &ConnectedPoint, - new: &ConnectedPoint, - ) { - self.kademlia.inject_address_change(peer, id, old, new) - } - - fn inject_event( - &mut self, - peer_id: PeerId, - connection: ConnectionId, - event: <::Handler as ProtocolsHandler>::OutEvent, - ) { - self.kademlia.inject_event(peer_id, connection, event) - } - - fn inject_addr_reach_failure( - &mut self, - peer_id: Option<&PeerId>, - addr: &Multiaddr, - error: &dyn std::error::Error, - ) { - self.kademlia - .inject_addr_reach_failure(peer_id, addr, error) - } - - fn inject_dial_failure(&mut self, peer_id: &PeerId) { - self.kademlia.inject_dial_failure(peer_id) - } - - fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.kademlia.inject_new_listen_addr(id, addr) - } - - fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.kademlia.inject_expired_listen_addr(id, addr); - } - - fn inject_listener_error( - &mut self, - id: ListenerId, - err: &(dyn std::error::Error + 'static), - ) { - self.kademlia.inject_listener_error(id, err) - } - - fn inject_listener_closed( - &mut self, - id: ListenerId, - reason: std::result::Result<(), &io::Error>, - ) { - self.kademlia.inject_listener_closed(id, reason) - } - - fn inject_new_external_addr(&mut self, addr: &Multiaddr) { - self.kademlia.inject_new_external_addr(addr) - } - - // This poll function is called by libp2p to fetch/generate new event. First - // in the local queue then in kademlia and lastly in Mdns. - #[allow(clippy::type_complexity)] - fn poll( - &mut self, - cx: &mut Context, - params: &mut impl PollParameters, - ) -> Poll< - NetworkBehaviourAction< - <::Handler as ProtocolsHandler>::InEvent, - Self::OutEvent, - >, - >{ - // Immediately process the content of `discovered`. - if let Some(ev) = self.pending_events.pop_front() { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)); - } - - // Poll Kademlia return every other event except kad event - while let Poll::Ready(ev) = self.kademlia.poll(cx, params) { - tracing::debug!("Kademlia event {:#?}", ev); - if let NetworkBehaviourAction::GenerateEvent(_kad_ev) = ev { - } else { - return Poll::Ready(ev.map_out(DiscoveryEvent::KademliaEvent)); - } - } - - // Poll the stream that fires when we need to start a random Kademlia - // query. When the stream provides a new value then it tries to look for - // a node and connect to it. - // TODO: explain a bit more the logic happening here - if let Some(next_kad_random_query) = self.next_kad_random_query.as_mut() - { - tracing::debug!( - "Kademlia random query {:#?}", - next_kad_random_query - ); - while next_kad_random_query.poll_next_unpin(cx).is_ready() { - if self.num_connections < self.discovery_max { - let random_peer_id = PeerId::random(); - tracing::debug!( - "Libp2p <= Starting random Kademlia request for {:?}", - random_peer_id - ); - if let Some(k) = self.kademlia.as_mut() { - k.get_closest_peers(random_peer_id); - } - } - - *next_kad_random_query = - stream::interval(self.duration_to_next_kad); - self.duration_to_next_kad = cmp::min( - self.duration_to_next_kad * 2, - Duration::from_secs(60), - ); - } - } - - // Poll mdns. If mdns generated new Discovered event then connect to it - // TODO: refactor this function, it can't be done as the kad done - while let Poll::Ready(ev) = self.mdns.poll(cx, params) { - match ev { - NetworkBehaviourAction::GenerateEvent(event) => match event { - MdnsEvent::Discovered(list) => { - if self.num_connections < self.discovery_max { - // Add any discovered peers to Kademlia - for (peer_id, multiaddr) in list { - if let Some(kad) = self.kademlia.as_mut() { - kad.add_address(&peer_id, multiaddr); - } - } - } else { - tracing::info!( - "max reached {:?}, {:?}", - self.num_connections, - self.discovery_max - ); - // Already over discovery max, don't add discovered - // peers. We could potentially buffer these - // addresses to be added later, but mdns is not an - // important use case and may be removed in future. - } - } - MdnsEvent::Expired(_) => {} - }, - NetworkBehaviourAction::DialAddress { address } => { - return Poll::Ready(NetworkBehaviourAction::DialAddress { - address, - }); - } - NetworkBehaviourAction::DialPeer { peer_id, condition } => { - return Poll::Ready(NetworkBehaviourAction::DialPeer { - peer_id, - condition, - }); - } - // Nothing to notify handler - NetworkBehaviourAction::NotifyHandler { .. } => {} - NetworkBehaviourAction::ReportObservedAddr { - address, - score, - } => { - return Poll::Ready( - NetworkBehaviourAction::ReportObservedAddr { - address, - score, - }, - ); - } - } - } - Poll::Pending - } -} diff --git a/apps/src/lib/node/gossip/p2p/behaviour/mod.rs b/apps/src/lib/node/gossip/p2p/behaviour/mod.rs deleted file mode 100644 index f70a300b9e..0000000000 --- a/apps/src/lib/node/gossip/p2p/behaviour/mod.rs +++ /dev/null @@ -1,412 +0,0 @@ -mod discovery; -use std::collections::hash_map::DefaultHasher; -use std::convert::TryFrom; -use std::hash::{Hash, Hasher}; -use std::time::Duration; - -use libp2p::gossipsub::subscription_filter::regex::RegexSubscriptionFilter; -use libp2p::gossipsub::subscription_filter::{ - TopicSubscriptionFilter, WhitelistSubscriptionFilter, -}; -use libp2p::gossipsub::{ - self, GossipsubEvent, GossipsubMessage, IdentTopic, IdentityTransform, - MessageAcceptance, MessageAuthenticity, MessageId, TopicHash, - ValidationMode, -}; -use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent}; -use libp2p::identity::Keypair; -use libp2p::ping::{Ping, PingEvent, PingFailure, PingSuccess}; -use libp2p::swarm::NetworkBehaviourEventProcess; -use libp2p::{NetworkBehaviour, PeerId}; -use namada::proto::{self, Intent, IntentGossipMessage}; -use thiserror::Error; -use tokio::sync::mpsc::Sender; - -use self::discovery::DiscoveryEvent; -use crate::config; -use crate::node::gossip::p2p::behaviour::discovery::{ - DiscoveryBehaviour, DiscoveryConfigBuilder, -}; - -/// Behaviour is composed of a `DiscoveryBehaviour` and an GossipsubBehaviour`. -/// It automatically connect to newly discovered peer, except specified -/// otherwise, and propagates intents to other peers. -#[derive(NetworkBehaviour)] -pub struct Behaviour { - pub intent_gossip_behaviour: Gossipsub, - pub discover_behaviour: DiscoveryBehaviour, - /// The identify protocol allows establishing P2P connections via Kademlia - identify: Identify, - /// Responds to inbound pings and periodically sends outbound pings on - /// every established connection - ping: Ping, - #[behaviour(ignore)] - pub peer_intent_send: Sender, -} - -#[derive(Error, Debug)] -pub enum Error { - #[error("Failed to subscribe")] - FailedSubscription(libp2p::gossipsub::error::SubscriptionError), - #[error("Failed initializing the topic filter: {0}")] - Filter(String), - #[error("Failed initializing the gossip behaviour: {0}")] - GossipConfig(String), - #[error("Failed on the the discovery behaviour config: {0}")] - DiscoveryConfig(String), - #[error("Failed initializing the discovery behaviour: {0}")] - Discovery(discovery::Error), - #[error("Failed initializing mdns: {0}")] - Mdns(std::io::Error), -} - -pub type Gossipsub = libp2p::gossipsub::Gossipsub< - IdentityTransform, - IntentGossipSubscriptionFilter, ->; - -// TODO merge type of config and this one ? Maybe not a good idea -// TODO extends with MaxSubscribionFilter -/// IntentGossipSubscriptionfilter is a wrapper of TopicSubscriptionFilter to -/// allows combination of any sort of filter. -pub enum IntentGossipSubscriptionFilter { - RegexFilter(RegexSubscriptionFilter), - WhitelistFilter(WhitelistSubscriptionFilter), -} - -/// IntentGossipEvent describe events received/sent in the gossipsub network. -/// All information are extracted from the GossipsubEvent type. This type is -/// used as a wrapper of GossipsubEvent in order to have only information of -/// interest and possibly enforce some invariant. -#[derive(Debug)] -pub struct IntentGossipEvent { - /// The PeerId that initially created this message - pub propagation_source: PeerId, - /// The MessageId of this message. This MessageId allows to discriminate - /// already received message - pub message_id: MessageId, - // TODO maybe remove the Option of this field to make mandatory to have an - // id. - /// The peer that transmitted this message to us. It can be anonymous - pub source: Option, - /// The content of the data - pub data: Vec, - /// The topic from which we received the message - pub topic: TopicHash, -} - -impl From for IntentGossipEvent { - /// Transforme a GossipsubEvent into an IntentGossipEvent. This function - /// fails if the gossipsubEvent does not contain a GossipsubMessage. - fn from(event: GossipsubEvent) -> Self { - if let GossipsubEvent::Message { - propagation_source, - message_id, - message: - GossipsubMessage { - source, - data, - topic, - sequence_number: _, - }, - } = event - { - Self { - propagation_source, - message_id, - source, - data, - topic, - } - } else { - panic!("Expected a GossipsubEvent::Message got {:?}", event) - } - } -} - -impl TopicSubscriptionFilter for IntentGossipSubscriptionFilter { - /// tcheck that the proposed topic can be subscribed - fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool { - match self { - IntentGossipSubscriptionFilter::RegexFilter(filter) => { - filter.can_subscribe(topic_hash) - } - IntentGossipSubscriptionFilter::WhitelistFilter(filter) => { - filter.can_subscribe(topic_hash) - } - } - } -} - -/// [message_id] use the hash of the message data as an id -pub fn message_id(message: &GossipsubMessage) -> MessageId { - let mut hasher = DefaultHasher::new(); - message.data.hash(&mut hasher); - MessageId::from(hasher.finish().to_string()) -} - -impl Behaviour { - /// Create a new behaviour based on the config given - pub async fn new( - key: Keypair, - config: &config::IntentGossiper, - peer_intent_send: Sender, - ) -> Self { - let public_key = key.public(); - let peer_id = PeerId::from_public_key(public_key.clone()); - - // TODO remove hardcoded value and add them to the config Except - // validation_mode, protocol_id_prefix, message_id_fn and - // validate_messages - // Set a custom gossipsub for our use case - let gossipsub_config = gossipsub::GossipsubConfigBuilder::default() - .protocol_id_prefix("intent_gossip") - .heartbeat_interval(Duration::from_secs(1)) - .validation_mode(ValidationMode::Strict) - .message_id_fn(message_id) - .max_transmit_size(16 * 1024 * 1024) - .validate_messages() - .mesh_outbound_min(1) - // TODO bootstrap peers should not be part of the mesh, so all the - // `.mesh` args should be set to 0 https://github.com/libp2p/specs/blob/70d7fda47dda88d828b4db72775c1602de57e91b/pubsub/gossipsub/gossipsub-v1.1.md#recommendations-for-network-operators - .mesh_n_low(2) - .mesh_n(3) - .mesh_n_high(6) - .build() - .unwrap(); - - let filter = match &config.subscription_filter { - crate::config::SubscriptionFilter::RegexFilter(regex) => { - IntentGossipSubscriptionFilter::RegexFilter( - RegexSubscriptionFilter(regex.clone()), - ) - } - crate::config::SubscriptionFilter::WhitelistFilter(topics) => { - IntentGossipSubscriptionFilter::WhitelistFilter( - WhitelistSubscriptionFilter( - topics - .iter() - .map(IdentTopic::new) - .map(TopicHash::from) - .collect(), - ), - ) - } - }; - - let mut intent_gossip_behaviour: Gossipsub = - Gossipsub::new_with_subscription_filter( - MessageAuthenticity::Signed(key), - gossipsub_config, - filter, - ) - .unwrap(); - - // subscribe to all topic listed in the config. - config - .topics - .iter() - .try_for_each(|topic| { - intent_gossip_behaviour - .subscribe(&IdentTopic::new(topic)) - .map_err(Error::FailedSubscription) - // it returns bool signifying if it was already subscribed. - // discard because it can't be false as the config.topics is - // a hash set - .map(|_| ()) - }) - .expect("failed to subscribe to topic"); - - let discover_behaviour = { - // TODO: check that bootstrap_peers are in multiaddr (otherwise it - // fails silently) - let discover_config = - if let Some(discover_config) = &config.discover_peer { - DiscoveryConfigBuilder::default() - .with_user_defined(config.seed_peers.clone()) - .discovery_limit(discover_config.max_discovery_peers) - .with_kademlia(discover_config.kademlia) - .with_mdns(discover_config.mdns) - .use_kademlia_disjoint_query_paths(true) - .build() - .unwrap() - } else { - DiscoveryConfigBuilder::default().build().unwrap() - }; - DiscoveryBehaviour::new(peer_id, discover_config) - .await - .unwrap() - }; - Self { - intent_gossip_behaviour, - discover_behaviour, - identify: Identify::new(IdentifyConfig::new( - "anoma/id/anoma/id/1.0.0".into(), - public_key, - )), - ping: Ping::default(), - peer_intent_send, - } - } - - /// tries to apply a new intent. Fails if the logic fails or if the intent - /// is rejected. If the matchmaker fails the message is only ignore - fn handle_intent(&mut self, intent: Intent) -> MessageAcceptance { - if let Err(err) = self.peer_intent_send.try_send(intent) { - tracing::error!("Error sending intent to the matchmaker: {}", err); - // The buffer is full or the channel is closed - return MessageAcceptance::Ignore; - } - MessageAcceptance::Accept - } - - /// Tries to decoded the arbitrary data in an intent then call - /// [Self::handle_intent]. fails if the data does not contains an intent - fn handle_raw_intent( - &mut self, - data: impl AsRef<[u8]>, - ) -> MessageAcceptance { - match IntentGossipMessage::try_from(data.as_ref()) { - Ok(message) => self.handle_intent(message.intent), - Err(proto::Error::NoIntentError) => { - tracing::info!("Empty message, rejecting it"); - MessageAcceptance::Reject - } - Err(proto::Error::IntentDecodingError(err)) => { - tracing::info!("error while decoding the intent: {:?}", err); - MessageAcceptance::Reject - } - _ => unreachable!(), - } - } -} - -impl NetworkBehaviourEventProcess for Behaviour { - /// When a new event is generated by the intent gossip behaviour - fn inject_event(&mut self, event: GossipsubEvent) { - tracing::info!("received a new message : {:?}", event); - match event { - GossipsubEvent::Message { - message, - propagation_source, - message_id, - } => { - // validity is the type of response return to the network - // (valid|reject|ignore) - let validity = self.handle_raw_intent(message.data); - self.intent_gossip_behaviour - .report_message_validation_result( - &message_id, - &propagation_source, - validity, - ) - .expect("Failed to validate the message"); - } - // When a peer subscribe to a new topic, this node also tries to - // connect to it using the filter defined in the config - GossipsubEvent::Subscribed { peer_id: _, topic } => { - // try to subscribe to the new topic - self.intent_gossip_behaviour - .subscribe(&IdentTopic::new(topic.into_string())) - .map_err(Error::FailedSubscription) - .unwrap_or_else(|e| { - tracing::error!("failed to subscribe: {:?}", e); - false - }); - } - // Nothing to do when you are informed that a peer unsubscribed to a - // topic. - // TODO: It could be interesting to unsubscribe to a topic when the - // node is not connected to anyone else. - GossipsubEvent::Unsubscribed { - peer_id: _, - topic: _, - } => {} - } - } -} - -impl NetworkBehaviourEventProcess for Behaviour { - // The logic is part of the DiscoveryBehaviour, nothing to do here. - fn inject_event(&mut self, event: DiscoveryEvent) { - match event { - DiscoveryEvent::Connected(peer) => { - tracing::info!("Connect to a new peer: {:?}", peer) - } - DiscoveryEvent::Disconnected(peer) => { - tracing::info!("Peer disconnected: {:?}", peer) - } - _ => {} - } - } -} - -impl NetworkBehaviourEventProcess for Behaviour { - fn inject_event(&mut self, event: IdentifyEvent) { - match event { - IdentifyEvent::Received { peer_id, info } => { - tracing::info!("Identified Peer {}", peer_id); - tracing::debug!("protocol_version {}", info.protocol_version); - tracing::debug!("agent_version {}", info.agent_version); - tracing::debug!("listening_addresses {:?}", info.listen_addrs); - tracing::debug!("observed_address {}", info.observed_addr); - tracing::debug!("protocols {:?}", info.protocols); - if let Some(kad) = self.discover_behaviour.kademlia.as_mut() { - // Only the first address is the public IP, the others - // seem to be private - if let Some(addr) = info.listen_addrs.first() { - tracing::info!( - "Routing updated peer ID: {}, address: {}", - peer_id, - addr - ); - let _update = kad.add_address(&peer_id, addr.clone()); - } - } - } - IdentifyEvent::Sent { .. } => (), - IdentifyEvent::Pushed { .. } => (), - IdentifyEvent::Error { peer_id, error } => { - tracing::error!( - "Error while attempting to identify the remote peer {}: \ - {},", - peer_id, - error - ); - } - } - } -} - -impl NetworkBehaviourEventProcess for Behaviour { - fn inject_event(&mut self, event: PingEvent) { - match event.result { - Ok(PingSuccess::Ping { rtt }) => { - tracing::debug!( - "PingSuccess::Ping rtt to {} is {} ms", - event.peer.to_base58(), - rtt.as_millis() - ); - } - Ok(PingSuccess::Pong) => { - tracing::debug!( - "PingSuccess::Pong from {}", - event.peer.to_base58() - ); - } - Err(PingFailure::Timeout) => { - tracing::warn!( - "PingFailure::Timeout {}", - event.peer.to_base58() - ); - } - Err(PingFailure::Other { error }) => { - tracing::warn!( - "PingFailure::Other {}: {}", - event.peer.to_base58(), - error - ); - } - } - } -} diff --git a/apps/src/lib/node/gossip/p2p/identity.rs b/apps/src/lib/node/gossip/p2p/identity.rs deleted file mode 100644 index 42442054c8..0000000000 --- a/apps/src/lib/node/gossip/p2p/identity.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::fs::OpenOptions; -use std::path::{Path, PathBuf}; - -use libp2p::identity::ed25519::Keypair; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; - -use crate::cli; - -const P2P_KEY_PATH: &str = "gossiper-p2p-private-key.json"; - -/// ed255519 keypair + hash of public key. The keypair used to encrypted the -/// data send in the libp2p network. -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Identity { - pub address: String, - #[serde(with = "keypair_serde")] - pub key: Keypair, -} - -// TODO this is needed because libp2p does not export ed255519 serde -// feature maybe a MR for libp2p to export theses functions ? -mod keypair_serde { - use libp2p::identity::ed25519::Keypair; - use serde::de::Error; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - - pub fn serialize( - value: &Keypair, - serializer: S, - ) -> Result - where - S: Serializer, - { - let bytes = value.encode(); - let string = hex::encode(&bytes[..]); - string.serialize(serializer) - } - pub fn deserialize<'d, D>(deserializer: D) -> Result - where - D: Deserializer<'d>, - { - let string = String::deserialize(deserializer)?; - let mut bytes = hex::decode(&string).map_err(Error::custom)?; - Keypair::decode(bytes.as_mut()).map_err(Error::custom) - } -} - -impl Identity { - /// Generates a new gossiper keypair and hash. - pub fn new() -> Self { - let key = Keypair::generate(); - let mut hasher = Sha256::new(); - hasher.update(key.public().encode()); - let address = format!("{:.40X}", hasher.finalize()); - Identity { address, key } - } - - /// Load identity from file or generate a new one if none found. - pub fn load_or_gen(base_dir: impl AsRef) -> Identity { - let file_path = Self::file_path(&base_dir); - match OpenOptions::new().read(true).open(&file_path) { - Ok(file) => { - let gossiper: Identity = serde_json::from_reader(file) - .expect("unexpected key encoding"); - gossiper - } - Err(err) => { - if let std::io::ErrorKind::NotFound = err.kind() { - tracing::info!( - "No P2P key found, generating a new one. This will be \ - written into {}", - file_path.to_string_lossy() - ); - Self::gen(base_dir) - } else { - eprintln!( - "Cannot read {}: {}", - file_path.to_string_lossy(), - err - ); - cli::safe_exit(1); - } - } - } - } - - /// Generate a new identity. - pub fn gen(base_dir: impl AsRef) -> Identity { - let file_path = Self::file_path(base_dir); - std::fs::create_dir_all(&file_path.parent().unwrap()).unwrap(); - let file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&file_path) - .expect("Couldn't open P2P key file"); - let gossiper = Identity::new(); - serde_json::to_writer_pretty(file, &gossiper) - .expect("Couldn't write private validator key file"); - gossiper - } - - pub fn file_path(base_dir: impl AsRef) -> PathBuf { - base_dir.as_ref().join(P2P_KEY_PATH) - } - - pub fn peer_id(&self) -> libp2p::PeerId { - let pk = self.key.public(); - let pk = libp2p::identity::PublicKey::Ed25519(pk); - libp2p::PeerId::from(pk) - } - - pub fn key(&self) -> libp2p::identity::Keypair { - libp2p::identity::Keypair::Ed25519(self.key.clone()) - } -} - -impl Default for Identity { - fn default() -> Self { - Self::new() - } -} diff --git a/apps/src/lib/node/gossip/p2p/mod.rs b/apps/src/lib/node/gossip/p2p/mod.rs deleted file mode 100644 index 2c135bc995..0000000000 --- a/apps/src/lib/node/gossip/p2p/mod.rs +++ /dev/null @@ -1,139 +0,0 @@ -pub mod behaviour; -mod identity; - -use std::path::Path; -use std::time::Duration; - -use behaviour::Behaviour; -use libp2p::core::connection::ConnectionLimits; -use libp2p::core::muxing::StreamMuxerBox; -use libp2p::core::transport::Boxed; -use libp2p::dns::DnsConfig; -use libp2p::identity::Keypair; -use libp2p::swarm::SwarmBuilder; -use libp2p::tcp::TcpConfig; -use libp2p::websocket::WsConfig; -use libp2p::{core, mplex, noise, PeerId, Transport, TransportError}; -use namada::proto::Intent; -use thiserror::Error; -use tokio::sync::mpsc::Sender; - -pub use self::identity::Identity; -use crate::config; - -pub type Swarm = libp2p::Swarm; - -#[derive(Error, Debug)] -pub enum Error { - #[error("Failed initializing the transport: {0}")] - Transport(std::io::Error), - #[error("Error with the network behavior: {0}")] - Behavior(crate::node::gossip::p2p::behaviour::Error), - #[error("Error while dialing: {0}")] - Dialing(libp2p::swarm::DialError), - #[error("Error while starting to listing: {0}")] - Listening(TransportError), - #[error("Error decoding peer identity")] - BadPeerIdentity(TransportError), -} -type Result = std::result::Result; - -pub struct P2P(pub Swarm); - -impl P2P { - /// Create a new peer based on the configuration given. Used transport is - /// tcp. A peer participate in the intent gossip system and helps the - /// propagation of intents. - pub async fn new( - config: &config::IntentGossiper, - base_dir: impl AsRef, - peer_intent_send: Sender, - ) -> Result { - let identity = Identity::load_or_gen(base_dir); - let peer_key = identity.key(); - // Id of the node on the libp2p network derived from the public key - let peer_id = identity.peer_id(); - - tracing::info!("Peer id: {:?}", peer_id.clone()); - - let transport = build_transport(&peer_key).await; - - // create intent gossip specific behaviour - let intent_gossip_behaviour = - Behaviour::new(peer_key, config, peer_intent_send).await; - - let connection_limits = build_p2p_connections_limit(); - - // Swarm is - let mut swarm = - SwarmBuilder::new(transport, intent_gossip_behaviour, peer_id) - .connection_limits(connection_limits) - .notify_handler_buffer_size( - std::num::NonZeroUsize::new(20).expect("Not zero"), - ) - .connection_event_buffer_size(64) - .build(); - - swarm - .listen_on(config.address.clone()) - .map_err(Error::Listening)?; - - Ok(Self(swarm)) - } -} - -// TODO explain a bit the choice made here -/// Create transport used by libp2p. See -/// for more information on libp2p -/// transport -pub async fn build_transport( - peer_key: &Keypair, -) -> Boxed<(PeerId, StreamMuxerBox)> { - let transport = { - let tcp_transport = TcpConfig::new().nodelay(true); - let dns_tcp_transport = DnsConfig::system(tcp_transport).await.unwrap(); - let ws_dns_tcp_transport = WsConfig::new(dns_tcp_transport.clone()); - dns_tcp_transport.or_transport(ws_dns_tcp_transport) - }; - - let auth_config = { - let dh_keys = noise::Keypair::::new() - .into_authentic(peer_key) - .expect("Noise key generation failed. Should never happen."); - - noise::NoiseConfig::xx(dh_keys).into_authenticated() - }; - - let mplex_config = { - let mut mplex_config = mplex::MplexConfig::new(); - mplex_config.set_max_buffer_behaviour(mplex::MaxBufferBehaviour::Block); - mplex_config.set_max_buffer_size(usize::MAX); - - let mut yamux_config = libp2p::yamux::YamuxConfig::default(); - yamux_config - .set_window_update_mode(libp2p::yamux::WindowUpdateMode::on_read()); - // TODO: check if its enought - yamux_config.set_max_buffer_size(16 * 1024 * 1024); - yamux_config.set_receive_window_size(16 * 1024 * 1024); - - core::upgrade::SelectUpgrade::new(yamux_config, mplex_config) - }; - - transport - .upgrade(core::upgrade::Version::V1) - .authenticate(auth_config) - .multiplex(mplex_config) - .timeout(Duration::from_secs(20)) - .boxed() -} - -// TODO document choice made here -// TODO inject it in the configuration instead of hard-coding it ? -pub fn build_p2p_connections_limit() -> ConnectionLimits { - ConnectionLimits::default() - .with_max_pending_incoming(Some(10)) - .with_max_pending_outgoing(Some(30)) - .with_max_established_incoming(Some(25)) - .with_max_established_outgoing(Some(25)) - .with_max_established_per_peer(Some(5)) -} diff --git a/apps/src/lib/node/gossip/rpc/client.rs b/apps/src/lib/node/gossip/rpc/client.rs deleted file mode 100644 index f33d6add34..0000000000 --- a/apps/src/lib/node/gossip/rpc/client.rs +++ /dev/null @@ -1,166 +0,0 @@ -use std::convert::TryFrom; -use std::net::SocketAddr; - -use libp2p::gossipsub::IdentTopic; -use namada::proto::{Intent, IntentGossipMessage}; -use tokio::sync::mpsc::{self, Sender}; -use tokio::sync::oneshot; -use tonic::transport::Server; -use tonic::{Request as TonicRequest, Response as TonicResponse, Status}; - -use crate::config::RpcServer; -use crate::node::gossip::p2p::behaviour::Gossipsub; -use crate::proto::services::rpc_service_server::{ - RpcService, RpcServiceServer, -}; -use crate::proto::services::{rpc_message, RpcMessage, RpcResponse}; -use crate::proto::{IntentMessage, SubscribeTopicMessage}; - -#[derive(Debug)] -struct Rpc { - inject_message: - mpsc::Sender<(rpc_message::Message, oneshot::Sender)>, -} - -#[tonic::async_trait] -impl RpcService for Rpc { - async fn send_message( - &self, - request: TonicRequest, - ) -> Result, Status> { - if let RpcMessage { message: Some(msg) } = request.into_inner() { - let (sender, receiver) = oneshot::channel(); - self.inject_message - .send((msg, sender)) - .await - .map_err(|err| - Status::cancelled(format!{"failed to send message to gossip app: {:?}",err}) - )? - ; - let response = receiver.await.map_err(|err| - Status::data_loss(format!{"failed to receive response from gossip app: {:?}", err}))?; - Ok(TonicResponse::new(response)) - } else { - tracing::error!("Received empty rpc message, nothing can be done"); - Ok(TonicResponse::new(RpcResponse::default())) - } - } -} - -pub async fn rpc_server( - addr: SocketAddr, - inject_message: Sender<( - rpc_message::Message, - oneshot::Sender, - )>, -) -> Result<(), tonic::transport::Error> { - let rpc = Rpc { inject_message }; - let svc = RpcServiceServer::new(rpc); - Server::builder().add_service(svc).serve(addr).await -} - -/// Start a rpc server in it's own thread. The used address to listen is in the -/// `config` argument. All received event by the rpc are send to the channel -/// return by this function. -pub async fn start_rpc_server( - config: &RpcServer, - rpc_sender: mpsc::Sender<( - rpc_message::Message, - tokio::sync::oneshot::Sender, - )>, -) { - let addr = config.address; - tracing::info!("RPC started at {}", config.address); - rpc_server(addr, rpc_sender).await.unwrap(); -} - -pub async fn handle_rpc_event( - event: rpc_message::Message, - gossip_sub: &mut Gossipsub, -) -> (RpcResponse, Option) { - match event { - rpc_message::Message::Intent(message) => { - match IntentMessage::try_from(message) { - Ok(message) => { - // Send the intent to gossip - let gossip_message = - IntentGossipMessage::new(message.intent.clone()); - let intent_bytes = gossip_message.to_bytes(); - - let gossip_result = match gossip_sub - .publish(IdentTopic::new(message.topic), intent_bytes) - { - Ok(message_id) => { - format!( - "Intent published in intent gossiper with \ - message ID: {}", - message_id - ) - } - Err(err) => { - format!( - "Failed to publish intent in gossiper: {:?}", - err - ) - } - }; - ( - RpcResponse { - result: format!( - "Intent received. {}.", - gossip_result, - ), - }, - Some(message.intent), - ) - } - Err(err) => ( - RpcResponse { - result: format!("Error decoding intent: {:?}", err), - }, - None, - ), - } - } - rpc_message::Message::Dkg(dkg_msg) => { - tracing::debug!("dkg not yet implemented {:?}", dkg_msg); - ( - RpcResponse { - result: String::from( - "DKG application not yet - implemented", - ), - }, - None, - ) - } - rpc_message::Message::Topic(topic_message) => { - let topic = SubscribeTopicMessage::from(topic_message); - let topic = IdentTopic::new(&topic.topic); - ( - match gossip_sub.subscribe(&topic) { - Ok(true) => { - let result = format!("Node subscribed to {}", topic); - tracing::info!("{}", result); - RpcResponse { result } - } - Ok(false) => { - let result = - format!("Node already subscribed to {}", topic); - tracing::info!("{}", result); - RpcResponse { result } - } - Err(err) => { - let result = format!( - "failed to subscribe to {}: {:?}", - topic, err - ); - tracing::error!("{}", result); - RpcResponse { result } - } - }, - None, - ) - } - } -} diff --git a/apps/src/lib/node/gossip/rpc/matchmakers.rs b/apps/src/lib/node/gossip/rpc/matchmakers.rs deleted file mode 100644 index c4912c8b96..0000000000 --- a/apps/src/lib/node/gossip/rpc/matchmakers.rs +++ /dev/null @@ -1,847 +0,0 @@ -//! This module provides connection between an intent gossiper node (the server) -//! and matchmakers (clients) over WebSocket. -//! -//! Both the server and the client can asynchronously listen for new messages -//! and send messages to the other side. - -use std::collections::HashSet; -use std::fmt::Debug; -use std::net::{SocketAddr, ToSocketAddrs}; -use std::sync::atomic::{self, AtomicBool}; -use std::sync::{Arc, RwLock}; - -use borsh::{BorshDeserialize, BorshSerialize}; -use derivative::Derivative; -use message_io::network::{Endpoint, ResourceId, ToRemoteAddr, Transport}; -use message_io::node::{self, NodeHandler, NodeListener}; - -use crate::cli; - -/// Message from intent gossiper to a matchmaker -#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -pub enum MsgFromServer { - /// Try to match an intent - AddIntent { id: Vec, data: Vec }, -} - -/// Message from a matchmaker to intent gossiper -#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -pub enum MsgFromClient { - /// The intent is invalid and hence it shouldn't be gossiped - InvalidIntent { id: Vec }, - /// The intent constraints are too complex for this matchmaker, gossip it - IntentConstraintsTooComplex { id: Vec }, - /// The matchmaker doesn't care about this intent, gossip it - IgnoredIntent { id: Vec }, - /// Intents were matched into a tx. Remove the matched intents from mempool - /// if the tx gets applied. - Matched { intent_ids: HashSet> }, - /// An intent was accepted and added, but no match found yet. Gossip it - Unmatched { id: Vec }, -} - -/// Intent gossiper server listener handles connections from [`ClientDialer`]s. -#[derive(Derivative)] -#[derivative(Debug)] -pub struct ServerListener { - /// The address on which the server is listening - pub address: SocketAddr, - /// The accepted client connections, shared with the [`ServerDialer`] - clients: Arc>>, - /// A node listener and its abort receiver. These are consumed once the - /// listener is started with [`ServerListener::listen`]. - #[derivative(Debug = "ignore")] - listener: Option<(NodeListener<()>, tokio::sync::mpsc::Receiver<()>)>, -} - -/// Intent gossiper server dialer can send messages to the connected -/// [`ClientListener`]s. -#[derive(Clone, Derivative)] -#[derivative(Debug)] -pub struct ServerDialer { - /// The connection handler - #[derivative(Debug = "ignore")] - handler: NodeHandler<()>, - /// Connection resource ID - resource_id: ResourceId, - /// The accepted client connections, shared with the [`ServerListener`] - clients: Arc>>, - /// A message to abort the server must be sent to stop the - /// [`ServerListener`]. This message will be sent on [`ServerDialer`]'s - /// `drop` call. - abort_send: tokio::sync::mpsc::Sender<()>, -} - -/// Server events are used internally by the async [`ServerListener`]. -#[derive(Clone, Debug)] -enum ServerEvent { - /// New endpoint has been accepted by a listener and considered ready to - /// use. The event contains the resource id of the listener that - /// accepted this connection. - Accepted(Endpoint, ResourceId), - /// Input message received by the network. - Message(Endpoint, MsgFromClient), - /// This event is only dispatched when a connection is lost. - Disconnected(Endpoint), -} - -/// Matchmaker client listener handles a connection from [`ServerDialer`]. -#[derive(Derivative)] -#[derivative(Debug)] -pub struct ClientListener { - /// The connection handler - #[derivative(Debug = "ignore")] - handler: NodeHandler<()>, - /// The server connection endpoint - server: Endpoint, - /// The address on which the client is listening - local_addr: SocketAddr, - /// The client listener. This is consumed once the listener is started with - /// [`ClientListener::listen`]. - #[derivative(Debug = "ignore")] - listener: Option>, - /// Server connection status - is_connected: Arc, -} - -/// Matchmaker client dialer can send messages to the connected -/// [`ServerListener`]. -#[derive(Clone, Derivative)] -#[derivative(Debug)] -pub struct ClientDialer { - /// The address on which the client is listening - pub local_addr: SocketAddr, - /// The server address - server: Endpoint, - /// The connection handler - #[derivative(Debug = "ignore")] - handler: NodeHandler<()>, - /// Server connection status - is_connected: Arc, -} - -impl ServerListener { - /// Create a new intent gossiper node server. Returns a listener and - /// a dialer that can be used to send messages to clients and to shut down - /// the server. - pub fn new_pair(address: impl ToSocketAddrs) -> (Self, ServerDialer) { - let clients: Arc>> = Default::default(); - let (handler, listener) = node::split::<()>(); - - let (resource_id, address) = match handler - .network() - .listen(Transport::Ws, &address) - { - Ok((resource_id, real_addr)) => { - tracing::info!("Matchmakers server running at {}", real_addr); - (resource_id, real_addr) - } - Err(err) => { - eprintln!( - "The matchmakers server cannot listen at {:?}: {}", - address.to_socket_addrs().unwrap().collect::>(), - err - ); - cli::safe_exit(1); - } - }; - - let (abort_send, abort_recv) = tokio::sync::mpsc::channel::<()>(1); - - ( - Self { - address, - clients: clients.clone(), - listener: Some((listener, abort_recv)), - }, - ServerDialer { - handler, - clients, - resource_id, - abort_send, - }, - ) - } - - /// Start the server listener and call `on_msg` on every received message. - /// The listener can be stopped early by [`ServerDialer::shutdown`]. - pub async fn listen(mut self, mut on_msg: impl FnMut(MsgFromClient)) { - // Open a channel for events received from the async listener - let (send, mut recv) = tokio::sync::mpsc::unbounded_channel(); - - // This is safe because `listen` consumes `self` created by - // [`ServerListener::new_pair`] - let (listener, mut abort_recv) = self.listener.take().unwrap(); - - tracing::debug!("Starting intent gossiper matchmakers server..."); - - // Start the async listener that will send server events over the - // channel - let _task = listener.for_each_async(move |event| { - match event.network() { - message_io::network::NetEvent::Message( - endpoint, - mut msg_bytes, - ) => match MsgFromClient::deserialize(&mut msg_bytes) { - Ok(msg) => { - let _ = send.send(ServerEvent::Message(endpoint, msg)); - } - Err(err) => { - tracing::error!( - "Couldn't decode a msg from matchmaker {}: {}", - endpoint, - err - ); - } - }, - message_io::network::NetEvent::Accepted(endpoint, id) => { - tracing::info!( - "Accepted connection from matchmaker {}", - endpoint - ); - let _ = send.send(ServerEvent::Accepted(endpoint, id)); - } - message_io::network::NetEvent::Disconnected(endpoint) => { - tracing::info!("Matchmaker disconnected: {}", endpoint); - let _ = send.send(ServerEvent::Disconnected(endpoint)); - } - message_io::network::NetEvent::Connected(endpoint, status) => { - // Server only gets `NetEvent::Accepted` from connected - // clients - tracing::error!( - "Unexpected server `NetEvent::Connected` with \ - endpoint {}, status {}", - endpoint, - status - ); - } - } - }); - - tracing::debug!("Intent gossiper matchmakers server is ready."); - - // Process the server events - loop { - tokio::select! { - _ = abort_recv.recv() => { - tracing::debug!("Shutting down intent gossiper matchmakers server."); - return; - }, - event = recv.recv() => if let Some(event) = event { - match event { - ServerEvent::Message(endpoint, msg) => { - tracing::debug!( - "Received msg from matchmaker {}: {:?}", - endpoint, - msg - ); - on_msg(msg); - } - ServerEvent::Accepted(endpoint, _id) => { - let mut clients = self.clients.write().unwrap(); - if !clients.insert(endpoint) { - tracing::warn!( - "Accepted matchmaker already known {}", - endpoint - ) - } - } - ServerEvent::Disconnected(endpoint) => { - let mut clients = self.clients.write().unwrap(); - if !clients.remove(&endpoint) { - tracing::warn!( - "Disconnected matchmaker unknown endpoint {}", - endpoint - ) - } - } - } - } - } - } - } -} - -impl ServerDialer { - /// Broadcast a message to all connected matchmaker clients - pub fn send(&mut self, msg: MsgFromServer) { - let net = self.handler.network(); - for client in self.clients.read().unwrap().iter() { - let msg_bytes = msg.try_to_vec().unwrap(); - let status = net.send(*client, &msg_bytes); - tracing::info!( - "Sent msg {:?} to {} with status {:?}", - msg, - client, - status - ); - } - } - - /// Is the server listener ready to start handling incoming connections? - pub fn is_ready(&self) -> bool { - self.handler - .network() - .is_ready(self.resource_id) - .unwrap_or_default() - } - - /// Force shut-down the [`ServerListener`] associated with this dialer. - pub fn shutdown(&mut self) { - self.handler.stop(); - // Send a message to abort and ignore the result - let _ = self.abort_send.blocking_send(()); - } -} - -impl ClientListener { - /// Create a new matchmaker client. Returns a listener and a dialer that - /// can be used to send messages to the server and to shut down the client. - pub fn new_pair(server_addr: impl ToRemoteAddr) -> (Self, ClientDialer) { - let server_addr = server_addr.to_remote_addr().unwrap(); - // Not using message-io signals - let (handler, listener) = node::split::<()>(); - - let (server, local_addr) = match handler - .network() - .connect(Transport::Ws, server_addr.clone()) - { - Ok(res) => res, - Err(err) => { - eprintln!( - "Cannot listen at {} for matchmakers server: {}", - server_addr, err, - ); - cli::safe_exit(1); - } - }; - tracing::info!("Matchmaker client running at {}", local_addr); - - let is_connected = Arc::new(AtomicBool::new(false)); - - ( - Self { - server, - local_addr, - listener: Some(listener), - is_connected: is_connected.clone(), - handler: handler.clone(), - }, - ClientDialer { - server, - local_addr, - handler, - is_connected, - }, - ) - } - - /// Start the client listener and call `on_msg` on every received message. - /// The listener can be stopped early by [`ClientDialer::shutdown`]. - pub fn listen(mut self, mut on_msg: impl FnMut(MsgFromServer)) { - // This is safe because `listen` consumes `self` - let listener = self.listener.take().unwrap(); - - // Start the blocking listener that will call `on_msg` on every message - let server_addr = self.server.addr(); - let local_addr_port = self.local_addr.port(); - - tracing::debug!("Matchmakers client is ready."); - - listener.for_each(move |event| { - tracing::debug!("Client event {:#?}", event); - match event { - node::NodeEvent::Network(net_event) => match net_event { - message_io::network::NetEvent::Message( - endpoint, - mut msg_bytes, - ) => match MsgFromServer::deserialize(&mut msg_bytes) { - Ok(msg) => { - on_msg(msg); - } - Err(err) => { - tracing::error!( - "Couldn't decode a msg from intent gossiper \ - {}: {}", - endpoint, - err - ); - } - }, - message_io::network::NetEvent::Connected( - _endpoint, - established, - ) => { - if established { - tracing::info!( - "Connected to the server at {}. The client is \ - identified by local port: {}", - server_addr, - local_addr_port - ); - } else { - tracing::error!( - "Cannot connect to the server at {}", - server_addr - ) - } - self.is_connected - .store(established, atomic::Ordering::SeqCst); - } - message_io::network::NetEvent::Disconnected(endpoint) => { - tracing::info!("Disconnected from {}", endpoint); - self.is_connected - .store(false, atomic::Ordering::SeqCst); - // Exit on disconnect, a user of this client can - // implement retry logic - self.handler.stop(); - } - message_io::network::NetEvent::Accepted(endpoint, _) => { - // Client only gets `NetEvent::Connected` from connected - // clients - tracing::error!( - "Unexpected client `NetEvent::Accepted` with \ - endpoint {}", - endpoint - ); - } - }, - node::NodeEvent::Signal(()) => { - // unused - } - } - }); - - tracing::debug!("Matchmakers client is shutting down."); - } -} - -impl ClientDialer { - /// Send a message to the intent gossiper server - pub fn send(&mut self, msg: MsgFromClient) { - let net = self.handler.network(); - let msg_bytes = msg.try_to_vec().unwrap(); - let status = net.send(self.server, &msg_bytes); - tracing::info!( - "Sent msg {:?} to {} with status {:?}", - msg, - self.server, - status - ); - } - - /// Is the client connected? - pub fn is_connected(&self) -> bool { - self.is_connected.load(atomic::Ordering::SeqCst) - } - - /// Force shut-down the [`ClientListener`] associated with this dialer. - pub fn shutdown(&mut self) { - self.handler.stop(); - } -} - -impl Drop for ServerDialer { - fn drop(&mut self) { - self.shutdown(); - } -} - -impl Drop for ClientDialer { - fn drop(&mut self) { - self.shutdown(); - } -} - -#[cfg(test)] -mod test { - use std::collections::HashMap; - use std::sync::atomic; - - use itertools::Itertools; - use proptest::prelude::*; - use proptest::prop_state_machine; - use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; - use proptest::test_runner::Config; - use test_log::test; - - use super::*; - - prop_state_machine! { - #![proptest_config(Config { - // Instead of the default 256, we only run 10 because otherwise it - // takes too long - cases: 10, - // 10 second timeout - timeout: 10_000, - .. Config::default() - })] - #[test] - /// A `StateMachineTest` implemented on `AbstractState` - fn connections_state_machine_test(sequential 1..20 => AbstractState); - } - - /// Abstract representation of a state of a server and client(s) - #[derive(Clone, Debug)] - struct AbstractState { - // true == running - server: bool, - clients: HashSet, - } - - /// State of a concrete server and client(s) implementation - #[derive(Default)] - struct ConcreteState { - server: Option, - clients: HashMap, - } - - /// State machine transitions - #[derive(Clone, Debug)] - enum Transition { - StartServer, - StopServer, - StartClient(ClientId), - StopClient(ClientId), - ServerMsg(MsgFromServer), - ClientMsg(ClientId, MsgFromClient), - } - - type ClientId = usize; - - struct TestServer { - /// The address of the server (assigned dynamically) - address: SocketAddr, - /// Runtime for the async listener - rt: tokio::runtime::Runtime, - #[allow(dead_code)] - /// Task that runs the async server listener - listener_handle: tokio::task::JoinHandle<()>, - /// A server dialer can send messages to clients - dialer: ServerDialer, - /// Messages received by the `listener` from clients are forwarded - /// to this receiver, to be checked by the test. - msgs_recv: std::sync::mpsc::Receiver, - } - - struct TestClient { - /// A client dialer can send messages to the server - dialer: ClientDialer, - /// A thread that runs the client listener - listener_handle: std::thread::JoinHandle<()>, - /// Messages received by the `listener` from the server are forwarded - /// to this receiver, to be checked by the test. - msgs_recv: std::sync::mpsc::Receiver, - } - - impl StateMachineTest for AbstractState { - type Abstract = Self; - type ConcreteState = ConcreteState; - - fn init_test( - _initial_state: ::State, - ) -> Self::ConcreteState { - ConcreteState::default() - } - - fn apply_concrete( - mut state: Self::ConcreteState, - transition: ::Transition, - ) -> Self::ConcreteState { - match transition { - Transition::StartServer => { - // Assign port dynamically - let (listener, dialer) = - ServerListener::new_pair("127.0.0.1:0"); - let address = listener.address; - let (msgs_send, msgs_recv) = std::sync::mpsc::channel(); - // Run the listener, we need an async runtime - let rt = tokio::runtime::Runtime::new().unwrap(); - let listener_handle = rt.spawn(async move { - listener - .listen(move |msg| { - msgs_send.send(msg).unwrap(); - }) - .await; - }); - - // Wait for the server to be ready - while !dialer.is_ready() { - println!("Waiting for the server to be ready"); - } - - state.server = Some(TestServer { - address, - rt, - dialer, - listener_handle, - msgs_recv, - }) - } - Transition::StopServer => { - // For the server, we have to send abort signal and drop - // the dialer - let mut server = state.server.take().unwrap(); - server.dialer.shutdown(); - server - .rt - .shutdown_timeout(std::time::Duration::from_secs(2)); - drop(server.dialer); - - if !state.clients.is_empty() { - println!( - "The server is waiting for all the clients to \ - stop..." - ); - while state.clients.values().any(|client| { - client - .dialer - .is_connected - .load(atomic::Ordering::SeqCst) - }) {} - // Stop the clients - for (id, client) in - std::mem::take(&mut state.clients).into_iter() - { - // Ask the client to stop - client.dialer.handler.stop(); - println!("Asking client {} listener to stop", id); - // Wait for it to actually stop - client.listener_handle.join().unwrap(); - println!("Client {} listener stopped", id); - } - println!("Clients stopped"); - } - } - Transition::StartClient(id) => { - let server_addr = state.server.as_ref().unwrap().address; - let (listener, dialer) = - ClientListener::new_pair(server_addr); - let (msgs_send, msgs_recv) = std::sync::mpsc::channel(); - let listener_handle = std::thread::spawn(move || { - listener.listen(|msg| { - msgs_send.send(msg).unwrap(); - }) - }); - - // If there is a server running ... - if let Some(server) = state.server.as_ref() { - // ... wait for the client to connect ... - while !dialer.is_connected() {} - // ... and for the server to accept it - while !server.dialer.clients.read().unwrap().iter().any( - |client| { - // Client's address is added once it's accepted - client.addr() == dialer.local_addr - }, - ) {} - } - - state.clients.insert( - id, - TestClient { - dialer, - listener_handle, - msgs_recv, - }, - ); - } - Transition::StopClient(id) => { - // Remove the client - let client = state.clients.remove(&id).unwrap(); - // Ask the client to stop - client.dialer.handler.stop(); - // Wait for it to actually stop - client.listener_handle.join().unwrap(); - } - Transition::ServerMsg(msg) => { - state.server.as_mut().unwrap().dialer.send(msg.clone()); - - // Post-condition: every client must receive the msg - for client in state.clients.values() { - let recv_msg = client.msgs_recv.recv().unwrap(); - assert_eq!(msg, recv_msg); - } - } - Transition::ClientMsg(id, msg) => { - let client = state.clients.get_mut(&id).unwrap(); - client.dialer.send(msg.clone()); - - // Post-condition: - // If there is a server running ... - if let Some(server) = state.server.as_mut() { - // ... it must receive the msg - let recv_msg = server.msgs_recv.recv().unwrap(); - assert_eq!(msg, recv_msg); - } - } - } - state - } - - fn test_sequential( - initial_state: ::State, - transitions: Vec< - ::Transition, - >, - ) { - let mut state = Self::init_test(initial_state); - for transition in transitions { - state = Self::apply_concrete(state, transition); - Self::invariants(&state); - } - - // Shutdown the server gracefully - if let Some(mut server) = state.server { - server.dialer.shutdown(); - server - .rt - .shutdown_timeout(std::time::Duration::from_secs(4)); - } - // Shutdown any clients too - if !state.clients.is_empty() { - println!( - "The server is waiting for all the clients to stop..." - ); - while state.clients.values().any(|client| { - client.dialer.is_connected.load(atomic::Ordering::SeqCst) - }) {} - println!("Clients stopped"); - } - } - } - - impl AbstractStateMachine for AbstractState { - type State = Self; - type Transition = Transition; - - fn init_state() -> BoxedStrategy { - Just(Self { - server: false, - clients: HashSet::default(), - }) - .boxed() - } - - fn transitions(state: &Self::State) -> BoxedStrategy { - use Transition::*; - if state.clients.is_empty() { - prop_oneof![ - Just(StartServer), - Just(StopServer), - (0..4_usize).prop_map(StartClient), - arb_msg_from_server().prop_map(ServerMsg), - ] - .boxed() - } else { - let ids: Vec<_> = - state.clients.iter().sorted().cloned().collect(); - let arb_id = proptest::sample::select(ids); - prop_oneof![ - Just(StartServer), - Just(StopServer), - (0..4_usize).prop_map(StartClient), - arb_msg_from_server().prop_map(ServerMsg), - arb_id.clone().prop_map(StopClient), - arb_id.prop_flat_map(|id| arb_msg_from_client() - .prop_map(move |msg| { ClientMsg(id, msg) })), - ] - .boxed() - } - } - - fn preconditions( - state: &Self::State, - transition: &Self::Transition, - ) -> bool { - match transition { - Transition::StartServer => !state.server, - Transition::StopServer => state.server, - Transition::StartClient(id) => { - // only start clients if the server is running and this - // client ID is not running - state.server && !state.clients.contains(id) - } - Transition::StopClient(id) => { - // stop only if this client is running - state.clients.contains(id) - } - Transition::ServerMsg(_) => { - // can send only if the server is running - state.server - } - Transition::ClientMsg(id, _) => { - // can send only if the server and this client is running - state.server && state.clients.contains(id) - } - } - } - - fn apply_abstract( - mut state: Self::State, - transition: &Self::Transition, - ) -> Self::State { - match transition { - Transition::StartServer => { - state.server = true; - } - Transition::StopServer => { - state.server = false; - // Clients should disconnect and stop - state.clients = Default::default(); - } - Transition::StartClient(id) => { - state.clients.insert(*id); - } - Transition::StopClient(id) => { - state.clients.remove(id); - } - Transition::ServerMsg(_msg) => { - // no change - } - Transition::ClientMsg(_id, _msg) => { - // no change - } - } - state - } - } - - prop_compose! { - /// Generate an arbitrary MsgFromServer - fn arb_msg_from_server() - (id in proptest::collection::vec(any::(), 1..100), - data in proptest::collection::vec(any::(), 1..100)) - -> MsgFromServer { - MsgFromServer::AddIntent { id, data } - } - } - - /// Generate an arbitrary MsgFromClient - fn arb_msg_from_client() -> impl Strategy { - let arb_intent_id = proptest::collection::vec(any::(), 1..100); - let invalid_intent = arb_intent_id - .clone() - .prop_map(|id| MsgFromClient::InvalidIntent { id }); - let intent_too_complex = arb_intent_id - .clone() - .prop_map(|id| MsgFromClient::IntentConstraintsTooComplex { id }); - let ignored_intent = arb_intent_id - .clone() - .prop_map(|id| MsgFromClient::IgnoredIntent { id }); - let unmatched_intent = arb_intent_id - .clone() - .prop_map(|id| MsgFromClient::Unmatched { id }); - let matched_intent = - proptest::collection::hash_set(arb_intent_id, 1..10).prop_map( - move |intent_ids| MsgFromClient::Matched { intent_ids }, - ); - prop_oneof![ - invalid_intent, - intent_too_complex, - ignored_intent, - matched_intent, - unmatched_intent, - ] - } -} diff --git a/apps/src/lib/node/gossip/rpc/mod.rs b/apps/src/lib/node/gossip/rpc/mod.rs deleted file mode 100644 index 541f5b1eb7..0000000000 --- a/apps/src/lib/node/gossip/rpc/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod client; -pub mod matchmakers; diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index a989338751..42a0ff1e9d 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -20,7 +20,6 @@ where /// Create a new genesis for the chain with specified id. This includes /// 1. A set of initial users and tokens /// 2. Setting up the validity predicates for both users and tokens - /// 3. A matchmaker pub fn init_chain( &mut self, init: request::InitChain, diff --git a/apps/src/lib/node/matchmaker.rs b/apps/src/lib/node/matchmaker.rs deleted file mode 100644 index 0a01528b00..0000000000 --- a/apps/src/lib/node/matchmaker.rs +++ /dev/null @@ -1,364 +0,0 @@ -use std::env; -use std::net::SocketAddr; -use std::path::{Path, PathBuf}; -use std::rc::Rc; -use std::sync::Arc; - -use borsh::{BorshDeserialize, BorshSerialize}; -use libc::c_void; -use libloading::Library; -use namada::proto::Tx; -use namada::types::address::{self, Address}; -use namada::types::dylib; -use namada::types::intent::{IntentTransfers, MatchedExchanges}; -use namada::types::key::*; -use namada::types::matchmaker::AddIntentResult; -use namada::types::transaction::{hash_tx, Fee, WrapperTx}; -use tendermint_config::net; -use tendermint_config::net::Address as TendermintAddress; - -use super::gossip::rpc::matchmakers::{ - ClientDialer, ClientListener, MsgFromClient, MsgFromServer, -}; -use crate::cli::args; -use crate::client::rpc; -use crate::client::tendermint_rpc_types::TxBroadcastData; -use crate::client::tx::broadcast_tx; -use crate::{cli, config, wasm_loader}; - -/// Run a matchmaker -#[tokio::main] -pub async fn run( - config::Matchmaker { - matchmaker_path, - tx_code_path, - }: config::Matchmaker, - intent_gossiper_addr: SocketAddr, - ledger_addr: TendermintAddress, - tx_signing_key: Rc, - tx_source_address: Address, - wasm_dir: impl AsRef, -) { - let matchmaker_path = matchmaker_path.unwrap_or_else(|| { - eprintln!("Please configure or specify the matchmaker path"); - cli::safe_exit(1); - }); - let tx_code_path = tx_code_path.unwrap_or_else(|| { - eprintln!("Please configure or specify the transaction code path"); - cli::safe_exit(1); - }); - - let (runner, result_handler) = Runner::new_pair( - intent_gossiper_addr, - matchmaker_path, - tx_code_path, - ledger_addr, - tx_signing_key, - tx_source_address, - wasm_dir, - ); - - // Instantiate and run the matchmaker implementation in a dedicated thread - let runner_join_handle = std::thread::spawn(move || { - runner.listen(); - }); - - // Process results async - result_handler.run().await; - - if let Err(error) = runner_join_handle.join() { - eprintln!("Matchmaker runner failed with: {:?}", error); - cli::safe_exit(1) - } -} - -/// A matchmaker receive intents and tries to find a match with previously -/// received intent. -#[derive(Debug)] -pub struct Runner { - matchmaker_path: PathBuf, - /// The client listener. This is consumed once the listener is started with - /// [`Runner::listen`]. - listener: Option, - /// Sender of results of matched intents to the [`ResultHandler`]. - result_send: tokio::sync::mpsc::UnboundedSender, -} - -/// Result handler processes the results sent from the matchmaker [`Runner`]. -#[derive(Debug)] -pub struct ResultHandler { - /// A dialer can send messages to the connected intent gossip node - dialer: ClientDialer, - /// A receiver of matched intents results from the [`Runner`]. - result_recv: tokio::sync::mpsc::UnboundedReceiver, - /// The ledger address to send any crafted transaction to - ledger_address: net::Address, - /// The code of the transaction that is going to be send to a ledger. - tx_code: Vec, - /// A source address for transactions created from intents. - tx_source_address: Address, - /// A keypair that will be used to sign transactions. - tx_signing_key: Rc, -} - -/// The loaded implementation's dylib and its state -#[derive(Debug)] -struct MatchmakerImpl { - /// The matchmaker's state as a raw mutable pointer to allow custom user - /// implementation in a dylib. - /// NOTE: The `state` field MUST be above the `library` field to ensure - /// that its destructor is ran before the implementation code is dropped. - state: MatchmakerState, - /// Matchmaker's implementation loaded from dylib - library: Library, -} - -/// The matchmaker's state as a raw mutable pointer to allow custom user -/// implementation in a dylib -#[derive(Debug)] -struct MatchmakerState(Arc<*mut c_void>); - -impl Runner { - /// Create a new matchmaker and a dialer that can be used to send messages - /// to the intent gossiper node. - pub fn new_pair( - intent_gossiper_addr: SocketAddr, - matchmaker_path: PathBuf, - tx_code_path: PathBuf, - ledger_address: TendermintAddress, - tx_signing_key: Rc, - tx_source_address: Address, - wasm_dir: impl AsRef, - ) -> (Self, ResultHandler) { - // Setup a channel for sending matchmaker results from `Self` to the - // `ResultHandler` - let (result_send, result_recv) = tokio::sync::mpsc::unbounded_channel(); - - // Prepare a client for intent gossiper node connection - let (listener, dialer) = ClientListener::new_pair(intent_gossiper_addr); - - let tx_code = wasm_loader::read_wasm(&wasm_dir, tx_code_path); - - ( - Self { - matchmaker_path, - listener: Some(listener), - result_send, - }, - ResultHandler { - dialer, - result_recv, - ledger_address, - tx_code, - tx_source_address, - tx_signing_key, - }, - ) - } - - pub fn listen(mut self) { - // Load the implementation's dylib and instantiate it. We have to do - // that here instead of `Self::new_pair`, because we cannot send - // it across threads and the listener is launched in a dedicated thread. - - // Check or add a filename extension to matchmaker path - let matchmaker_filename = - if let Some(ext) = self.matchmaker_path.extension() { - if ext != dylib::FILE_EXT { - tracing::warn!( - "Unexpected matchmaker file extension. Expected {}, \ - got {}.", - dylib::FILE_EXT, - ext.to_string_lossy(), - ); - } - self.matchmaker_path.clone() - } else { - let mut filename = self.matchmaker_path.clone(); - filename.set_extension(dylib::FILE_EXT); - filename - }; - - let matchmaker_dylib = if matchmaker_filename.is_absolute() { - // If the path is absolute, use it as is - matchmaker_filename - } else { - // The dylib should be built in the same directory as where Anoma - // binaries are, even when ran via `cargo run`. Anoma's pre-built - // binaries are distributed with the dylib(s) in the same directory. - let dylib_dir_with_bins = || { - let anoma_path = env::current_exe().unwrap(); - anoma_path - .parent() - .map(|path| path.to_owned()) - .unwrap() - .join(&matchmaker_filename) - }; - // Anoma built from source (`make install`) will install the - // dylib(s) to `~/.cargo/lib`. - let dylib_dir_installed = || { - directories::BaseDirs::new() - .expect("Couldn't determine the $HOME directory") - .home_dir() - .join(".cargo") - .join("lib") - .join(&matchmaker_filename) - }; - // Argument with file path relative to the current dir. - let dylib_dir_in_cwd = || { - let anoma_path = env::current_dir().unwrap(); - anoma_path.join(&matchmaker_filename) - }; - - // Try to find the matchmaker lib in either directory (computed - // lazily) - let matchmaker_dylib: Option = - check_file_exists(dylib_dir_with_bins) - .or_else(|| check_file_exists(dylib_dir_installed)) - .or_else(|| check_file_exists(dylib_dir_in_cwd)); - matchmaker_dylib.unwrap_or_else(|| { - panic!( - "The matchmaker library couldn't not be found. Did you \ - build it? Attempted to find it in directories \"{}\", \ - \"{}\" and \"{}\".", - dylib_dir_with_bins().to_string_lossy(), - dylib_dir_installed().to_string_lossy(), - dylib_dir_in_cwd().to_string_lossy(), - ); - }) - }; - tracing::info!( - "Running matchmaker from {}", - matchmaker_dylib.to_string_lossy() - ); - - let matchmaker_code = - unsafe { Library::new(matchmaker_dylib).unwrap() }; - - // Instantiate the matchmaker - let new_matchmaker: libloading::Symbol< - unsafe extern "C" fn() -> *mut c_void, - > = unsafe { matchmaker_code.get(b"_new_matchmaker").unwrap() }; - - let state = MatchmakerState(Arc::new(unsafe { new_matchmaker() })); - - let r#impl = MatchmakerImpl { - state, - library: matchmaker_code, - }; - - // Run the listener for messages from the connected intent gossiper node - self.listener.take().unwrap().listen(|msg| match msg { - MsgFromServer::AddIntent { id, data } => { - self.try_match_intent(&r#impl, id, data); - } - }) - } - - /// add the intent to the matchmaker mempool and tries to find a match for - /// that intent - fn try_match_intent( - &self, - r#impl: &MatchmakerImpl, - intent_id: Vec, - intent_data: Vec, - ) { - let add_intent: libloading::Symbol< - unsafe extern "C" fn( - *mut c_void, - &Vec, - &Vec, - ) -> AddIntentResult, - > = unsafe { r#impl.library.get(b"_add_intent").unwrap() }; - - let result = - unsafe { add_intent(*r#impl.state.0, &intent_id, &intent_data) }; - - self.result_send.send(result).unwrap(); - } -} - -impl Drop for MatchmakerImpl { - fn drop(&mut self) { - let drop_matchmaker: libloading::Symbol< - unsafe extern "C" fn(*mut c_void), - > = unsafe { self.library.get(b"_drop_matchmaker").unwrap() }; - - unsafe { drop_matchmaker(*self.state.0) }; - } -} - -impl ResultHandler { - async fn run(mut self) { - while let Some(result) = self.result_recv.recv().await { - if let Some(tx) = result.tx { - self.submit_tx(tx).await - } - if let Some(intent_ids) = result.matched_intents { - self.dialer.send(MsgFromClient::Matched { intent_ids }) - } - } - } - - async fn submit_tx(&self, tx_data: Vec) { - let tx_code = self.tx_code.clone(); - let matches = MatchedExchanges::try_from_slice(&tx_data[..]).unwrap(); - let intent_transfers = IntentTransfers { - matches, - source: self.tx_source_address.clone(), - }; - let tx_data = intent_transfers.try_to_vec().unwrap(); - let to_broadcast = { - let epoch = rpc::query_epoch(args::Query { - ledger_address: self.ledger_address.clone(), - }) - .await; - let tx = WrapperTx::new( - Fee { - amount: 0.into(), - token: address::xan(), - }, - &self.tx_signing_key, - epoch, - 0.into(), - Tx::new(tx_code, Some(tx_data)).sign(&self.tx_signing_key), - // TODO: Actually use the fetched encryption key - Default::default(), - ); - let wrapper_hash = hash_tx(&tx.try_to_vec().unwrap()).to_string(); - - let decrypted_hash = tx.tx_hash.to_string(); - TxBroadcastData::Wrapper { - tx: tx - .sign(&self.tx_signing_key) - .expect("Wrapper tx signing keypair should be correct"), - wrapper_hash, - decrypted_hash, - } - }; - - let response = - broadcast_tx(self.ledger_address.clone(), &to_broadcast).await; - match response { - Ok(tx_response) => { - tracing::info!( - "Injected transaction from matchmaker with result: {:#?}", - tx_response - ); - } - Err(err) => { - tracing::error!( - "Matchmaker error in submitting a transaction to the \ - ledger: {}", - err - ); - } - } - } -} - -/// Return the path of the file returned by `lazy_path` argument, if it exists. -fn check_file_exists(lazy_path: impl Fn() -> PathBuf) -> Option { - let path = lazy_path(); - if path.exists() { Some(path) } else { None } -} diff --git a/apps/src/lib/node/mod.rs b/apps/src/lib/node/mod.rs index 2265547868..370e1150a2 100644 --- a/apps/src/lib/node/mod.rs +++ b/apps/src/lib/node/mod.rs @@ -1,3 +1 @@ -pub mod gossip; pub mod ledger; -pub mod matchmaker; diff --git a/apps/src/lib/proto/README.md b/apps/src/lib/proto/README.md deleted file mode 100644 index 33a3b4673d..0000000000 --- a/apps/src/lib/proto/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Protobuf Compiled Definitions - -**The source files in the `generated` directory are generated by build.rs, do not edit them by hand** diff --git a/apps/src/lib/proto/generated.rs b/apps/src/lib/proto/generated.rs deleted file mode 100644 index 4e379ae78b..0000000000 --- a/apps/src/lib/proto/generated.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod services; diff --git a/apps/src/lib/proto/generated/.gitignore b/apps/src/lib/proto/generated/.gitignore deleted file mode 100644 index 6f5f3d11d3..0000000000 --- a/apps/src/lib/proto/generated/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.rs diff --git a/apps/src/lib/proto/mod.rs b/apps/src/lib/proto/mod.rs deleted file mode 100644 index 363f4e5455..0000000000 --- a/apps/src/lib/proto/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod generated; -mod types; - -pub use generated::services; -pub use types::{IntentMessage, RpcMessage, SubscribeTopicMessage}; diff --git a/apps/src/lib/proto/types.rs b/apps/src/lib/proto/types.rs deleted file mode 100644 index c8ef8af7e3..0000000000 --- a/apps/src/lib/proto/types.rs +++ /dev/null @@ -1,148 +0,0 @@ -use std::convert::{TryFrom, TryInto}; - -use namada::proto::{Dkg, Error, Intent}; - -use super::generated::services; - -pub type Result = std::result::Result; - -pub enum RpcMessage { - IntentMessage(IntentMessage), - SubscribeTopicMessage(SubscribeTopicMessage), - Dkg(Dkg), -} - -impl From for services::RpcMessage { - fn from(message: RpcMessage) -> Self { - let message = match message { - RpcMessage::IntentMessage(m) => { - services::rpc_message::Message::Intent(m.into()) - } - RpcMessage::SubscribeTopicMessage(m) => { - services::rpc_message::Message::Topic(m.into()) - } - RpcMessage::Dkg(d) => services::rpc_message::Message::Dkg(d.into()), - }; - services::RpcMessage { - message: Some(message), - } - } -} - -impl RpcMessage { - pub fn new_intent(intent: Intent, topic: String) -> Self { - RpcMessage::IntentMessage(IntentMessage::new(intent, topic)) - } - - pub fn new_topic(topic: String) -> Self { - RpcMessage::SubscribeTopicMessage(SubscribeTopicMessage::new(topic)) - } - - pub fn new_dkg(dkg: Dkg) -> Self { - RpcMessage::Dkg(dkg) - } -} - -#[derive(Debug, PartialEq)] -pub struct IntentMessage { - pub intent: Intent, - pub topic: String, -} - -impl TryFrom for IntentMessage { - type Error = Error; - - fn try_from(message: services::IntentMessage) -> Result { - match message.intent { - Some(intent) => Ok(IntentMessage { - intent: intent.try_into()?, - topic: message.topic, - }), - None => Err(Error::NoIntentError), - } - } -} - -impl From for services::IntentMessage { - fn from(message: IntentMessage) -> Self { - services::IntentMessage { - intent: Some(message.intent.into()), - topic: message.topic, - } - } -} - -impl IntentMessage { - pub fn new(intent: Intent, topic: String) -> Self { - IntentMessage { intent, topic } - } -} - -#[derive(Debug, PartialEq)] -pub struct SubscribeTopicMessage { - pub topic: String, -} - -impl From for SubscribeTopicMessage { - fn from(message: services::SubscribeTopicMessage) -> Self { - SubscribeTopicMessage { - topic: message.topic, - } - } -} - -impl From for services::SubscribeTopicMessage { - fn from(message: SubscribeTopicMessage) -> Self { - services::SubscribeTopicMessage { - topic: message.topic, - } - } -} - -impl SubscribeTopicMessage { - pub fn new(topic: String) -> Self { - SubscribeTopicMessage { topic } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_intent_message() { - let data = "arbitrary data".as_bytes().to_owned(); - let intent = Intent::new(data); - let topic = "arbitrary string".to_owned(); - let intent_message = IntentMessage::new(intent.clone(), topic.clone()); - - let intent_rpc_message = RpcMessage::new_intent(intent, topic); - let services_rpc_message: services::RpcMessage = - intent_rpc_message.into(); - match services_rpc_message.message { - Some(services::rpc_message::Message::Intent(i)) => { - let message_from_types = - IntentMessage::try_from(i).expect("no intent"); - assert_eq!(intent_message, message_from_types); - } - _ => panic!("no intent message"), - } - } - - #[test] - fn test_topic_message() { - let topic = "arbitrary string".to_owned(); - let topic_message = SubscribeTopicMessage::new(topic.clone()); - - let topic_rpc_message = RpcMessage::new_topic(topic); - let services_rpc_message: services::RpcMessage = - topic_rpc_message.into(); - match services_rpc_message.message { - Some(services::rpc_message::Message::Topic(t)) => { - let message_from_types = SubscribeTopicMessage::from(t); - assert_eq!(topic_message, message_from_types); - } - _ => panic!("no intent message"), - } - } -} diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index 6a4f9dacc3..ad519d64de 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -4,8 +4,7 @@ pub use dev::{ addresses, albert_address, albert_keypair, bertha_address, bertha_keypair, christel_address, christel_keypair, daewon_address, daewon_keypair, keys, - matchmaker_address, matchmaker_keypair, validator_address, - validator_keypair, validator_keys, + validator_address, validator_keypair, validator_keys, }; use namada::ledger::{eth_bridge, governance, pos}; use namada::types::address::Address; @@ -107,7 +106,6 @@ mod dev { ("bertha".into(), bertha_keypair()), ("christel".into(), christel_keypair()), ("daewon".into(), daewon_keypair()), - ("matchmaker".into(), matchmaker_keypair()), ("validator".into(), validator_keypair()), ] } @@ -118,7 +116,6 @@ mod dev { ("pos".into(), pos::ADDRESS), ("pos_slash_pool".into(), pos::SLASH_POOL_ADDRESS), ("governance".into(), governance::vp::ADDRESS), - ("matchmaker".into(), matchmaker_address()), ("validator".into(), validator_address()), ("albert".into(), albert_address()), ("bertha".into(), bertha_address()), @@ -158,11 +155,6 @@ mod dev { Address::decode("atest1v4ehgw36ggcnsdee8qerswph8y6ry3p5xgunvve3xaqngd3kxc6nqwz9gseyydzzg5unys3ht2n48q").expect("The token address decoding shouldn't fail") } - /// An established matchmaker address for testing & development - pub fn matchmaker_address() -> Address { - Address::decode("atest1v4ehgw36x5mnswphx565gv2yxdprzvf5gdp523jpxy6rvv6zxaznzsejxeznzseh8pp5ywz93xwala").expect("The address decoding shouldn't fail") - } - pub fn albert_keypair() -> common::SecretKey { // generated from // [`namada::types::key::ed25519::gen_keypair`] @@ -222,16 +214,4 @@ mod dev { let ed_sk = ed25519::SecretKey::try_from_slice(&bytes).unwrap(); ed_sk.try_to_sk().unwrap() } - - pub fn matchmaker_keypair() -> common::SecretKey { - // generated from - // [`namada::types::key::ed25519::gen_keypair`] - let bytes = [ - 91, 67, 244, 37, 241, 33, 157, 218, 37, 172, 191, 122, 75, 2, 44, - 219, 28, 123, 44, 34, 9, 240, 244, 49, 112, 192, 180, 98, 142, 160, - 182, 14, - ]; - let ed_sk = ed25519::SecretKey::try_from_slice(&bytes).unwrap(); - ed_sk.try_to_sk().unwrap() - } } diff --git a/documentation/docs/src/testnets/internal-testnet-1.md b/documentation/docs/src/testnets/internal-testnet-1.md index d4ad45adb6..ef376341d8 100644 --- a/documentation/docs/src/testnets/internal-testnet-1.md +++ b/documentation/docs/src/testnets/internal-testnet-1.md @@ -165,7 +165,7 @@ specified. However, any transparent account can sign these transactions. ### Build from Source -Build the provided validity predicate, transaction and matchmaker wasm modules +Build the provided validity predicate and transaction wasm modules ```shell make build-wasm-scripts-docker diff --git a/documentation/docs/src/user-guide/getting-started.md b/documentation/docs/src/user-guide/getting-started.md index 6f2ce00e30..03e77110ba 100644 --- a/documentation/docs/src/user-guide/getting-started.md +++ b/documentation/docs/src/user-guide/getting-started.md @@ -3,7 +3,7 @@ This guide assumes that the Namada binaries are [installed](./install.md) and available on path. These are: - `namada`: The main binary that can be used to interact with all the components of Namada -- `namadan`: The ledger and intent gossiper node +- `namadan`: The ledger node - `namadac`: The client - `namadaw`: The wallet diff --git a/documentation/docs/src/user-guide/intent-gossiper-and-matchmaker.md b/documentation/docs/src/user-guide/intent-gossiper-and-matchmaker.md deleted file mode 100644 index abacd929d6..0000000000 --- a/documentation/docs/src/user-guide/intent-gossiper-and-matchmaker.md +++ /dev/null @@ -1,124 +0,0 @@ -# The Intent gossiper and Matchmaker - -To run an intent gossiper node with an RPC server through which new intents can be submitted: - -```shell -anoma node gossip --rpc "127.0.0.1:26660" -``` - -To run a token exchange matchmaker: - -```shell -anoma node matchmaker --matchmaker-path libmm_token_exch --tx-code-path wasm/tx_from_intent.wasm --ledger-address "127.0.0.1:26657" --source matchmaker --signing-key matchmaker -``` - -Mind that `matchmaker` must be an established account known on the ledger with a key in your wallet that will be used to sign transactions submitted from the matchmaker to the ledger. - -This pre-built matchmaker implementation is [the fungible token exchange `mm_token_exch`](https://github.com/anoma/anoma/blob/5051b3abbc645aed2e40e1ff8db2d682e9a115e9/matchmaker/mm_token_exch/src/lib.rs), that is being used together with [the pre-built `tx_from_intent` transaction WASM](https://github.com/anoma/anoma/blob/5051b3abbc645aed2e40e1ff8db2d682e9a115e9/wasm/wasm_source/src/lib.rs#L140) to submit transaction from matched intents to the ledger. - -## ✋ Example intents - -1) Lets create some accounts: - - ```shell - anoma wallet key gen --alias alberto --unsafe-dont-encrypt - anoma client init-account --alias alberto-account --public-key alberto --source alberto - - anoma wallet key gen --alias christel --unsafe-dont-encrypt - anoma client init-account --alias christel-account --public-key christel --source christel - - anoma wallet key gen --alias bertha --unsafe-dont-encrypt - anoma client init-account --alias bertha-account --public-key bertha --source bertha - - anoma wallet key gen --alias my-matchmaker --unsafe-dont-encrypt - anoma client init-account --alias my-matchmaker-account --public-key my-matchmaker --source my-matchmaker - ``` - -1) We then need some tokens: - - ```shell - anoma client transfer --source faucet --target alberto-account --signer alberto-account --token BTC --amount 1000 - anoma client transfer --source faucet --target bertha-account --signer bertha-account --token ETH --amount 1000 - anoma client transfer --source faucet --target christel-account --signer christel-account --token NAM --amount 1000 - ``` - -1) Lets export some variables: - - ```shell - export ALBERTO=$(anoma wallet address find --alias alberto-account | cut -c 28- | tr -d '\n') - export CHRISTEL=$(anoma wallet address find --alias christel-account | cut -c 28- | tr -d '\n') - export BERTHA=$(anoma wallet address find --alias bertha-account | cut -c 28- | tr -d '\n') - export NAM=$(anoma wallet address find --alias NAM | cut -c 28- | tr -d '\n') - export BTC=$(anoma wallet address find --alias BTC | cut -c 28- | tr -d '\n') - export ETH=$(anoma wallet address find --alias ETH | cut -c 28- | tr -d '\n') - ``` - -1) Create files with the intents description: - - ```shell - echo '[{"addr":"'$ALBERTO'","key":"'$ALBERTO'","max_sell":"70","min_buy":"100","rate_min":"2","token_buy":"'$NAM'","token_sell":"'$BTC'","vp_path": "wasm_for_tests/vp_always_true.wasm"}]' > intent.A.data - - echo '[{"addr":"'$BERTHA'","key":"'$BERTHA'","max_sell":"300","min_buy":"50","rate_min":"0.7","token_buy":"'$BTC'","token_sell":"'$ETH'"}]' > intent.B.data - - echo '[{"addr":"'$CHRISTEL'","key":"'$CHRISTEL'","max_sell":"200","min_buy":"20","rate_min":"0.5","token_buy":"'$ETH'","token_sell":"'$NAM'"}]' > intent.C.data - ``` - -1) Start the ledger, intent gossiper and the matchmaker. Instruct the intent gossiper to subscribe to a topic "asset_v1": - - ```shell - anoma node ledger run - - anoma node gossip --rpc "127.0.0.1:26660" - - anoma node matchmaker --matchmaker-path wasm/mm_token_exch.wasm --tx-code-path wasm/tx_from_intent.wasm --ledger-address "127.0.0.1:26657" --source mm-1 --signing-key mm-1 - - anoma client subscribe-topic --node "http://127.0.0.1:26660" --topic "asset_v1" - ``` - -1) Submit the intents (the target gossiper node must be running an RPC server): - - ```shell - anoma client intent --data-path intent.A.data --topic "asset_v1" --signing-key alberto --node "http://127.0.0.1:26660" - anoma client intent --data-path intent.B.data --topic "asset_v1" --signing-key bertha --node "http://127.0.0.1:26660" - anoma client intent --data-path intent.C.data --topic "asset_v1" --signing-key christel --node "http://127.0.0.1:26660" - ``` - - The matchmaker should find a match from these intents and submit a transaction to the ledger that performs the n-party transfers of tokens. - -1) You can check the balances with: - - ```shell - anoma client balance --owner alberto-account - anoma client balance --owner bertha-account - anoma client balance --owner christel-account - ``` - -## 🤝 Custom matchmaker - -A custom matchmaker code can be built from [`matchmaker/mm_template`](https://github.com/anoma/anoma/tree/master/matchmaker/mm_template). - -The `anoma_macros::Matchmaker` macro can be used to derive the binding code for the matchmaker runner on any custom implementation, e.g.: - -```rust -#[derive(Default, Matchmaker)] -struct MyMatchmaker; -``` - -This macro requires that there is a `Default` implementation (derived or custom) for the matchmaker, which can be used by the runner to instantiate the matchmaker. - -The matchmaker must also implement `AddIntent`, e.g.: - -```rust -impl AddIntent for MyMatchmaker { - // This function will be called when a new intent is received - fn add_intent( - &mut self, - _intent_id: &Vec, - _intent_data: &Vec, - ) -> AddIntentResult { - AddIntentResult::default() - } -} -``` - -To submit a transaction from the matchmaker, add it to the `AddIntentResult` along with a hash set of the intent IDs that were matched into the transaction. diff --git a/genesis/dev.toml b/genesis/dev.toml index 4c48bcc279..35b8140ea9 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -114,11 +114,6 @@ address = "atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwf public_key = "d06f8d4f897f329a50fd23ba5d2503bbe22fab2f14d5f625e07a65f617eb2778" vp = "vp_user" -[established.matchmaker] -address = "atest1v4ehgw36x5mnswphx565gv2yxdprzvf5gdp523jpxy6rvv6zxaznzsejxeznzseh8pp5ywz93xwala" -public_key = "f4fe03b0d3130f077e4d51cc7748baac998750476bef994a0a73ac4e7d183168" -vp = "vp_user" - # An implicit account present at genesis. # Daewon (a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6mtz) diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 96fd787ca2..54961a99fe 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -1,6 +1,5 @@ # Genesis configuration source for E2E tests with: -# - 1 genesis validator and intent # gossip nodes -# - a matchmaker configured on the first validator node +# - 1 genesis validator # - User accounts same as the ones in "dev" build (Albert, Bertha, Christel) genesis_time = "2021-09-30T10:00:00Z" @@ -18,13 +17,6 @@ staking_reward_vp = "vp_user" # We set the port to be the default+1000, so that if a local node was running at # the same time as the E2E tests, it wouldn't affect them. net_address = "127.0.0.1:27656" -# This has to be an alias of one of the established accounts -matchmaker_account = "matchmaker" -# A matchmaker dylib program's name (the platform specific extension -# `(dll|dylib|so)` is added by Anoma) -matchmaker_code = "libmm_token_exch" -# A transaction WASM code used by the matchmaker -matchmaker_tx = "wasm/tx_from_intent.wasm" # Some tokens present at genesis. @@ -41,8 +33,6 @@ Christel = 1000000 Daewon = 1000000 faucet = 9223372036854 "faucet.public_key" = 100 -matchmaker = 1000000 -"matchmaker.public_key" = 1000 "validator-0.public_key" = 100 [token.BTC] @@ -110,9 +100,6 @@ faucet = 9223372036854 [established.faucet] vp = "vp_testnet_faucet" -[established.matchmaker] -vp = "vp_user" - [established.Albert] vp = "vp_user" diff --git a/macros/src/lib.rs b/macros/src/lib.rs index afa49c66ab..2d6e06d66d 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,5 +1,5 @@ -//! Anoma macros for generating WASM binding code for transactions, validity -//! predicates and matchmaker. +//! Anoma macros for generating WASM binding code for transactions and validity +//! predicates. #![doc(html_favicon_url = "https://dev.anoma.net/master/favicon.png")] #![doc(html_logo_url = "https://dev.anoma.net/master/rustdoc-logo.png")] @@ -8,7 +8,7 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, DeriveInput, ItemFn}; +use syn::{parse_macro_input, ItemFn}; /// Generate WASM binding for a transaction main entrypoint function. /// @@ -123,114 +123,3 @@ pub fn validity_predicate( }; TokenStream::from(gen) } - -/// Derive dynamic library binding for a matchmaker implementation. -/// -/// This macro requires that the data structure implements -/// [`std::default::Default`] that is used to instantiate the matchmaker and -/// `namada::types::matchmaker::AddIntent` to implement a custom matchmaker -/// algorithm. -/// -/// # Examples -/// -/// ```compiler_fail -/// use namada::types::matchmaker::AddIntent; -/// use namada_macros::Matchmaker; -/// -/// #[derive(Default, Matchmaker)] -/// struct Matchmaker; -/// -/// impl AddIntent for Matchmaker { -/// } -/// ``` -#[proc_macro_derive(Matchmaker)] -pub fn matchmaker(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - let ident = &ast.ident; - // Print out the original AST and add add_intent implementation and binding - let gen = quote! { - - /// Add the marker trait - #[automatically_derived] - impl namada::types::matchmaker::Matchmaker for #ident {} - - /// Instantiate a new matchmaker and return a pointer to it. The caller is - /// responsible for making sure that the memory of the pointer will be dropped, - /// which can be done by calling the `_drop_matchmaker` function. - #[no_mangle] - #[automatically_derived] - fn _new_matchmaker() -> *mut std::ffi::c_void { - let state = Box::new(#ident::default()); - let state_ptr = Box::into_raw(state) as *mut std::ffi::c_void; - state_ptr - } - - /// Drop the matchmaker's state to reclaim its memory - #[no_mangle] - #[automatically_derived] - fn _drop_matchmaker(state_ptr: *mut std::ffi::c_void) { - // The state will be dropped on going out of scope - let _state = unsafe { Box::from_raw(state_ptr as *mut #ident) }; - } - - /// Ask the matchmaker to process a new intent - #[allow(clippy::ptr_arg)] - #[no_mangle] - #[automatically_derived] - fn _add_intent( - state_ptr: *mut std::ffi::c_void, - intent_id: &Vec, - intent_data: &Vec, - ) -> namada::types::matchmaker::AddIntentResult { - let state_ptr = state_ptr as *mut #ident; - let mut state: #ident = unsafe { std::ptr::read(state_ptr) }; - let result = state.add_intent(intent_id, intent_data); - unsafe { std::ptr::write(state_ptr, state) }; - result - } - }; - TokenStream::from(gen) -} - -/// Generate WASM binding for matchmaker filter main entrypoint function. -/// -/// This macro expects a function with signature: -/// -/// ```compiler_fail -/// fn validate_intent(intent: Vec) -> bool -/// ``` -#[proc_macro_attribute] -pub fn filter(_attr: TokenStream, input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as ItemFn); - let ident = &ast.sig.ident; - let gen = quote! { - // Use `wee_alloc` as the global allocator. - #[global_allocator] - static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - - #ast - - /// The module interface callable by wasm runtime - #[no_mangle] - extern "C" fn _validate_intent( - intent_data_ptr: u64, - intent_data_len: u64, - ) -> u64 { - let get_data = |ptr, len| { - let slice = unsafe { - core::slice::from_raw_parts(ptr as *const u8, len as _) - }; - slice.to_vec() - }; - - if #ident( - get_data(intent_data_ptr, intent_data_len), - ) { - 0 - } else { - 1 - } - } - }; - TokenStream::from(gen) -} diff --git a/proto/services.proto b/proto/services.proto deleted file mode 100644 index 9f16dddb91..0000000000 --- a/proto/services.proto +++ /dev/null @@ -1,30 +0,0 @@ -syntax = "proto3"; - -package services; - -import "types.proto"; - -service RPCService { - rpc SendMessage(RpcMessage) returns (RpcResponse); -} - -message IntentMessage{ - types.Intent intent = 1; - string topic = 2; -} - -message SubscribeTopicMessage{ - string topic = 2; -} - -message RpcMessage { - oneof message { - IntentMessage intent = 1; - SubscribeTopicMessage topic = 2; - types.Dkg dkg = 3; - } -} - -message RpcResponse { - string result = 1; -} diff --git a/proto/types.proto b/proto/types.proto index b4a0162b50..58494ec824 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -11,24 +11,8 @@ message Tx { google.protobuf.Timestamp timestamp = 3; } -message Intent { - bytes data = 1; - google.protobuf.Timestamp timestamp = 2; -} - -message IntentGossipMessage{ - // TODO remove oneof because it's not used so far - oneof msg { - Intent intent = 1; - } -} - -message Dkg { - string data = 1; -} +message Dkg { string data = 1; } -message DkgGossipMessage{ - oneof dkg_message { - Dkg dkg = 1; - } +message DkgGossipMessage { + oneof dkg_message { Dkg dkg = 1; } } diff --git a/shared/build.rs b/shared/build.rs index 3c5ffb41f1..74dd72b753 100644 --- a/shared/build.rs +++ b/shared/build.rs @@ -58,9 +58,6 @@ fn main() { tonic_build::configure() .out_dir("src/proto/generated") .format(use_rustfmt) - // TODO try to add json encoding to simplify use for user - // .type_attribute("types.Intent", "#[derive(serde::Serialize, - // serde::Deserialize)]") .protoc_arg("--experimental_allow_proto3_optional") .compile(&[format!("{}/types.proto", PROTO_SRC)], &[PROTO_SRC]) .unwrap(); diff --git a/shared/src/proto/mod.rs b/shared/src/proto/mod.rs index aa971f0b96..564a26e941 100644 --- a/shared/src/proto/mod.rs +++ b/shared/src/proto/mod.rs @@ -3,9 +3,7 @@ pub mod generated; mod types; -pub use types::{ - Dkg, Error, Intent, IntentGossipMessage, IntentId, Signed, SignedTxData, Tx, -}; +pub use types::{Dkg, Error, Signed, SignedTxData, Tx}; #[cfg(test)] mod tests { diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 7a936dd58f..6f7efd4b70 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -1,6 +1,4 @@ -use std::collections::hash_map::DefaultHasher; use std::convert::{TryFrom, TryInto}; -use std::fmt::Display; use std::hash::{Hash, Hasher}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -17,12 +15,8 @@ use crate::types::transaction::hash_tx; pub enum Error { #[error("Error decoding a transaction from bytes: {0}")] TxDecodingError(prost::DecodeError), - #[error("Error decoding an IntentGossipMessage from bytes: {0}")] - IntentDecodingError(prost::DecodeError), #[error("Error decoding an DkgGossipMessage from bytes: {0}")] DkgDecodingError(prost::DecodeError), - #[error("Intent is empty")] - NoIntentError, #[error("Dkg is empty")] NoDkgError, #[error("Timestamp is empty")] @@ -221,53 +215,6 @@ impl Tx { } } -#[derive(Clone, Debug, PartialEq)] -pub struct IntentGossipMessage { - pub intent: Intent, -} - -impl TryFrom<&[u8]> for IntentGossipMessage { - type Error = Error; - - fn try_from(intent_bytes: &[u8]) -> Result { - let intent = types::IntentGossipMessage::decode(intent_bytes) - .map_err(Error::IntentDecodingError)?; - match &intent.msg { - Some(types::intent_gossip_message::Msg::Intent(intent)) => { - Ok(IntentGossipMessage { - intent: intent.clone().try_into()?, - }) - } - None => Err(Error::NoIntentError), - } - } -} - -impl From for types::IntentGossipMessage { - fn from(message: IntentGossipMessage) -> Self { - types::IntentGossipMessage { - msg: Some(types::intent_gossip_message::Msg::Intent( - message.intent.into(), - )), - } - } -} - -impl IntentGossipMessage { - pub fn new(intent: Intent) -> Self { - IntentGossipMessage { intent } - } - - pub fn to_bytes(&self) -> Vec { - let mut bytes = vec![]; - let message: types::IntentGossipMessage = self.clone().into(); - message - .encode(&mut bytes) - .expect("encoding an intent gossip message failed"); - bytes - } -} - #[allow(dead_code)] #[derive(Clone, Debug, PartialEq)] pub struct DkgGossipMessage { @@ -317,67 +264,6 @@ impl DkgGossipMessage { } } -#[derive(Clone, Debug, PartialEq, Hash, Eq)] -pub struct Intent { - pub data: Vec, - pub timestamp: DateTimeUtc, -} - -impl TryFrom for Intent { - type Error = Error; - - fn try_from(intent: types::Intent) -> Result { - let timestamp = match intent.timestamp { - Some(t) => t.try_into().map_err(Error::InvalidTimestamp)?, - None => return Err(Error::NoTimestampError), - }; - Ok(Intent { - data: intent.data, - timestamp, - }) - } -} - -impl From for types::Intent { - fn from(intent: Intent) -> Self { - let timestamp = Some(intent.timestamp.into()); - types::Intent { - data: intent.data, - timestamp, - } - } -} - -impl Intent { - pub fn new(data: Vec) -> Self { - Intent { - data, - timestamp: DateTimeUtc::now(), - } - } - - pub fn id(&self) -> IntentId { - let mut hasher = DefaultHasher::new(); - self.hash(&mut hasher); - IntentId::from(hasher.finish().to_string()) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct IntentId(pub Vec); - -impl>> From for IntentId { - fn from(value: T) -> Self { - Self(value.into()) - } -} - -impl Display for IntentId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0)) - } -} - #[allow(dead_code)] #[derive(Clone, Debug, PartialEq)] pub struct Dkg { @@ -431,18 +317,6 @@ mod tests { } } - #[test] - fn test_intent_gossip_message() { - let data = "arbitrary data".as_bytes().to_owned(); - let intent = Intent::new(data); - let message = IntentGossipMessage::new(intent); - - let bytes = message.to_bytes(); - let message_from_bytes = IntentGossipMessage::try_from(bytes.as_ref()) - .expect("decoding failed"); - assert_eq!(message_from_bytes, message); - } - #[test] fn test_dkg_gossip_message() { let data = "arbitrary string".to_owned(); @@ -455,26 +329,6 @@ mod tests { assert_eq!(message_from_bytes, message); } - #[test] - fn test_intent() { - let data = "arbitrary data".as_bytes().to_owned(); - let intent = Intent::new(data.clone()); - - let types_intent: types::Intent = intent.clone().into(); - let intent_from_types = - Intent::try_from(types_intent).expect("no timestamp"); - assert_eq!(intent_from_types, intent); - - let types_intent = types::Intent { - data, - timestamp: None, - }; - match Intent::try_from(types_intent) { - Err(Error::NoTimestampError) => {} - _ => panic!("unexpected result"), - } - } - #[test] fn test_dkg() { let data = "arbitrary string".to_owned(); diff --git a/shared/src/types/intent.rs b/shared/src/types/intent.rs deleted file mode 100644 index c3effbb4fc..0000000000 --- a/shared/src/types/intent.rs +++ /dev/null @@ -1,475 +0,0 @@ -//! Intent data definitions and transaction and validity-predicate helpers. - -use std::collections::{HashMap, HashSet}; -use std::convert::TryFrom; -use std::io::ErrorKind; - -use borsh::{BorshDeserialize, BorshSerialize}; -use derivative::Derivative; -use rust_decimal::prelude::*; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -use crate::proto::Signed; -use crate::types::address::Address; -use crate::types::storage::{DbKeySeg, Key, KeySeg}; -use crate::types::token; - -/// A simple intent for fungible token trade -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, - Eq, -)] -pub struct FungibleTokenIntent { - /// List of exchange definitions - pub exchange: HashSet>, -} - -#[derive( - Debug, - Clone, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, - Eq, - PartialEq, - Hash, - PartialOrd, - Derivative, -)] -/// The definition of an intent exchange -pub struct Exchange { - /// The source address - pub addr: Address, - /// The token to be sold - pub token_sell: Address, - /// The minimum rate - pub rate_min: DecimalWrapper, - /// The maximum amount of token to be sold - pub max_sell: token::Amount, - /// The token to be bought - pub token_buy: Address, - /// The amount of token to be bought - pub min_buy: token::Amount, - /// The vp code - #[derivative(Debug = "ignore")] - pub vp: Option>, -} - -/// These are transfers crafted from matched [`Exchange`]s created by a -/// matchmaker program. -#[derive( - Debug, - Clone, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, - PartialEq, -)] -pub struct MatchedExchanges { - /// Transfers crafted from the matched intents - pub transfers: HashSet, - // TODO benchmark between an map or a set, see which is less costly - /// The exchanges that were matched - pub exchanges: HashMap>, - /// The intents - // TODO: refactor this without duplicating stuff. The exchanges in the - // `exchanges` hashmap are already contained in the FungibleTokenIntents - // belows - pub intents: HashMap>, -} - -/// These are transfers crafted from matched [`Exchange`]s with a source address -/// that is expected to sign this data. -#[derive( - Debug, - Clone, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, - PartialEq, -)] -pub struct IntentTransfers { - /// Matched exchanges - pub matches: MatchedExchanges, - /// Source address that should sign this data - pub source: Address, -} - -/// Struct holding a safe rapresentation of a float -#[derive( - Debug, - Clone, - Eq, - PartialEq, - Hash, - PartialOrd, - Serialize, - Deserialize, - Default, -)] -pub struct DecimalWrapper(pub Decimal); - -impl From for DecimalWrapper { - fn from(decimal: Decimal) -> Self { - DecimalWrapper(decimal) - } -} - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("Error parsing as decimal: {0}.")] - DecimalParseError(String), -} - -impl TryFrom for DecimalWrapper { - type Error = Error; - - fn try_from(amount: token::Amount) -> Result { - let decimal = Decimal::from_i128(amount.change()); - - match decimal { - Some(d) => Ok(DecimalWrapper::from(d)), - None => Err(Error::DecimalParseError(amount.change().to_string())), - } - } -} - -impl FromStr for DecimalWrapper { - type Err = Error; - - fn from_str(s: &str) -> Result { - let decimal = Decimal::from_str(s) - .map_err(|e| Self::Err::DecimalParseError(e.to_string())); - - match decimal { - Ok(d) => Ok(DecimalWrapper::from(d)), - Err(e) => Err(e), - } - } -} - -impl BorshSerialize for DecimalWrapper { - fn serialize( - &self, - writer: &mut W, - ) -> std::io::Result<()> { - let vec = self.0.to_string().as_bytes().to_vec(); - let bytes = vec - .try_to_vec() - .expect("DecimalWrapper bytes encoding shouldn't fail"); - writer.write_all(&bytes) - } -} - -impl BorshDeserialize for DecimalWrapper { - fn deserialize(buf: &mut &[u8]) -> std::io::Result { - // deserialize the bytes first - let bytes: Vec = - BorshDeserialize::deserialize(buf).map_err(|e| { - std::io::Error::new( - ErrorKind::InvalidInput, - format!("Error decoding DecimalWrapper: {}", e), - ) - })?; - let decimal_str: &str = - std::str::from_utf8(bytes.as_slice()).map_err(|e| { - std::io::Error::new( - ErrorKind::InvalidInput, - format!("Error decoding decimal: {}", e), - ) - })?; - let decimal = Decimal::from_str(decimal_str).map_err(|e| { - std::io::Error::new( - ErrorKind::InvalidInput, - format!("Error decoding decimal: {}", e), - ) - })?; - Ok(DecimalWrapper(decimal)) - } -} - -impl MatchedExchanges { - /// Create an empty [`MatchedExchanges`]. - pub fn empty() -> Self { - Self { - transfers: HashSet::new(), - exchanges: HashMap::new(), - intents: HashMap::new(), - } - } -} - -const INVALID_INTENT_STORAGE_KEY: &str = "invalid_intent"; - -/// Obtain a storage key for user's invalid intent set. -pub fn invalid_intent_key(owner: &Address) -> Key { - Key::from(owner.to_db_key()) - .push(&INVALID_INTENT_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Check if the given storage key is a key for a set of intent sig. If it is, -/// returns the owner. -pub fn is_invalid_intent_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [DbKeySeg::AddressSeg(owner), DbKeySeg::StringSeg(key)] - if key == INVALID_INTENT_STORAGE_KEY => - { - Some(owner) - } - _ => None, - } -} - -#[cfg(test)] -mod tests { - use std::env; - use std::iter::FromIterator; - - use constants::*; - - use super::*; - use crate::ledger::storage::types::{decode, encode}; - use crate::types::key; - - #[test] - fn test_encode_decode_intent_transfer_without_vp() { - let bertha_addr = Address::from_str(BERTHA).unwrap(); - let albert_addr = Address::from_str(ALBERT).unwrap(); - - let bertha_keypair = key::testing::keypair_1(); - let albert_keypair = key::testing::keypair_2(); - - let exchange_one = Exchange { - addr: Address::from_str(BERTHA).unwrap(), - token_buy: Address::from_str(XAN).unwrap(), - token_sell: Address::from_str(BTC).unwrap(), - max_sell: token::Amount::from(100), - min_buy: token::Amount::from(1), - rate_min: DecimalWrapper::from_str("0.1").unwrap(), - vp: None, - }; - let exchange_two = Exchange { - addr: Address::from_str(ALBERT).unwrap(), - token_buy: Address::from_str(BTC).unwrap(), - token_sell: Address::from_str(XAN).unwrap(), - max_sell: token::Amount::from(1), - min_buy: token::Amount::from(100), - rate_min: DecimalWrapper::from_str("10").unwrap(), - vp: None, - }; - - let signed_exchange_one = Signed::new(&bertha_keypair, exchange_one); - let signed_exchange_two = Signed::new(&bertha_keypair, exchange_two); - - let mut it = MatchedExchanges::empty(); - it.exchanges = HashMap::<_, _>::from_iter( - vec![ - (bertha_addr.clone(), signed_exchange_one.clone()), - (albert_addr.clone(), signed_exchange_two.clone()), - ] - .into_iter(), - ); - - it.intents = HashMap::<_, _>::from_iter( - vec![ - ( - bertha_addr.clone(), - Signed::new( - &bertha_keypair, - FungibleTokenIntent { - exchange: HashSet::from_iter(vec![ - signed_exchange_one, - ]), - }, - ), - ), - ( - albert_addr.clone(), - Signed::new( - &albert_keypair, - FungibleTokenIntent { - exchange: HashSet::from_iter(vec![ - signed_exchange_two, - ]), - }, - ), - ), - ] - .into_iter(), - ); - - it.transfers = HashSet::<_>::from_iter( - vec![ - token::Transfer { - source: bertha_addr.clone(), - target: albert_addr.clone(), - token: Address::from_str(BTC).unwrap(), - amount: token::Amount::from(100), - }, - token::Transfer { - source: albert_addr, - target: bertha_addr, - token: Address::from_str(XAN).unwrap(), - amount: token::Amount::from(1), - }, - ] - .into_iter(), - ); - - let encoded_intent_transfer = encode(&it); - let decoded_intent_transfer: MatchedExchanges = - decode(encoded_intent_transfer).unwrap(); - - assert!(decoded_intent_transfer == it); - } - - #[test] - fn test_encode_decode_intent_transfer_with_vp() { - let bertha_addr = Address::from_str(BERTHA).unwrap(); - let albert_addr = Address::from_str(ALBERT).unwrap(); - - let bertha_keypair = key::testing::keypair_1(); - let albert_keypair = key::testing::keypair_2(); - - let working_dir = env::current_dir().unwrap(); - - let exchange_one = Exchange { - addr: Address::from_str(BERTHA).unwrap(), - token_buy: Address::from_str(XAN).unwrap(), - token_sell: Address::from_str(BTC).unwrap(), - max_sell: token::Amount::from(100), - min_buy: token::Amount::from(1), - rate_min: DecimalWrapper::from_str("0.1").unwrap(), - vp: Some( - std::fs::read(format!( - "{}/../{}", - working_dir.to_string_lossy(), - VP_ALWAYS_FALSE_WASM - )) - .unwrap(), - ), - }; - let exchange_two = Exchange { - addr: Address::from_str(ALBERT).unwrap(), - token_buy: Address::from_str(BTC).unwrap(), - token_sell: Address::from_str(XAN).unwrap(), - max_sell: token::Amount::from(1), - min_buy: token::Amount::from(100), - rate_min: DecimalWrapper::from_str("10").unwrap(), - vp: Some( - std::fs::read(format!( - "{}/../{}", - working_dir.to_string_lossy(), - VP_ALWAYS_TRUE_WASM - )) - .unwrap(), - ), - }; - - let signed_exchange_one = Signed::new(&bertha_keypair, exchange_one); - let signed_exchange_two = Signed::new(&bertha_keypair, exchange_two); - - let mut it = MatchedExchanges::empty(); - it.exchanges = HashMap::<_, _>::from_iter( - vec![ - (bertha_addr.clone(), signed_exchange_one.clone()), - (albert_addr.clone(), signed_exchange_two.clone()), - ] - .into_iter(), - ); - - it.intents = HashMap::<_, _>::from_iter( - vec![ - ( - bertha_addr.clone(), - Signed::new( - &bertha_keypair, - FungibleTokenIntent { - exchange: HashSet::from_iter(vec![ - signed_exchange_one, - ]), - }, - ), - ), - ( - albert_addr.clone(), - Signed::new( - &albert_keypair, - FungibleTokenIntent { - exchange: HashSet::from_iter(vec![ - signed_exchange_two, - ]), - }, - ), - ), - ] - .into_iter(), - ); - - it.transfers = HashSet::<_>::from_iter( - vec![ - token::Transfer { - source: bertha_addr.clone(), - target: albert_addr.clone(), - token: Address::from_str(BTC).unwrap(), - amount: token::Amount::from(100), - }, - token::Transfer { - source: albert_addr, - target: bertha_addr, - token: Address::from_str(XAN).unwrap(), - amount: token::Amount::from(1), - }, - ] - .into_iter(), - ); - - let encoded_intent_transfer = encode(&it); - let decoded_intent_transfer: MatchedExchanges = - decode(encoded_intent_transfer).unwrap(); - - assert!(decoded_intent_transfer == it); - } - - #[cfg(test)] - #[allow(dead_code)] - mod constants { - - // User addresses - pub const ALBERT: &str = "atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4"; - pub const BERTHA: &str = "atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw"; - pub const CHRISTEL: &str = "atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p"; - - // Fungible token addresses - pub const XAN: &str = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5"; - pub const BTC: &str = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp"; - pub const ETH: &str = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p"; - pub const DOT: &str = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn"; - - // Bite-sized tokens - pub const SCHNITZEL: &str = "atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt"; - pub const APFEL: &str = "atest1v4ehgw36gfryydj9g3p5zv3kg9znyd358ycnzsfcggc5gvecgc6ygs2rxv6ry3zpg4zrwdfeumqcz9"; - pub const KARTOFFEL: &str = "atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90"; - - // Paths to the WASMs used for tests - pub const TX_TRANSFER_WASM: &str = "wasm/tx_transfer.wasm"; - pub const VP_USER_WASM: &str = "wasm/vp_user.wasm"; - pub const TX_NO_OP_WASM: &str = "wasm_for_tests/tx_no_op.wasm"; - pub const VP_ALWAYS_TRUE_WASM: &str = - "wasm_for_tests/vp_always_true.wasm"; - pub const VP_ALWAYS_FALSE_WASM: &str = - "wasm_for_tests/vp_always_false.wasm"; - } -} diff --git a/shared/src/types/matchmaker.rs b/shared/src/types/matchmaker.rs deleted file mode 100644 index 5ee67a83ed..0000000000 --- a/shared/src/types/matchmaker.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Matchmaker types - -use std::collections::HashSet; - -/// A matchmaker marker trait. This should not be implemented manually. Instead, -/// it is added by the derive `Matchmaker` macro, which also adds necessary -/// binding code for matchmaker dylib runner. -pub trait Matchmaker: AddIntent {} - -/// A matchmaker must implement this trait -pub trait AddIntent: Default { - // TODO: For some reason, using `&[u8]` causes the `decode_intent_data` to - // fail decoding - /// Add a new intent to matchmaker's state - #[allow(clippy::ptr_arg)] - fn add_intent( - &mut self, - intent_id: &Vec, - intent_data: &Vec, - ) -> AddIntentResult; -} - -/// The result of calling matchmaker's `add_intent` function -#[derive(Clone, Debug, Default)] -pub struct AddIntentResult { - /// A transaction matched from the intent, if any - pub tx: Option>, - /// The intent IDs that were matched into the tx, if any - pub matched_intents: Option>>, -} diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 4b8d7288ca..240fbdb70c 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -6,10 +6,8 @@ pub mod dylib; pub mod governance; pub mod hash; pub mod ibc; -pub mod intent; pub mod internal; pub mod key; -pub mod matchmaker; pub mod nft; pub mod storage; pub mod time; diff --git a/shared/src/vm/mod.rs b/shared/src/vm/mod.rs index 88c8803836..2e14666f81 100644 --- a/shared/src/vm/mod.rs +++ b/shared/src/vm/mod.rs @@ -1,5 +1,4 @@ -//! Virtual machine modules for running transactions, validity predicates, -//! matchmaker and matchmaker's filter. +//! Virtual machine modules for running transactions and validity predicates. use std::ffi::c_void; use std::marker::PhantomData; diff --git a/shared/src/vm/types.rs b/shared/src/vm/types.rs index c2a9ef11cb..480190ad08 100644 --- a/shared/src/vm/types.rs +++ b/shared/src/vm/types.rs @@ -29,9 +29,6 @@ pub struct VpInput<'a> { pub verifiers: &'a BTreeSet
, } -/// Input for matchmaker wasm module call -pub type MatchmakerInput = Vec; - /// Key-value pair represents data from account's subspace #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct KeyVal { diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 4ae17e8fff..cc07e3704f 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -38,7 +38,6 @@ file-serve = "0.2.0" fs_extra = "1.2.0" hex = "0.4.3" itertools = "0.10.0" -libp2p = "0.38.0" pretty_assertions = "0.7.2" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} diff --git a/tests/src/e2e.rs b/tests/src/e2e.rs index a9eb2b2cf5..0afc343098 100644 --- a/tests/src/e2e.rs +++ b/tests/src/e2e.rs @@ -12,7 +12,6 @@ //! `ANOMA_E2E_KEEP_TEMP=true`. pub mod eth_bridge_tests; -pub mod gossip_tests; pub mod helpers; pub mod ledger_tests; pub mod setup; diff --git a/tests/src/e2e/gossip_tests.rs b/tests/src/e2e/gossip_tests.rs deleted file mode 100644 index 5da19c0758..0000000000 --- a/tests/src/e2e/gossip_tests.rs +++ /dev/null @@ -1,348 +0,0 @@ -//! By default, these tests will run in release mode. This can be disabled -//! by setting environment variable `ANOMA_E2E_DEBUG=true`. For debugging, -//! you'll typically also want to set `RUST_BACKTRACE=1`, e.g.: -//! -//! ```ignore,shell -//! ANOMA_E2E_DEBUG=true RUST_BACKTRACE=1 cargo test e2e::gossip_tests -- --test-threads=1 --nocapture -//! ``` -//! -//! To keep the temporary files created by a test, use env var -//! `ANOMA_E2E_KEEP_TEMP=true`. - -use std::env; -use std::fs::OpenOptions; -use std::path::PathBuf; - -use color_eyre::eyre::Result; -use escargot::CargoBuild; -use serde_json::json; -use setup::constants::*; - -use super::setup::ENV_VAR_DEBUG; -use crate::e2e::helpers::{ - find_address, get_actor_rpc, get_gossiper_mm_server, -}; -use crate::e2e::setup::{self, Bin, Who}; -use crate::{run, run_as}; - -/// Test that when we "run-gossip" a peer with no seeds should fail -/// bootstrapping kademlia. A peer with a seed should be able to -/// bootstrap kademia and connect to the other peer. -/// In this test we: -/// 1. Check that a gossip node can start and stop cleanly -/// 2. Check that two peers connected to the same seed node discover each other -#[test] -#[ignore] // this is not currently being developed, run with `cargo test -- --ignored` -fn run_gossip() -> Result<()> { - let test = - setup::network(|genesis| setup::add_validators(2, genesis), None)?; - - // 1. Start the first gossip node and then stop it - let mut node_0 = - run_as!(test, Who::Validator(0), Bin::Node, &["gossip"], Some(40))?; - node_0.send_control('c')?; - node_0.exp_eof()?; - drop(node_0); - - // 2. Check that two peers connected to the same seed node discover each - // other. Start the first gossip node again (the seed node). - let mut node_0 = - run_as!(test, Who::Validator(0), Bin::Node, &["gossip"], Some(40))?; - let (_unread, matched) = node_0.exp_regex(r"Peer id: PeerId\(.*\)")?; - let node_0_peer_id = matched - .trim() - .rsplit_once('\"') - .unwrap() - .0 - .rsplit_once('\"') - .unwrap() - .1; - let _bg_node_0 = node_0.background(); - - // Start the second gossip node (a peer node) - let mut node_1 = - run_as!(test, Who::Validator(1), Bin::Node, &["gossip"], Some(40))?; - - let (_unread, matched) = node_1.exp_regex(r"Peer id: PeerId\(.*\)")?; - let node_1_peer_id = matched - .trim() - .rsplit_once('\"') - .unwrap() - .0 - .rsplit_once('\"') - .unwrap() - .1; - node_1.exp_string(&format!( - "Connect to a new peer: PeerId(\"{}\")", - node_0_peer_id - ))?; - let _bg_node_1 = node_1.background(); - - // Start the third gossip node (another peer node) - let mut node_2 = - run_as!(test, Who::Validator(2), Bin::Node, &["gossip"], Some(20))?; - // The third node should connect to node 1 via Identify and Kademlia peer - // discovery protocol - node_2.exp_string(&format!( - "Connect to a new peer: PeerId(\"{}\")", - node_1_peer_id - ))?; - node_2.exp_string(&format!("Identified Peer {}", node_1_peer_id))?; - node_2 - .exp_string(&format!("Routing updated peer ID: {}", node_1_peer_id))?; - - Ok(()) -} - -/// This test runs a ledger node and 2 gossip nodes. It then crafts 3 intents -/// and sends them to the matchmaker. The matchmaker should be able to match -/// them into a transfer transaction and submit it to the ledger. -#[test] -#[ignore] // this is not currently being developed, run with `cargo test -- --ignored` -fn match_intents() -> Result<()> { - let test = setup::single_node_net()?; - - // Make sure that the default matchmaker is built - println!("Building the matchmaker \"mm_token_exch\" implementation..."); - let run_debug = match env::var(ENV_VAR_DEBUG) { - Ok(val) => val.to_ascii_lowercase() != "false", - _ => false, - }; - let manifest_path = test - .working_dir - .join("matchmaker") - .join("mm_token_exch") - .join("Cargo.toml"); - let cmd = CargoBuild::new().manifest_path(manifest_path); - let cmd = if run_debug { cmd } else { cmd.release() }; - let msgs = cmd.exec().unwrap(); - for msg in msgs { - msg.unwrap(); - } - println!("Done building the matchmaker."); - - let mut ledger = - run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Anoma ledger node started")?; - ledger.exp_string("No state could be found")?; - // Wait to commit a block - ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; - let bg_ledger = ledger.background(); - - let intent_a_path_input = test.test_dir.path().join("intent.A.data"); - let intent_b_path_input = test.test_dir.path().join("intent.B.data"); - let intent_c_path_input = test.test_dir.path().join("intent.C.data"); - - let albert = find_address(&test, ALBERT)?; - let bertha = find_address(&test, BERTHA)?; - let christel = find_address(&test, CHRISTEL)?; - let xan = find_address(&test, XAN)?; - let btc = find_address(&test, BTC)?; - let eth = find_address(&test, ETH)?; - let intent_a_json = json!([ - { - "key": bertha, - "addr": bertha, - "min_buy": "100.0", - "max_sell": "70", - "token_buy": xan, - "token_sell": btc, - "rate_min": "2", - "vp_path": test.working_dir.join(VP_ALWAYS_TRUE_WASM).to_string_lossy().into_owned(), - } - ]); - - let intent_b_json = json!([ - { - "key": albert, - "addr": albert, - "min_buy": "50", - "max_sell": "300", - "token_buy": btc, - "token_sell": eth, - "rate_min": "0.7" - } - ]); - let intent_c_json = json!([ - { - "key": christel, - "addr": christel, - "min_buy": "20", - "max_sell": "200", - "token_buy": eth, - "token_sell": xan, - "rate_min": "0.5" - } - ]); - generate_intent_json(intent_a_path_input.clone(), intent_a_json); - generate_intent_json(intent_b_path_input.clone(), intent_b_json); - generate_intent_json(intent_c_path_input.clone(), intent_c_json); - - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - let validator_one_gossiper = - get_gossiper_mm_server(&test, &Who::Validator(0)); - - // The RPC port starts at 27660 (see `setup::network`) - let rpc_port = 27660; - let rpc_address = format!("127.0.0.1:{}", rpc_port); - - // Start intent gossiper node - let mut gossiper = run_as!( - test, - Who::Validator(0), - Bin::Node, - &["gossip", "--rpc", &rpc_address], - Some(20) - )?; - - // Wait gossip to start - gossiper.exp_string(&format!("RPC started at {}", rpc_address))?; - let _bg_gossiper = gossiper.background(); - - // Start matchmaker - let mut matchmaker = run_as!( - test, - Who::Validator(0), - Bin::Node, - &[ - "matchmaker", - "--source", - "matchmaker", - "--signing-key", - "matchmaker-key", - "--ledger-address", - &validator_one_rpc, - "--intent-gossiper", - &validator_one_gossiper, - ], - Some(40) - )?; - - // Wait for the matchmaker to start - matchmaker.exp_string("Connected to the server")?; - let bg_matchmaker = matchmaker.background(); - - let rpc_address = format!("http://{}", rpc_address); - // Send intent A - let mut session_send_intent_a = run!( - test, - Bin::Client, - &[ - "intent", - "--node", - &rpc_address, - "--data-path", - intent_a_path_input.to_str().unwrap(), - "--topic", - "asset_v1", - "--signing-key", - BERTHA_KEY, - "--ledger-address", - &validator_one_rpc - ], - Some(40), - )?; - - // means it sent it correctly but not able to gossip it (which is - // correct since there is only 1 node) - session_send_intent_a.exp_string( - "Failed to publish intent in gossiper: InsufficientPeers", - )?; - drop(session_send_intent_a); - - let mut matchmaker = bg_matchmaker.foreground(); - matchmaker.exp_string("trying to match new intent")?; - let bg_matchmaker = matchmaker.background(); - - // Send intent B - let mut session_send_intent_b = run!( - test, - Bin::Client, - &[ - "intent", - "--node", - &rpc_address, - "--data-path", - intent_b_path_input.to_str().unwrap(), - "--topic", - "asset_v1", - "--signing-key", - ALBERT_KEY, - "--ledger-address", - &validator_one_rpc - ], - Some(40), - )?; - - // means it sent it correctly but not able to gossip it (which is - // correct since there is only 1 node) - session_send_intent_b.exp_string( - "Failed to publish intent in gossiper: InsufficientPeers", - )?; - drop(session_send_intent_b); - - let mut matchmaker = bg_matchmaker.foreground(); - matchmaker.exp_string("trying to match new intent")?; - let bg_matchmaker = matchmaker.background(); - - // Send intent C - let mut session_send_intent_c = run!( - test, - Bin::Client, - &[ - "intent", - "--node", - &rpc_address, - "--data-path", - intent_c_path_input.to_str().unwrap(), - "--topic", - "asset_v1", - "--signing-key", - CHRISTEL_KEY, - "--ledger-address", - &validator_one_rpc - ], - Some(40), - )?; - - // means it sent it correctly but not able to gossip it (which is - // correct since there is only 1 node) - session_send_intent_c.exp_string( - "Failed to publish intent in gossiper: InsufficientPeers", - )?; - drop(session_send_intent_c); - - // check that the transfers transactions are correct - let mut matchmaker = bg_matchmaker.foreground(); - matchmaker.exp_string(&format!( - "crafting transfer: {}, {}, 70", - bertha, albert - ))?; - matchmaker.exp_string(&format!( - "crafting transfer: {}, {}, 200", - christel, bertha - ))?; - matchmaker.exp_string(&format!( - "crafting transfer: {}, {}, 100", - albert, christel - ))?; - - // check that the all VPs accept the transaction - let mut ledger = bg_ledger.foreground(); - ledger.exp_string("all VPs accepted transaction")?; - - Ok(()) -} - -fn generate_intent_json( - intent_path: PathBuf, - exchange_json: serde_json::Value, -) { - let intent_writer = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(intent_path) - .unwrap(); - serde_json::to_writer(intent_writer, &exchange_json).unwrap(); -} diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index cc0c45cf8c..705c822760 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -42,7 +42,7 @@ pub fn find_address(test: &Test, alias: impl AsRef) -> Result
{ Ok(address) } -/// Find the address of the intent gossiper node's RPC endpoint. +/// Find the address of the node's RPC endpoint. pub fn get_actor_rpc(test: &Test, who: &Who) -> String { let base_dir = test.get_base_dir(who); let tendermint_mode = match who { @@ -54,18 +54,6 @@ pub fn get_actor_rpc(test: &Test, who: &Who) -> String { config.ledger.tendermint.rpc_address.to_string() } -/// Find the address of the intent gossiper node's matchmakers server. -pub fn get_gossiper_mm_server(test: &Test, who: &Who) -> String { - let base_dir = test.get_base_dir(who); - let tendermint_mode = match who { - Who::NonValidator => TendermintMode::Full, - Who::Validator(_) => TendermintMode::Validator, - }; - let config = - Config::load(&base_dir, &test.net.chain_id, Some(tendermint_mode)); - config.intent_gossiper.matchmakers_server_addr.to_string() -} - /// Find the address of an account by its alias from the wallet #[allow(dead_code)] pub fn find_keypair( diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 6499bf9806..d1df560108 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -65,21 +65,12 @@ pub fn add_validators(num: u8, mut genesis: GenesisConfig) -> GenesisConfig { let validator_0 = genesis.validator.get_mut("validator-0").unwrap(); // Clone the first validator before modifying it let other_validators = validator_0.clone(); - // Set the first validator to be a bootstrap node to enable P2P connectivity - validator_0.intent_gossip_seed = Some(true); - // A bootstrap node doesn't participate in the gossipsub protocol for - // gossiping intents, so we remove its matchmaker - validator_0.matchmaker_account = None; - validator_0.matchmaker_code = None; - validator_0.matchmaker_tx = None; let net_address_0 = SocketAddr::from_str(validator_0.net_address.as_ref().unwrap()) .unwrap(); let net_address_port_0 = net_address_0.port(); for ix in 0..num { let mut validator = other_validators.clone(); - // Only the first validator is bootstrap - validator.intent_gossip_seed = None; let mut net_address = net_address_0; // 6 ports for each validator let first_port = net_address_port_0 + 6 * (ix as u16 + 1); @@ -769,7 +760,6 @@ pub mod constants { pub const CHRISTEL: &str = "Christel"; pub const CHRISTEL_KEY: &str = "Christel-key"; pub const DAEWON: &str = "Daewon"; - pub const MATCHMAKER_KEY: &str = "matchmaker-key"; // Native VP aliases pub const GOVERNANCE_ADDRESS: &str = "governance"; diff --git a/vm_env/src/intent.rs b/vm_env/src/intent.rs deleted file mode 100644 index 226cb708db..0000000000 --- a/vm_env/src/intent.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::collections::HashSet; - -use namada::proto::Signed; -use namada::types::intent; -use namada::types::key::*; - -/// Tx imports and functions. -pub mod tx { - pub use namada::types::intent::*; - - use super::*; - pub fn invalidate_exchange(intent: &Signed) { - use crate::imports::tx; - let key = intent::invalid_intent_key(&intent.data.addr); - let mut invalid_intent: HashSet = - tx::read(&key.to_string()).unwrap_or_default(); - invalid_intent.insert(intent.sig.clone()); - tx::write(&key.to_string(), &invalid_intent) - } -} - -/// Vp imports and functions. -pub mod vp { - pub use namada::types::intent::*; - - use super::*; - - pub fn vp_exchange(intent: &Signed) -> bool { - use crate::imports::vp; - let key = intent::invalid_intent_key(&intent.data.addr); - - let invalid_intent_pre: HashSet = - vp::read_pre(&key.to_string()).unwrap_or_default(); - let invalid_intent_post: HashSet = - vp::read_post(&key.to_string()).unwrap_or_default(); - !invalid_intent_pre.contains(&intent.sig) - && invalid_intent_post.contains(&intent.sig) - } -} diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 578079d695..0da9ae9804 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -9,7 +9,6 @@ pub mod governance; pub mod ibc; pub mod imports; -pub mod intent; pub mod key; pub mod nft; pub mod proof_of_stake; @@ -29,7 +28,6 @@ pub mod tx_prelude { pub use crate::governance::tx as governance; pub use crate::ibc::{Ibc, IbcActions}; pub use crate::imports::tx::*; - pub use crate::intent::tx as intent; pub use crate::nft::tx as nft; pub use crate::proof_of_stake::{self, PoS, PosRead, PosWrite}; pub use crate::token::tx as token; @@ -48,7 +46,6 @@ pub mod vp_prelude { pub use namada_macros::validity_predicate; pub use crate::imports::vp::*; - pub use crate::intent::vp as intent; pub use crate::key::vp as key; pub use crate::nft::vp as nft; pub use crate::token::vp as token; diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index 3a674ad97b..8929992754 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -1,7 +1,5 @@ #[cfg(feature = "tx_bond")] pub mod tx_bond; -#[cfg(feature = "tx_from_intent")] -pub mod tx_from_intent; #[cfg(feature = "tx_ibc")] pub mod tx_ibc; #[cfg(feature = "tx_init_account")] diff --git a/wasm/wasm_source/src/tx_from_intent.rs b/wasm/wasm_source/src/tx_from_intent.rs deleted file mode 100644 index e39963fae7..0000000000 --- a/wasm/wasm_source/src/tx_from_intent.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! A tx for a token transfer crafted by matchmaker from intents. -//! This tx uses `intent::IntentTransfers` wrapped inside -//! `SignedTxData` as its input as declared in `shared` crate. - -use namada_tx_prelude::*; - -#[transaction] -fn apply_tx(tx_data: Vec) { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - - let tx_data = - intent::IntentTransfers::try_from_slice(&signed.data.unwrap()[..]); - - let tx_data = tx_data.unwrap(); - - // make sure that the matchmaker has to validate this tx - insert_verifier(&tx_data.source); - - for token::Transfer { - source, - target, - token, - amount, - } in tx_data.matches.transfers - { - token::transfer(&source, &target, &token, amount); - } - - tx_data - .matches - .exchanges - .values() - .into_iter() - .for_each(intent::invalidate_exchange); -} diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index a222a344ef..fbd606b182 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -6,24 +6,15 @@ //! It allows to bond, unbond and withdraw tokens to and from PoS system with a //! valid signature. //! -//! It allows to fulfil intents that were signed by this account's key if they -//! haven't already been fulfilled (fulfilled intents are added to the owner's -//! invalid intent set). -//! //! Any other storage key changes are allowed only with a valid signature. -use namada_vp_prelude::intent::{ - Exchange, FungibleTokenIntent, IntentTransfers, -}; use namada_vp_prelude::storage::KeySeg; use namada_vp_prelude::*; use once_cell::unsync::Lazy; -use rust_decimal::prelude::*; enum KeyType<'a> { Token(&'a Address), PoS, - InvalidIntentSet(&'a Address), Nft(&'a Address), Vp(&'a Address), GovernanceVote(&'a Address), @@ -36,8 +27,6 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { Self::Token(address) } else if proof_of_stake::is_pos_key(key) { Self::PoS - } else if let Some(address) = intent::is_invalid_intent_key(key) { - Self::InvalidIntentSet(address) } else if let Some(address) = nft::is_nft_key(key) { Self::Nft(address) } else if gov_storage::is_vote_key(key) { @@ -83,11 +72,6 @@ fn validate_tx( _ => false, }); - let valid_intent = Lazy::new(|| match &*signed_tx_data { - Ok(signed_tx_data) => check_intent_transfers(&addr, signed_tx_data), - _ => false, - }); - if !is_tx_whitelisted() { return false; } @@ -103,14 +87,13 @@ fn validate_tx( read_post(&key).unwrap_or_default(); let change = post.change() - pre.change(); // debit has to signed, credit doesn't - let valid = change >= 0 || *valid_sig || *valid_intent; + let valid = change >= 0 || *valid_sig; debug_log!( - "token key: {}, change: {}, valid_sig: {}, \ - valid_intent: {}, valid modification: {}", + "token key: {}, change: {}, valid_sig: {}, valid \ + modification: {}", key, change, *valid_sig, - *valid_intent, valid ); valid @@ -148,27 +131,6 @@ fn validate_tx( ); valid } - KeyType::InvalidIntentSet(owner) => { - if owner == &addr { - let key = key.to_string(); - let pre: HashSet = - read_pre(&key).unwrap_or_default(); - let post: HashSet = - read_post(&key).unwrap_or_default(); - // A new invalid intent must have been added - pre.len() + 1 == post.len() - } else { - debug_log!( - "This address ({}) is not of owner ({}) of \ - InvalidIntentSet key: {}", - addr, - owner, - key - ); - // If this is not the owner, allow any change - true - } - } KeyType::Nft(owner) => { if owner == &addr { *valid_sig @@ -218,148 +180,6 @@ fn validate_tx( true } -fn check_intent_transfers( - addr: &Address, - signed_tx_data: &SignedTxData, -) -> bool { - if let Some((raw_intent_transfers, exchange, intent)) = - try_decode_intent(addr, signed_tx_data) - { - log_string("check intent"); - return check_intent(addr, exchange, intent, raw_intent_transfers); - } - false -} - -fn try_decode_intent( - addr: &Address, - signed_tx_data: &SignedTxData, -) -> Option<( - Vec, - namada_vp_prelude::Signed, - namada_vp_prelude::Signed, -)> { - let raw_intent_transfers = signed_tx_data.data.as_ref().cloned()?; - let mut tx_data = - IntentTransfers::try_from_slice(&raw_intent_transfers[..]).ok()?; - debug_log!( - "tx_data.matches.exchanges: {:?}, {}", - tx_data.matches.exchanges, - &addr - ); - if let (Some(exchange), Some(intent)) = ( - tx_data.matches.exchanges.remove(addr), - tx_data.matches.intents.remove(addr), - ) { - return Some((raw_intent_transfers, exchange, intent)); - } else { - log_string("no intent with a matching address"); - } - None -} - -fn check_intent( - addr: &Address, - exchange: namada_vp_prelude::Signed, - intent: namada_vp_prelude::Signed, - raw_intent_transfers: Vec, -) -> bool { - // verify signature - let pk = key::get(addr); - if let Some(pk) = pk { - if intent.verify(&pk).is_err() { - log_string("invalid sig"); - return false; - } - } else { - return false; - } - - // verify the intent have not been already used - if !intent::vp_exchange(&exchange) { - return false; - } - - // verify the intent is fulfilled - let Exchange { - addr, - token_sell, - rate_min, - token_buy, - min_buy, - max_sell, - vp, - } = &exchange.data; - - debug_log!("vp is: {}", vp.is_some()); - - if let Some(code) = vp { - let eval_result = eval(code.to_vec(), raw_intent_transfers); - debug_log!("eval result: {}", eval_result); - if !eval_result { - return false; - } - } - - debug_log!( - "exchange description: {}, {}, {}, {}, {}", - token_sell, - token_buy, - max_sell.change(), - min_buy.change(), - rate_min.0 - ); - - let token_sell_key = token::balance_key(token_sell, addr).to_string(); - let mut sell_difference: token::Amount = - read_pre(&token_sell_key).unwrap_or_default(); - let sell_post: token::Amount = - read_post(token_sell_key).unwrap_or_default(); - - sell_difference.spend(&sell_post); - - let token_buy_key = token::balance_key(token_buy, addr).to_string(); - let buy_pre: token::Amount = read_pre(&token_buy_key).unwrap_or_default(); - let mut buy_difference: token::Amount = - read_post(token_buy_key).unwrap_or_default(); - - buy_difference.spend(&buy_pre); - - let sell_diff: Decimal = sell_difference.change().into(); // -> how many token I sold - let buy_diff: Decimal = buy_difference.change().into(); // -> how many token I got - - debug_log!( - "buy_diff > 0: {}, rate check: {}, max_sell > sell_diff: {}, buy_diff \ - > min_buy: {}", - buy_difference.change() > 0, - buy_diff / sell_diff >= rate_min.0, - max_sell.change() >= sell_difference.change(), - buy_diff >= min_buy.change().into() - ); - - if !(buy_difference.change() > 0 - && (buy_diff / sell_diff >= rate_min.0) - && max_sell.change() >= sell_difference.change() - && buy_diff >= min_buy.change().into()) - { - debug_log!( - "invalid exchange, {} / {}, sell diff: {}, buy diff: {}, \ - max_sell: {}, rate_min: {}, min_buy: {}, buy_diff / sell_diff: {}", - token_sell, - token_buy, - sell_difference.change(), - buy_difference.change(), - max_sell.change(), - rate_min.0, - min_buy.change(), - buy_diff / sell_diff - ); - false - } else { - true - } -} - #[cfg(test)] mod tests { use address::testing::arb_non_internal_address; diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 9df5195b2f..876455fcaa 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1357,7 +1357,7 @@ dependencies = [ [[package]] name = "libsecp256k1" version = "0.7.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", @@ -1373,7 +1373,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1383,7 +1383,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1527,7 +1527,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1584,7 +1584,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -1593,7 +1593,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1611,7 +1611,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1619,7 +1619,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -1629,7 +1629,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1637,7 +1637,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "getrandom", From 40836504fc64fadaeaf1245812791817c18f5a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Sep 2022 11:09:26 +0200 Subject: [PATCH 102/197] changelog: add #500 --- .../unreleased/miscellaneous/493-remove-intent-gossiper.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/miscellaneous/493-remove-intent-gossiper.md diff --git a/.changelog/unreleased/miscellaneous/493-remove-intent-gossiper.md b/.changelog/unreleased/miscellaneous/493-remove-intent-gossiper.md new file mode 100644 index 0000000000..543edeb6aa --- /dev/null +++ b/.changelog/unreleased/miscellaneous/493-remove-intent-gossiper.md @@ -0,0 +1,2 @@ +- Removed intent gossiper and matchmaker code + ([#493](https://github.com/anoma/namada/issues/493)) \ No newline at end of file From 4c0e7e895e5f637d4821f2d14e88b419bf4dde85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Sep 2022 11:36:23 +0200 Subject: [PATCH 103/197] update wasm checksums --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 898f6e9763..e222bd6aef 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.16097490afa7378c79e6216751b20796cde3a9026c34255c3f1e5ec5a4c9482e.wasm", - "tx_from_intent.wasm": "tx_from_intent.f8d1937b17a3abaf7ea595526c870b3d57ddef8e0c1bc96f8e0a448864b186c7.wasm", - "tx_ibc.wasm": "tx_ibc.378b10551c0b22c2c892d24e2676ee5160d654e2e53a50e7925e0f2c6321497b.wasm", - "tx_init_account.wasm": "tx_init_account.adab66c2b4d635e9c42133936aafb143363f91dddff2a60f94df504ffec951a6.wasm", - "tx_init_nft.wasm": "tx_init_nft.d1065ebd80ba6ea97f29bc2268becf9ba3ba2952641992464f3e9e868df17447.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.184131576a579f9ece96460d1eb20e5970fcd149b0527c8e56b711e5c535aa5f.wasm", - "tx_init_validator.wasm": "tx_init_validator.2990747d24d467b56e19724c5d13df826a3aab83f7e1bf26558dbdf44e260f8a.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.33db14dea4a03ff7508ca44f3ae956d83c0abceb3dae5be844668e54ac22b273.wasm", - "tx_transfer.wasm": "tx_transfer.a601d62296f56f6b4dabb0a2ad082478d195e667c7469f363bdfd5fe41349bd8.wasm", - "tx_unbond.wasm": "tx_unbond.014cbf5b0aa3ac592c0a6940dd502ec8569a3af4d12782e3a5931c15dc13042f.wasm", - "tx_update_vp.wasm": "tx_update_vp.83d4caeb5a9ca3009cd899810493a6b87b4c07fa9ed36f297db99dc881fb9a1c.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.bcb5280be9dfeed0a7650ba5e4a3cebc2c19b76780fd74dcb345be3da766b64a.wasm", - "tx_withdraw.wasm": "tx_withdraw.8fc0a3439ee9ae66047c520519877bc1f540e0cb02abfa31afa8cce8cd069b6f.wasm", - "vp_nft.wasm": "vp_nft.2c820c728d241b82bf0ed3c552ee9e7c046bceaa4f7b6f12d3236a1a3d7c1589.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.6e762f3fda8aa7a252e2b29a4a312db91ded062d6c18b8b489883733c89dc227.wasm", - "vp_token.wasm": "vp_token.c45cc3848f12fc47713702dc206d1312ad740a6bbee7f141556714f6f89d4985.wasm", - "vp_user.wasm": "vp_user.d6cd2f4b5bc26f96df6aa300fddf4d25e1656920d59896209bd54ae8d407ecde.wasm" + "tx_bond.wasm": "tx_bond.3955960def0f45b2c7eda810354716b29cf7063891cf23bf001f067220b348a5.wasm", + "tx_from_intent.wasm": "tx_from_intent.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", + "tx_ibc.wasm": "tx_ibc.1b6ee957fb5a8ad75bdbdf5435014dcd1b9bc4dc0d749d9401a9814ddaf4c55a.wasm", + "tx_init_account.wasm": "tx_init_account.273fadc04419989eaecdbcce8efac0ee40223f1850c55a29228afd6fa5e0abce.wasm", + "tx_init_nft.wasm": "tx_init_nft.2ef0c7cb42266494a37c5de05cf912eca777c76f8cedbe1164f13cbaaf490cd9.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.c6426e5f9bc06c01f331f8399f69c131287c707a33e78d8c8c57dfce435d9150.wasm", + "tx_init_validator.wasm": "tx_init_validator.dde9e2b6f7bea6e56326667becc97a3ab863a15a44528550536922bb4550cba2.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.db1c11ed264dcce8b0a3218cecabc4af8832a22891d9c67fa6f92be78fa861fb.wasm", + "tx_transfer.wasm": "tx_transfer.6deab964149869ec638777a344c5fb1ad486c4f44c731d89793d5940fc3d8714.wasm", + "tx_unbond.wasm": "tx_unbond.57286cab933765181af77672fad7f69c373708e459a01d48631f549c693399b9.wasm", + "tx_update_vp.wasm": "tx_update_vp.dd4b87f25b3d866ef6a7c30ea3cba743e2c011fa5c44ece7a53cff6ad682d975.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.a18464e5966177552fc237c07111bbdd0d70c2940e5e761b2f80c217f4fa32db.wasm", + "tx_withdraw.wasm": "tx_withdraw.fe25543edec0ef74eadcd1de2f354e32002f8a252e4232ed2be4e0340675646f.wasm", + "vp_nft.wasm": "vp_nft.70446e909b233f60beda248085d7de9ba7822d81e580e220bda28764975ace54.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.af177f0935c903c8b5c49305b759e48131cbf8032b6d409216ee74a12a6903b2.wasm", + "vp_token.wasm": "vp_token.3511bed65a7c98fea300cebafed28623ba776da488d06e285f0bf168d4bf1fdf.wasm", + "vp_user.wasm": "vp_user.5748f41419ac1b2eab918ffbe4e7f68125e1cdfe9537ffbcbcc2785c3030d0dd.wasm" } \ No newline at end of file From dad616b883c76a9ede8ea0275fdae8cfdbe1089f Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 14 Sep 2022 20:07:58 +0200 Subject: [PATCH 104/197] WIP: StateMachine tests for lazy_vec validation update Cargo.lock --- .../storage_api/collections/lazy_vec.rs | 322 +-------- shared/src/types/storage.rs | 11 +- .../storage_api/collections/lazy_vec.txt | 7 + tests/src/lib.rs | 2 + tests/src/storage_api/collections/lazy_vec.rs | 631 ++++++++++++++++++ tests/src/storage_api/collections/mod.rs | 1 + tests/src/storage_api/mod.rs | 1 + tests/src/vm_host_env/tx.rs | 25 + 8 files changed, 684 insertions(+), 316 deletions(-) create mode 100644 tests/proptest-regressions/storage_api/collections/lazy_vec.txt create mode 100644 tests/src/storage_api/collections/lazy_vec.rs create mode 100644 tests/src/storage_api/collections/mod.rs create mode 100644 tests/src/storage_api/mod.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index 79967cb9c3..d86f33ec08 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -55,7 +55,7 @@ pub enum SubKeyWithData { /// Possible actions that can modify a [`LazyVec`]. This roughly corresponds to /// the methods that have `StorageWrite` access. -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum Action { /// Push a value `T` into a [`LazyVec`] Push(T), @@ -86,10 +86,10 @@ pub enum ValidationError { #[error("Pop at a wrong index. Got {got}, expected {expected}.")] UnexpectedPopIndex { got: Index, expected: Index }, #[error( - "Update (combination of pop and push) at a wrong index. Got {got}, \ - expected {expected}." + "Update (or a combination of pop and push) at a wrong index. Got \ + {got}, expected maximum {max}." )] - UnexpectedUpdateIndex { got: Index, expected: Index }, + UnexpectedUpdateIndex { got: Index, max: Index }, #[error("An index has overflown its representation: {0}")] IndexOverflow(>::Error), #[error("Unexpected underflow in `{0} - {0}`")] @@ -323,8 +323,8 @@ where let builder = builder.get_or_insert(ValidationBuilder::default()); builder.changes.push(change); - return Ok(true); } + return Ok(true); } Ok(false) } @@ -474,34 +474,14 @@ impl ValidationBuilder { } } - // And finally iterate updates in increasing order of indices - let mut last_updated = Option::None; + // And finally iterate updates for index in updated { - if let Some(last_updated) = last_updated { - // Following additions should be at monotonically increasing - // indices - let expected = last_updated + 1; - if expected != index { - return Err(ValidationError::UnexpectedUpdateIndex { - got: index, - expected, - }); - } - } - last_updated = Some(index); - } - if let Some(index) = last_updated { - let expected = len_pre.checked_sub(deleted_len).ok_or( - ValidationError::UnexpectedUnderflow(len_pre, deleted_len), - )?; - if index != expected { - // The last update must be at the pre length value minus - // deleted_len. - // If something is added and then deleted in a - // single tx, it will never be visible here. + // Update index has to be within the length bounds + let max = len_pre + len_diff; + if index >= max { return Err(ValidationError::UnexpectedUpdateIndex { got: index, - expected: len_pre, + max, }); } } @@ -512,12 +492,6 @@ impl ValidationBuilder { #[cfg(test)] mod test { - use proptest::prelude::*; - use proptest::prop_state_machine; - use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; - use proptest::test_runner::Config; - use test_log::test; - use super::*; use crate::ledger::storage::testing::TestStorage; @@ -556,280 +530,4 @@ mod test { Ok(()) } - - prop_state_machine! { - #![proptest_config(Config { - // Instead of the default 256, we only run 5 because otherwise it - // takes too long and it's preferable to crank up the number of - // transitions instead, to allow each case to run for more epochs as - // some issues only manifest once the model progresses further. - // Additionally, more cases will be explored every time this test is - // executed in the CI. - cases: 5, - .. Config::default() - })] - #[test] - /// A `StateMachineTest` implemented on `LazyVec` that manipulates - /// it with `Transition`s and checks its state against an in-memory - /// `std::collections::Vec`. - fn lazy_vec_api_state_machine_test(sequential 1..100 => ConcreteLazyVecState); - - } - - /// Some borsh-serializable type with arbitrary fields to be used inside - /// LazyVec state machine test - #[derive( - Clone, - Debug, - BorshSerialize, - BorshDeserialize, - PartialEq, - Eq, - PartialOrd, - Ord, - )] - struct TestVecItem { - x: u64, - y: bool, - } - - #[derive(Debug)] - struct ConcreteLazyVecState { - // The eager vec in `AbstractLazyVecState` is not visible in `impl - // StateMachineTest for ConcreteLazyVecState`, it's only used to drive - // transition generation, so we duplicate it here and apply the - // transitions on it the same way (with - // `fn apply_transition_on_eager_vec`) - eager_vec: Vec, - lazy_vec: LazyVec, - storage: TestStorage, - } - - #[derive(Clone, Debug)] - struct AbstractLazyVecState(Vec); - - /// Possible transitions that can modify a [`LazyVec`]. This roughly - /// corresponds to the methods that have `StorageWrite` access and is very - /// similar to [`Action`] - #[derive(Clone, Debug)] - pub enum Transition { - /// Push a value `T` into a [`LazyVec`] - Push(T), - /// Pop a value from a [`LazyVec`] - Pop, - /// Update a value `T` at index from pre to post state in a - /// [`LazyVec`] - Update { - /// index at which the value is updated - index: Index, - /// value to update the element to - value: T, - }, - } - - impl AbstractStateMachine for AbstractLazyVecState { - type State = Self; - type Transition = Transition; - - fn init_state() -> BoxedStrategy { - Just(Self(vec![])).boxed() - } - - // Apply a random transition to the state - fn transitions(state: &Self::State) -> BoxedStrategy { - if state.0.is_empty() { - prop_oneof![arb_test_vec_item().prop_map(Transition::Push)] - .boxed() - } else { - let indices: Vec = - (0_usize..state.0.len()).map(|ix| ix as Index).collect(); - let arb_index = proptest::sample::select(indices); - prop_oneof![ - Just(Transition::Pop), - arb_test_vec_item().prop_map(Transition::Push), - (arb_index, arb_test_vec_item()).prop_map( - |(index, value)| Transition::Update { index, value } - ) - ] - .boxed() - } - } - - fn apply_abstract( - mut state: Self::State, - transition: &Self::Transition, - ) -> Self::State { - apply_transition_on_eager_vec(&mut state.0, transition); - state - } - - fn preconditions( - state: &Self::State, - transition: &Self::Transition, - ) -> bool { - if state.0.is_empty() { - // Ensure that the pop or update transitions are not applied to - // an empty state - !matches!( - transition, - Transition::Pop | Transition::Update { .. } - ) - } else if let Transition::Update { index, .. } = transition { - // Ensure that the update index is a valid one - *index < (state.0.len() - 1) as Index - } else { - true - } - } - } - - impl StateMachineTest for ConcreteLazyVecState { - type Abstract = AbstractLazyVecState; - type ConcreteState = Self; - - fn init_test( - _initial_state: ::State, - ) -> Self::ConcreteState { - Self { - eager_vec: vec![], - lazy_vec: LazyVec::open( - storage::Key::parse("key_path/arbitrary").unwrap(), - ), - storage: TestStorage::default(), - } - } - - fn apply_concrete( - mut state: Self::ConcreteState, - transition: ::Transition, - ) -> Self::ConcreteState { - // Transition application on lazy vec and post-conditions: - match dbg!(&transition) { - Transition::Push(value) => { - let old_len = state.lazy_vec.len(&state.storage).unwrap(); - - state - .lazy_vec - .push(&mut state.storage, value.clone()) - .unwrap(); - - // Post-conditions: - let new_len = state.lazy_vec.len(&state.storage).unwrap(); - let stored_value = state - .lazy_vec - .get(&state.storage, new_len - 1) - .unwrap() - .unwrap(); - assert_eq!( - &stored_value, value, - "the new item must be added to the back" - ); - assert_eq!(old_len + 1, new_len, "length must increment"); - } - Transition::Pop => { - let old_len = state.lazy_vec.len(&state.storage).unwrap(); - - let popped = state - .lazy_vec - .pop(&mut state.storage) - .unwrap() - .unwrap(); - - // Post-conditions: - let new_len = state.lazy_vec.len(&state.storage).unwrap(); - assert_eq!(old_len, new_len + 1, "length must decrement"); - assert_eq!( - &popped, - state.eager_vec.last().unwrap(), - "popped element matches the last element in eager vec \ - before it's updated" - ); - } - Transition::Update { index, value } => { - let old_len = state.lazy_vec.len(&state.storage).unwrap(); - let old_val = state - .lazy_vec - .get(&state.storage, *index) - .unwrap() - .unwrap(); - - state - .lazy_vec - .update(&mut state.storage, *index, value.clone()) - .unwrap(); - - // Post-conditions: - let new_len = state.lazy_vec.len(&state.storage).unwrap(); - let new_val = state - .lazy_vec - .get(&state.storage, *index) - .unwrap() - .unwrap(); - assert_eq!(old_len, new_len, "length must not change"); - assert_eq!( - &old_val, - state.eager_vec.get(*index as usize).unwrap(), - "old value must match the value at the same index in \ - the eager vec before it's updated" - ); - assert_eq!( - &new_val, value, - "new value must match that which was passed into the \ - Transition::Update" - ); - } - } - - // Apply transition in the eager vec for comparison - apply_transition_on_eager_vec(&mut state.eager_vec, &transition); - - // Global post-conditions: - - // All items in eager vec must be present in lazy vec - for (ix, expected_item) in state.eager_vec.iter().enumerate() { - let got = state - .lazy_vec - .get(&state.storage, ix as Index) - .unwrap() - .expect("The expected item must be present in lazy vec"); - assert_eq!(expected_item, &got, "at index {ix}"); - } - - // All items in lazy vec must be present in eager vec - for (ix, expected_item) in - state.lazy_vec.iter(&state.storage).unwrap().enumerate() - { - let expected_item = expected_item.unwrap(); - let got = state - .eager_vec - .get(ix) - .expect("The expected item must be present in eager vec"); - assert_eq!(&expected_item, got, "at index {ix}"); - } - - state - } - } - - /// Generate an arbitrary `TestVecItem` - fn arb_test_vec_item() -> impl Strategy { - (any::(), any::()).prop_map(|(x, y)| TestVecItem { x, y }) - } - - /// Apply `Transition` on an eager `Vec`. - fn apply_transition_on_eager_vec( - vec: &mut Vec, - transition: &Transition, - ) { - match transition { - Transition::Push(value) => vec.push(value.clone()), - Transition::Pop => { - let _popped = vec.pop(); - } - Transition::Update { index, value } => { - let entry = vec.get_mut(*index as usize).unwrap(); - *entry = value.clone(); - } - } - } } diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index b5d72b7b02..1c3f6b1313 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -378,15 +378,18 @@ impl Key { /// - `Some(None)` if the prefix is matched, but it has no suffix, or /// - `None` if it doesn't match pub fn split_prefix(&self, prefix: &Self) -> Option> { - if self.segments.len() < prefix.len() { + if self.segments.len() < prefix.segments.len() { return None; } else if self == prefix { return Some(None); } - let mut self_prefix = self.segments.clone(); - let rest = self_prefix.split_off(prefix.len()); + // This is safe, because we check that the length of segments in self >= + // in prefix above + let (self_prefix, rest) = self.segments.split_at(prefix.segments.len()); if self_prefix == prefix.segments { - Some(Some(Key { segments: rest })) + Some(Some(Key { + segments: rest.to_vec(), + })) } else { None } diff --git a/tests/proptest-regressions/storage_api/collections/lazy_vec.txt b/tests/proptest-regressions/storage_api/collections/lazy_vec.txt new file mode 100644 index 0000000000..97a16dcbeb --- /dev/null +++ b/tests/proptest-regressions/storage_api/collections/lazy_vec.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 4330a283e32b5ff3f38d0af2298e1e98c30b1901c1027b572070a1af3356688e # shrinks to (initial_state, transitions) = (AbstractLazyVecState { valid_transitions: [], committed_transitions: [] }, [Push(TestVecItem { x: 15352583996758053781, y: true }), Pop, CommitTx, Push(TestVecItem { x: 6904067244182623445, y: false }), CommitTx, Pop, Push(TestVecItem { x: 759762287021483883, y: true }), Push(TestVecItem { x: 7885704082671389345, y: true }), Pop, Pop, Push(TestVecItem { x: 2762344561419437403, y: false }), Push(TestVecItem { x: 11448034977049028254, y: false }), Update { index: 0, value: TestVecItem { x: 7097339541298715775, y: false } }, Pop, Pop, Push(TestVecItem { x: 457884036257686887, y: true }), CommitTx, Push(TestVecItem { x: 17719281119971095810, y: true }), CommitTx, Push(TestVecItem { x: 4612681906563857058, y: false }), CommitTx, CommitTx, Pop, CommitTx, Pop, Push(TestVecItem { x: 4269537158299505726, y: false }), CommitTx, Pop, Pop, CommitTx, CommitTx, CommitTx, CommitTx, Push(TestVecItem { x: 9020889554694833528, y: true }), Push(TestVecItem { x: 4022797489860699620, y: false }), Update { index: 0, value: TestVecItem { x: 6485081152860611495, y: true } }, Pop, CommitTx, Push(TestVecItem { x: 14470031031894733310, y: false }), Push(TestVecItem { x: 1113274973965556867, y: true }), Push(TestVecItem { x: 4122902042678339346, y: false }), Push(TestVecItem { x: 9672639635189564637, y: true }), Pop, Pop, Pop, CommitTx, Update { index: 0, value: TestVecItem { x: 6372193991838429158, y: false } }, Push(TestVecItem { x: 15140852824102579010, y: false }), Pop, Pop, Pop, Push(TestVecItem { x: 4012218522073776592, y: false }), Push(TestVecItem { x: 10637893847792386454, y: true }), Push(TestVecItem { x: 3357788278949652885, y: false }), CommitTx, CommitTx, Pop, Pop, CommitTx, Pop, Push(TestVecItem { x: 11768518086398350214, y: true }), Push(TestVecItem { x: 4361685178396183644, y: true }), Pop, CommitTx, Push(TestVecItem { x: 2450907664540456425, y: false }), Push(TestVecItem { x: 18184919885943118586, y: true }), Update { index: 1, value: TestVecItem { x: 10611906658537706503, y: false } }, Push(TestVecItem { x: 4887827541279511396, y: false }), Update { index: 0, value: TestVecItem { x: 13021774003761931172, y: false } }, Push(TestVecItem { x: 3644118228573898014, y: false }), CommitTx, Update { index: 0, value: TestVecItem { x: 1276840798381751183, y: false } }, Pop, Pop]) diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 1b75f83bdc..78ebf2473c 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -12,6 +12,8 @@ mod e2e; #[cfg(test)] mod native_vp; pub mod storage; +#[cfg(test)] +mod storage_api; /// Using this import requires `tracing` and `tracing-subscriber` dependencies. /// Set env var `RUST_LOG=info` to see the logs from a test run (and diff --git a/tests/src/storage_api/collections/lazy_vec.rs b/tests/src/storage_api/collections/lazy_vec.rs new file mode 100644 index 0000000000..20ee80592d --- /dev/null +++ b/tests/src/storage_api/collections/lazy_vec.rs @@ -0,0 +1,631 @@ +#[cfg(test)] +mod tests { + use std::convert::TryInto; + + use borsh::{BorshDeserialize, BorshSerialize}; + use namada::types::address::{self, Address}; + use namada::types::storage; + use namada_tx_prelude::storage::KeySeg; + use namada_tx_prelude::storage_api::collections::{ + lazy_vec, LazyCollection, LazyVec, + }; + use proptest::prelude::*; + use proptest::prop_state_machine; + use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; + use proptest::test_runner::Config; + use test_log::test; + + use crate::tx::tx_host_env; + use crate::vp::vp_host_env; + + prop_state_machine! { + #![proptest_config(Config { + // Instead of the default 256, we only run 5 because otherwise it + // takes too long and it's preferable to crank up the number of + // transitions instead, to allow each case to run for more epochs as + // some issues only manifest once the model progresses further. + // Additionally, more cases will be explored every time this test is + // executed in the CI. + cases: 5, + .. Config::default() + })] + #[test] + fn lazy_vec_api_state_machine_test(sequential 1..100 => ConcreteLazyVecState); + } + + /// Some borsh-serializable type with arbitrary fields to be used inside + /// LazyVec state machine test + #[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + )] + struct TestVecItem { + x: u64, + y: bool, + } + + /// A `StateMachineTest` implemented on this struct manipulates it with + /// `Transition`s, which are also being accumulated into + /// `current_transitions`. It then: + /// + /// - checks its state against an in-memory `std::collections::Vec` + /// - runs validation and checks that the `LazyVec::Action`s reported from + /// validation match with transitions that were applied + /// + /// Additionally, one of the transitions is to commit a block and/or + /// transaction, during which the currently accumulated state changes are + /// persisted, or promoted from transaction write log to block's write log. + #[derive(Debug)] + struct ConcreteLazyVecState { + /// Address is used to prefix the storage key of the `lazy_vec` in + /// order to simulate a transaction and a validity predicate + /// check from changes on the `lazy_vec` + address: Address, + /// In the test, we apply the same transitions on the `lazy_vec` as on + /// `eager_vec` to check that `lazy_vec`'s state is consistent with + /// `eager_vec`. + eager_vec: Vec, + /// Handle to a lazy vec + lazy_vec: LazyVec, + /// Valid LazyVec changes in the current transaction + current_transitions: Vec>, + } + + #[derive(Clone, Debug)] + struct AbstractLazyVecState { + /// Valid LazyVec changes in the current transaction + valid_transitions: Vec>, + /// Valid LazyVec changes committed to storage + committed_transitions: Vec>, + } + + /// Possible transitions that can modify a [`LazyVec`]. This roughly + /// corresponds to the methods that have `StorageWrite` access and is very + /// similar to [`Action`] + #[derive(Clone, Debug)] + pub enum Transition { + /// Commit all valid transitions in the current transaction + CommitTx, + /// Commit all valid transitions in the current transaction and also + /// commit the current block + CommitTxAndBlock, + /// Push a value `T` into a [`LazyVec`] + Push(T), + /// Pop a value from a [`LazyVec`] + Pop, + /// Update a value `T` at index from pre to post state in a + /// [`LazyVec`] + Update { + /// index at which the value is updated + index: lazy_vec::Index, + /// value to update the element to + value: T, + }, + } + + impl AbstractStateMachine for AbstractLazyVecState { + type State = Self; + type Transition = Transition; + + fn init_state() -> BoxedStrategy { + Just(Self { + valid_transitions: vec![], + committed_transitions: vec![], + }) + .boxed() + } + + // Apply a random transition to the state + fn transitions(state: &Self::State) -> BoxedStrategy { + let length = state.len(); + if length == 0 { + prop_oneof![ + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => arb_test_vec_item().prop_map(Transition::Push) + ] + .boxed() + } else { + let arb_index = || { + let indices: Vec = (0..length).collect(); + proptest::sample::select(indices) + }; + prop_oneof![ + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => (arb_index(), arb_test_vec_item()).prop_map( + |(index, value)| Transition::Update { index, value } + ), + 3 => Just(Transition::Pop), + 5 => arb_test_vec_item().prop_map(Transition::Push), + ] + .boxed() + } + } + + fn apply_abstract( + mut state: Self::State, + transition: &Self::Transition, + ) -> Self::State { + match transition { + Transition::CommitTx => { + let valid_actions_to_commit = + std::mem::take(&mut state.valid_transitions); + state + .committed_transitions + .extend(valid_actions_to_commit.into_iter()); + } + _ => state.valid_transitions.push(transition.clone()), + } + state + } + + fn preconditions( + state: &Self::State, + transition: &Self::Transition, + ) -> bool { + let length = state.len(); + if length == 0 { + // Ensure that the pop or update transitions are not applied to + // an empty state + !matches!( + transition, + Transition::Pop | Transition::Update { .. } + ) + } else if let Transition::Update { index, .. } = transition { + // Ensure that the update index is a valid one + *index < (length - 1) + } else { + true + } + } + } + + impl StateMachineTest for ConcreteLazyVecState { + type Abstract = AbstractLazyVecState; + type ConcreteState = Self; + + fn init_test( + _initial_state: ::State, + ) -> Self::ConcreteState { + // Init transaction env in which we'll be applying the transitions + tx_host_env::init(); + + // The lazy_vec's path must be prefixed by the address to be able + // to trigger a validity predicate on it + let address = address::testing::established_address_1(); + tx_host_env::with(|env| env.spawn_accounts([&address])); + let lazy_vec_prefix: storage::Key = address.to_db_key().into(); + + Self { + address, + eager_vec: vec![], + lazy_vec: LazyVec::open( + lazy_vec_prefix.push(&"arbitrary".to_string()).unwrap(), + ), + current_transitions: vec![], + } + } + + fn apply_concrete( + mut state: Self::ConcreteState, + transition: ::Transition, + ) -> Self::ConcreteState { + // Apply transitions in transaction env + let ctx = tx_host_env::ctx(); + + // Persist the transitions in the current tx, or clear previous ones + // if we're committing a tx + match &transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + state.current_transitions = vec![]; + } + _ => { + state.current_transitions.push(transition.clone()); + } + } + + // Transition application on lazy vec and post-conditions: + match &transition { + Transition::CommitTx => { + // commit the tx without committing the block + tx_host_env::with(|env| env.write_log.commit_tx()); + } + Transition::CommitTxAndBlock => { + // commit the tx and the block + tx_host_env::commit_tx_and_block(); + } + Transition::Push(value) => { + let old_len = state.lazy_vec.len(ctx).unwrap(); + + state.lazy_vec.push(ctx, value.clone()).unwrap(); + + // Post-conditions: + let new_len = state.lazy_vec.len(ctx).unwrap(); + let stored_value = + state.lazy_vec.get(ctx, new_len - 1).unwrap().unwrap(); + assert_eq!( + &stored_value, value, + "the new item must be added to the back" + ); + assert_eq!(old_len + 1, new_len, "length must increment"); + + state.assert_validation_accepted(new_len); + } + Transition::Pop => { + let old_len = state.lazy_vec.len(ctx).unwrap(); + + let popped = state.lazy_vec.pop(ctx).unwrap().unwrap(); + + // Post-conditions: + let new_len = state.lazy_vec.len(ctx).unwrap(); + assert_eq!(old_len, new_len + 1, "length must decrement"); + assert_eq!( + &popped, + state.eager_vec.last().unwrap(), + "popped element matches the last element in eager vec \ + before it's updated" + ); + + state.assert_validation_accepted(new_len); + } + Transition::Update { index, value } => { + let old_len = state.lazy_vec.len(ctx).unwrap(); + let old_val = + state.lazy_vec.get(ctx, *index).unwrap().unwrap(); + + state.lazy_vec.update(ctx, *index, value.clone()).unwrap(); + + // Post-conditions: + let new_len = state.lazy_vec.len(ctx).unwrap(); + let new_val = + state.lazy_vec.get(ctx, *index).unwrap().unwrap(); + assert_eq!(old_len, new_len, "length must not change"); + assert_eq!( + &old_val, + state.eager_vec.get(*index as usize).unwrap(), + "old value must match the value at the same index in \ + the eager vec before it's updated" + ); + assert_eq!( + &new_val, value, + "new value must match that which was passed into the \ + Transition::Update" + ); + + state.assert_validation_accepted(new_len); + } + } + + // Apply transition in the eager vec for comparison + apply_transition_on_eager_vec(&mut state.eager_vec, &transition); + + // Global post-conditions: + + // All items in eager vec must be present in lazy vec + for (ix, expected_item) in state.eager_vec.iter().enumerate() { + let got = state + .lazy_vec + .get(ctx, ix as lazy_vec::Index) + .unwrap() + .expect("The expected item must be present in lazy vec"); + assert_eq!(expected_item, &got, "at index {ix}"); + } + + // All items in lazy vec must be present in eager vec + for (ix, expected_item) in + state.lazy_vec.iter(ctx).unwrap().enumerate() + { + let expected_item = expected_item.unwrap(); + let got = state + .eager_vec + .get(ix) + .expect("The expected item must be present in eager vec"); + assert_eq!(&expected_item, got, "at index {ix}"); + } + + state + } + } + + impl AbstractLazyVecState { + /// Find the length of the vector from the applied transitions + fn len(&self) -> u64 { + (vec_len_diff_from_transitions(self.committed_transitions.iter()) + + vec_len_diff_from_transitions(self.valid_transitions.iter())) + .try_into() + .expect( + "It shouldn't be possible to underflow length from all \ + transactions applied in abstract state", + ) + } + } + + /// Find the difference in length of the vector from the applied transitions + fn vec_len_diff_from_transitions<'a>( + all_transitions: impl Iterator>, + ) -> i64 { + let mut push_count: i64 = 0; + let mut pop_count: i64 = 0; + + for trans in all_transitions { + match trans { + Transition::CommitTx + | Transition::CommitTxAndBlock + | Transition::Update { .. } => {} + Transition::Push(_) => push_count += 1, + Transition::Pop => pop_count += 1, + } + } + push_count - pop_count + } + + impl ConcreteLazyVecState { + fn assert_validation_accepted(&self, new_vec_len: u64) { + // Init the VP env from tx env in which we applied the vec + // transitions + let tx_env = tx_host_env::take(); + vp_host_env::init_from_tx(self.address.clone(), tx_env, |_| {}); + + // Simulate a validity predicate run using the lazy vec's validation + // helpers + let changed_keys = + vp_host_env::with(|env| env.all_touched_storage_keys()); + + let mut validation_builder = None; + + // Push followed by pop is a no-op, in which case we'd still see the + // changed keys for these actions, but they wouldn't affect the + // validation result and they never get persisted, but we'd still + // them as changed key here. To guard against this case, + // we check that `vec_len_from_transitions` is not empty. + let vec_len_diff = + vec_len_diff_from_transitions(self.current_transitions.iter()); + + // To help debug validation issues... + dbg!( + &self.current_transitions, + &changed_keys + .iter() + .map(storage::Key::to_string) + .collect::>() + ); + + for key in &changed_keys { + let is_sub_key = self + .lazy_vec + .accumulate( + vp_host_env::ctx(), + &mut validation_builder, + key, + ) + .unwrap(); + + assert!( + is_sub_key, + "We're only modifying the lazy_vec's keys here. Key: \ + \"{key}\", vec length diff {vec_len_diff}" + ); + } + if !changed_keys.is_empty() && vec_len_diff != 0 { + assert!( + validation_builder.is_some(), + "If some keys were changed, the builder must get filled in" + ); + let actions = validation_builder.unwrap().build().expect( + "With valid transitions only, validation should always \ + pass", + ); + let mut actions_to_check = actions.clone(); + + // Check that every transition has a corresponding action from + // validation. We drop the found actions to check that all + // actions are matched too. + let current_transitions = normalize_transitions( + &self.current_transitions, + new_vec_len, + ); + for transition in ¤t_transitions { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + } + Transition::Push(expected_val) => { + let mut ix = 0; + while ix < actions_to_check.len() { + if let lazy_vec::Action::Push(val) = + &actions_to_check[ix] + { + if expected_val == val { + actions_to_check.remove(ix); + break; + } + } + ix += 1; + } + } + Transition::Pop => { + let mut ix = 0; + while ix < actions_to_check.len() { + if let lazy_vec::Action::Pop(_val) = + &actions_to_check[ix] + { + actions_to_check.remove(ix); + break; + } + ix += 1; + } + } + Transition::Update { + index: expected_index, + value, + } => { + let mut ix = 0; + while ix < actions_to_check.len() { + if let lazy_vec::Action::Update { + index, + pre: _, + post, + } = &actions_to_check[ix] + { + if expected_index == index && post == value + { + actions_to_check.remove(ix); + break; + } + } + ix += 1; + } + } + } + } + + assert!( + actions_to_check.is_empty(), + "All the actions reported from validation {actions:#?} \ + should have been matched with SM transitions \ + {current_transitions:#?}, but these actions didn't \ + match: {actions_to_check:#?}", + ) + } + + // Put the tx_env back before checking the result + tx_host_env::set_from_vp_env(vp_host_env::take()); + } + } + + /// Generate an arbitrary `TestVecItem` + fn arb_test_vec_item() -> impl Strategy { + (any::(), any::()).prop_map(|(x, y)| TestVecItem { x, y }) + } + + /// Apply `Transition` on an eager `Vec`. + fn apply_transition_on_eager_vec( + vec: &mut Vec, + transition: &Transition, + ) { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => {} + Transition::Push(value) => vec.push(value.clone()), + Transition::Pop => { + let _popped = vec.pop(); + } + Transition::Update { index, value } => { + let entry = vec.get_mut(*index as usize).unwrap(); + *entry = value.clone(); + } + } + } + + /// Normalize transitions: + /// - pop at ix + push(val) at ix -> update(ix, val) + /// - push(val) at ix + update(ix, new_val) -> push(new_val) at ix + /// - update(ix, val) + update(ix, new_val) -> update(ix, new_val) + /// + /// Note that the normalizable transitions pairs do not have to be directly + /// next to each other, but their order does matter. + fn normalize_transitions( + transitions: &[Transition], + new_vec_len: u64, + ) -> Vec> { + let stack_start_pos = ((new_vec_len as i64) + - vec_len_diff_from_transitions(transitions.iter())) + as u64; + let mut stack_pos = stack_start_pos; + let mut collapsed = vec![]; + 'outer: for transition in transitions { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + collapsed.push(transition.clone()) + } + Transition::Push(value) => { + // If there are some pops, the last one can be collapsed + // with this push + if stack_pos < stack_start_pos { + // Find the pop from the back + let mut found_ix = None; + for (ix, transition) in + collapsed.iter().enumerate().rev() + { + if let Transition::Pop = transition { + found_ix = Some(ix); + break; + } + } + let ix = found_ix.expect("Pop must be found"); + // pop at ix + push(val) at ix -> update(ix, val) + + // Replace the Pop with an Update and don't insert the + // Push + *collapsed.get_mut(ix).unwrap() = Transition::Update { + index: stack_pos, + value: value.clone(), + }; + } else { + collapsed.push(transition.clone()); + } + stack_pos += 1; + } + Transition::Pop => { + collapsed.push(transition.clone()); + stack_pos -= 1; + } + Transition::Update { index, value } => { + // If there are some pushes, check if one of them is at the + // same index as this update + if stack_pos > stack_start_pos { + let mut current_pos = stack_start_pos; + for (ix, collapsed_transition) in + collapsed.iter().enumerate() + { + match collapsed_transition { + Transition::CommitTx + | Transition::CommitTxAndBlock => {} + Transition::Push(_) => { + if ¤t_pos == index { + // push(val) at `ix` + update(ix, + // new_val) -> + // push(new_val) at `ix` + + // Replace the Push with the new Push of + // Update's + // value and don't insert the Update + *collapsed.get_mut(ix).unwrap() = + Transition::Push(value.clone()); + continue 'outer; + } + current_pos += 1; + } + Transition::Pop => { + current_pos -= 1; + } + Transition::Update { + index: prev_update_index, + value: _, + } => { + if index == prev_update_index { + // update(ix, val) + update(ix, new_val) + // -> update(ix, new_val) + + // Replace the Update with the new + // Update instead of inserting it + *collapsed.get_mut(ix).unwrap() = + transition.clone(); + continue 'outer; + } + } + } + } + } + collapsed.push(transition.clone()) + } + } + } + collapsed + } +} diff --git a/tests/src/storage_api/collections/mod.rs b/tests/src/storage_api/collections/mod.rs new file mode 100644 index 0000000000..d874b88e22 --- /dev/null +++ b/tests/src/storage_api/collections/mod.rs @@ -0,0 +1 @@ +mod lazy_vec; diff --git a/tests/src/storage_api/mod.rs b/tests/src/storage_api/mod.rs new file mode 100644 index 0000000000..bc487bd59e --- /dev/null +++ b/tests/src/storage_api/mod.rs @@ -0,0 +1 @@ +mod collections; diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 728c488bca..6e23ced06b 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -18,6 +18,8 @@ use namada::vm::{self, WasmCacheRwAccess}; use namada_tx_prelude::{BorshSerialize, Ctx}; use tempfile::TempDir; +use crate::vp::TestVpEnv; + /// Tx execution context provides access to host env functions static mut CTX: Ctx = unsafe { Ctx::new() }; @@ -235,6 +237,29 @@ mod native_tx_host_env { with(|env| env.commit_tx_and_block()) } + /// Set the [`TestTxEnv`] back from a [`TestVpEnv`]. This is useful when + /// testing validation with multiple transactions that accumulate some state + /// changes. + pub fn set_from_vp_env(vp_env: TestVpEnv) { + let TestVpEnv { + storage, + write_log, + tx, + vp_wasm_cache, + vp_cache_dir, + .. + } = vp_env; + let tx_env = TestTxEnv { + storage, + write_log, + vp_wasm_cache, + vp_cache_dir, + tx, + ..Default::default() + }; + set(tx_env); + } + /// A helper macro to create implementations of the host environment /// functions exported to wasm, which uses the environment from the /// `ENV` variable. From 4b60051a5f72f137dc3321145a0ff878132df0fe Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 16 Sep 2022 17:15:14 +0200 Subject: [PATCH 105/197] WIP: validation for lazy_map --- .../storage_api/collections/lazy_map.rs | 148 ++++- .../storage_api/collections/lazy_map.txt | 7 + tests/src/storage_api/collections/lazy_map.rs | 608 ++++++++++++++++++ tests/src/storage_api/collections/mod.rs | 1 + 4 files changed, 761 insertions(+), 3 deletions(-) create mode 100644 tests/proptest-regressions/storage_api/collections/lazy_map.txt create mode 100644 tests/src/storage_api/collections/lazy_map.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index a47ff0734a..3801848b8f 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -3,12 +3,15 @@ use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; +use derivative::Derivative; +use thiserror::Error; use super::super::Result; use super::{LazyCollection, ReadError}; -use crate::ledger::storage_api::validation::Data; +use crate::ledger::storage_api::validation::{self, Data}; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; -use crate::types::storage::{self, KeySeg}; +use crate::ledger::vp_env::VpEnv; +use crate::types::storage::{self, DbKeySeg, KeySeg}; /// Subkey corresponding to the data elements of the LazyMap pub const DATA_SUBKEY: &str = "data"; @@ -52,12 +55,21 @@ pub enum SubKeyWithData { /// the methods that have `StorageWrite` access. /// TODO: In a nested collection, `V` may be an action inside the nested /// collection. -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum Action { /// Insert or update a value `V` at key `K` in a [`LazyMap`]. Insert(K, V), /// Remove a value `V` at key `K` from a [`LazyMap`]. Remove(K, V), + /// Update a value `V` at key `K` in a [`LazyMap`]. + Update { + /// key at which the value is updated + key: K, + /// value before the update + pre: V, + /// value after the update + post: V, + }, } /// TODO: In a nested collection, `V` may be an action inside the nested @@ -70,6 +82,46 @@ pub enum Nested { Remove(K, V), } +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum ValidationError { + #[error("Storage error in reading key {0}")] + StorageError(storage::Key), + // #[error("Incorrect difference in LazyVec's length")] + // InvalidLenDiff, + // #[error("An empty LazyVec must be deleted from storage")] + // EmptyVecShouldBeDeleted, + // #[error("Push at a wrong index. Got {got}, expected {expected}.")] + // UnexpectedPushIndex { got: Index, expected: Index }, + // #[error("Pop at a wrong index. Got {got}, expected {expected}.")] + // UnexpectedPopIndex { got: Index, expected: Index }, + // #[error( + // "Update (combination of pop and push) at a wrong index. Got {got}, + // \ expected {expected}." + // )] + // UnexpectedUpdateIndex { got: Index, expected: Index }, + // #[error("An index has overflown its representation: {0}")] + // IndexOverflow(>::Error), + // #[error("Unexpected underflow in `{0} - {0}`")] + // UnexpectedUnderflow(Index, Index), + #[error("Invalid storage key {0}")] + InvalidSubKey(storage::Key), +} + +/// [`LazyMap`] validation result +pub type ValidationResult = std::result::Result; + +/// [`LazyMap`] validation builder from storage changes. The changes can be +/// accumulated with `LazyMap::validate()` and then turned into a list +/// of valid actions on the map with `ValidationBuilder::build()`. +#[derive(Debug, Derivative)] +// https://mcarton.github.io/rust-derivative/latest/Default.html#custom-bound +#[derivative(Default(bound = ""))] +pub struct ValidationBuilder { + /// The accumulator of found changes under the vector + pub changes: Vec>, +} + impl LazyCollection for LazyMap where K: storage::KeySeg, @@ -247,6 +299,96 @@ where ) -> Result<()> { storage.write(storage_key, val) } + + /// Check if the given storage key is a valid LazyMap sub-key and if so + /// return which one + pub fn is_valid_sub_key( + &self, + key: &storage::Key, + ) -> storage_api::Result>> { + let suffix = match key.split_prefix(&self.key) { + None => { + // not matching prefix, irrelevant + return Ok(None); + } + Some(None) => { + // no suffix, invalid + return Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(); + } + Some(Some(suffix)) => suffix, + }; + + // Match the suffix against expected sub-keys + match &suffix.segments[..] { + [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { + Ok(Some(SubKey::Data(key_in_kv))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + } + _ => Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(), + } + } + + /// Accumulate storage changes inside a [`ValidationBuilder`]. This is + /// typically done by the validity predicate while looping through the + /// changed keys. If the resulting `builder` is not `None`, one must + /// call `fn build()` on it to get the validation result. + /// This function will return `Ok(true)` if the storage key is a valid + /// sub-key of this collection, `Ok(false)` if the storage key doesn't match + /// the prefix of this collection, or fail with + /// [`ValidationError::InvalidSubKey`] if the prefix matches this + /// collection, but the key itself is not recognized. + pub fn accumulate( + &self, + env: &ENV, + builder: &mut Option>, + key_changed: &storage::Key, + ) -> storage_api::Result + where + ENV: for<'a> VpEnv<'a>, + { + if let Some(sub) = self.is_valid_sub_key(key_changed)? { + let SubKey::Data(key) = sub; + let data = validation::read_data(env, key_changed)?; + let change = data.map(|data| SubKeyWithData::Data(key, data)); + if let Some(change) = change { + let builder = + builder.get_or_insert(ValidationBuilder::default()); + builder.changes.push(change); + } + return Ok(true); + } + Ok(false) + } +} + +impl ValidationBuilder +where + K: storage::KeySeg + Ord + Clone, +{ + /// Build a list of actions from storage changes. + pub fn build(self) -> Vec> { + self.changes + .into_iter() + .map(|change| { + let SubKeyWithData::Data(key, data) = change; + match data { + Data::Add { post } => Action::Insert(key, post), + Data::Update { pre, post } => { + Action::Update { key, pre, post } + } + Data::Delete { pre } => Action::Remove(key, pre), + } + }) + .collect() + } } #[cfg(test)] diff --git a/tests/proptest-regressions/storage_api/collections/lazy_map.txt b/tests/proptest-regressions/storage_api/collections/lazy_map.txt new file mode 100644 index 0000000000..2de7510923 --- /dev/null +++ b/tests/proptest-regressions/storage_api/collections/lazy_map.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 59b8eaaf5d8e03e58b346ef229a2487f68fea488197420f150682f7275ce2b83 # shrinks to (initial_state, transitions) = (AbstractLazyMapState { valid_transitions: [], committed_transitions: [] }, [Insert(11178241982156558453, TestVal { x: 9618691367534591266, y: true }), CommitTx, Update(11178241982156558453, TestVal { x: 2635377083098935189, y: false }), Update(11178241982156558453, TestVal { x: 11485387163946255361, y: false }), Insert(4380901092919801530, TestVal { x: 17235291421018840542, y: false }), Update(11178241982156558453, TestVal { x: 1936190700145956620, y: false }), Update(11178241982156558453, TestVal { x: 6934621224353358508, y: false }), Update(11178241982156558453, TestVal { x: 16175036327810390362, y: true }), Remove(5606457884982633480), Insert(7124206407862523505, TestVal { x: 5513772825695605555, y: true }), CommitTxAndBlock, CommitTx, Insert(13347045100814804679, TestVal { x: 5157295776286367034, y: false }), Update(7124206407862523505, TestVal { x: 1989909525753197955, y: false }), Update(4380901092919801530, TestVal { x: 13085578877588425331, y: false }), Update(7124206407862523505, TestVal { x: 1620781139263176467, y: true }), Insert(5806457332157050619, TestVal { x: 14632354209749334932, y: true }), Remove(1613213961397167063), Update(7124206407862523505, TestVal { x: 3848976302483310370, y: true }), Update(4380901092919801530, TestVal { x: 15281186775251770467, y: false }), Remove(5303306623647571548), Insert(5905425607805327902, TestVal { x: 1274794101048822414, y: false }), Insert(2305446651611241243, TestVal { x: 7872403441503057017, y: true }), Insert(2843165193114615911, TestVal { x: 13698490566286768452, y: false }), Insert(3364298091459048760, TestVal { x: 8891279000465212397, y: true }), CommitTx, Insert(17278527568142155478, TestVal { x: 8166151895050476136, y: false }), Remove(9206713523174765253), Remove(1148985045479283759), Insert(13346103305566843535, TestVal { x: 13148026974798633058, y: true }), Remove(17185699086139524651), CommitTx, Update(7124206407862523505, TestVal { x: 3047872255943216792, y: false }), CommitTxAndBlock, CommitTxAndBlock, Remove(4672009405538026945), Update(5905425607805327902, TestVal { x: 6635343936644805461, y: false }), Insert(14100441716981493843, TestVal { x: 8068697312326956479, y: true }), Insert(8370580326875672309, TestVal { x: 18416630552728813406, y: false }), Update(2305446651611241243, TestVal { x: 3777718192999015176, y: false }), Remove(1532142753559370584), Remove(10097030807802775125), Insert(10080356901530935857, TestVal { x: 17171047520093964037, y: false }), Update(3364298091459048760, TestVal { x: 702372485798608773, y: true }), Insert(5504969092734638033, TestVal { x: 314752460808087203, y: true }), Remove(5486040497128339175), Insert(7884678026881625058, TestVal { x: 4313610278903495077, y: true }), CommitTx, Insert(11228024342874184864, TestVal { x: 428512502841968552, y: false }), Insert(4684666745142518471, TestVal { x: 13122515680485564107, y: true }), Remove(14243063045921130600), Remove(4530767959521683042), Insert(10236349778753659715, TestVal { x: 3138294567956031715, y: true }), Update(2305446651611241243, TestVal { x: 8133236604817109805, y: false }), Update(2843165193114615911, TestVal { x: 12001998927296899868, y: false }), CommitTxAndBlock, CommitTx, CommitTxAndBlock]) diff --git a/tests/src/storage_api/collections/lazy_map.rs b/tests/src/storage_api/collections/lazy_map.rs new file mode 100644 index 0000000000..c7a309ab4d --- /dev/null +++ b/tests/src/storage_api/collections/lazy_map.rs @@ -0,0 +1,608 @@ +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + use std::convert::TryInto; + + use borsh::{BorshDeserialize, BorshSerialize}; + use namada::types::address::{self, Address}; + use namada::types::storage; + use namada_tx_prelude::storage::KeySeg; + use namada_tx_prelude::storage_api::collections::{ + lazy_map, LazyCollection, LazyMap, + }; + use proptest::prelude::*; + use proptest::prop_state_machine; + use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; + use proptest::test_runner::Config; + use test_log::test; + + use crate::tx::tx_host_env; + use crate::vp::vp_host_env; + + prop_state_machine! { + #![proptest_config(Config { + // Instead of the default 256, we only run 5 because otherwise it + // takes too long and it's preferable to crank up the number of + // transitions instead, to allow each case to run for more epochs as + // some issues only manifest once the model progresses further. + // Additionally, more cases will be explored every time this test is + // executed in the CI. + cases: 5, + .. Config::default() + })] + #[test] + fn lazy_map_api_state_machine_test(sequential 1..100 => ConcreteLazyMapState); + } + + /// Type of key used in the map + type TestKey = u64; + + /// Some borsh-serializable type with arbitrary fields to be used inside + /// LazyMap state machine test + #[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + )] + struct TestVal { + x: u64, + y: bool, + } + + /// A `StateMachineTest` implemented on this struct manipulates it with + /// `Transition`s, which are also being accumulated into + /// `current_transitions`. It then: + /// + /// - checks its state against an in-memory `std::collections::HashMap` + /// - runs validation and checks that the `LazyMap::Action`s reported from + /// validation match with transitions that were applied + /// + /// Additionally, one of the transitions is to commit a block and/or + /// transaction, during which the currently accumulated state changes are + /// persisted, or promoted from transaction write log to block's write log. + #[derive(Debug)] + struct ConcreteLazyMapState { + /// Address is used to prefix the storage key of the `lazy_map` in + /// order to simulate a transaction and a validity predicate + /// check from changes on the `lazy_map` + address: Address, + /// In the test, we apply the same transitions on the `lazy_map` as on + /// `eager_map` to check that `lazy_map`'s state is consistent with + /// `eager_map`. + eager_map: BTreeMap, + /// Handle to a lazy map + lazy_map: LazyMap, + /// Valid LazyMap changes in the current transaction + current_transitions: Vec, + } + + #[derive(Clone, Debug, Default)] + struct AbstractLazyMapState { + /// Valid LazyMap changes in the current transaction + valid_transitions: Vec, + /// Valid LazyMap changes committed to storage + committed_transitions: Vec, + } + + /// Possible transitions that can modify a [`LazyMap`]. + /// This roughly corresponds to the methods that have `StorageWrite` + /// access and is very similar to [`Action`] + #[derive(Clone, Debug)] + enum Transition { + /// Commit all valid transitions in the current transaction + CommitTx, + /// Commit all valid transitions in the current transaction and also + /// commit the current block + CommitTxAndBlock, + /// Insert a key-val into a [`LazyMap`] + Insert(TestKey, TestVal), + /// Remove a key-val from a [`LazyMap`] + Remove(TestKey), + /// Update a value at key from pre to post state in a + /// [`LazyMap`] + Update(TestKey, TestVal), + } + + impl AbstractStateMachine for AbstractLazyMapState { + type State = Self; + type Transition = Transition; + + fn init_state() -> BoxedStrategy { + Just(Self::default()).boxed() + } + + // Apply a random transition to the state + fn transitions(state: &Self::State) -> BoxedStrategy { + let length = state.len(); + if length == 0 { + prop_oneof![ + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => (arb_map_key(), arb_map_val()).prop_map(|(key, val)| Transition::Insert(key, val)) + ] + .boxed() + } else { + let keys = state.find_existing_keys(); + let arb_existing_map_key = + || proptest::sample::select(keys.clone()); + prop_oneof![ + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => (arb_existing_map_key(), arb_map_val()).prop_map(|(key, val)| + Transition::Update(key, val) + ), + 3 => arb_existing_map_key().prop_map(Transition::Remove), + 5 => (arb_map_key().prop_filter("insert on non-existing keys only", move |key| !keys.contains(&key)), arb_map_val()).prop_map(|(key, val)| Transition::Insert(key, val)) + ] + .boxed() + } + } + + fn apply_abstract( + mut state: Self::State, + transition: &Self::Transition, + ) -> Self::State { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + let valid_actions_to_commit = + std::mem::take(&mut state.valid_transitions); + state + .committed_transitions + .extend(valid_actions_to_commit.into_iter()); + } + _ => state.valid_transitions.push(transition.clone()), + } + state + } + + fn preconditions( + state: &Self::State, + transition: &Self::Transition, + ) -> bool { + let length = state.len(); + // Ensure that the remove or update transitions are not applied + // to an empty state + if length == 0 + && matches!( + transition, + Transition::Remove(_) | Transition::Update(_, _) + ) + { + return false; + } + match transition { + Transition::Update(key, _) | Transition::Remove(key) => { + let keys = state.find_existing_keys(); + // Ensure that the update/remove key is an existing one + keys.contains(key) + } + Transition::Insert(key, _) => { + let keys = state.find_existing_keys(); + // Ensure that the insert key is not an existing one + !keys.contains(key) + } + _ => true, + } + } + } + + impl StateMachineTest for ConcreteLazyMapState { + type Abstract = AbstractLazyMapState; + type ConcreteState = Self; + + fn init_test( + _initial_state: ::State, + ) -> Self::ConcreteState { + // Init transaction env in which we'll be applying the transitions + tx_host_env::init(); + + // The lazy_map's path must be prefixed by the address to be able + // to trigger a validity predicate on it + let address = address::testing::established_address_1(); + tx_host_env::with(|env| env.spawn_accounts([&address])); + let lazy_map_prefix: storage::Key = address.to_db_key().into(); + + Self { + address, + eager_map: BTreeMap::new(), + lazy_map: LazyMap::open( + lazy_map_prefix.push(&"arbitrary".to_string()).unwrap(), + ), + current_transitions: vec![], + } + } + + fn apply_concrete( + mut state: Self::ConcreteState, + transition: ::Transition, + ) -> Self::ConcreteState { + // Apply transitions in transaction env + let ctx = tx_host_env::ctx(); + + // Persist the transitions in the current tx, or clear previous ones + // if we're committing a tx + match &transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + state.current_transitions = vec![]; + } + _ => { + state.current_transitions.push(transition.clone()); + } + } + + // Transition application on lazy map and post-conditions: + match &transition { + Transition::CommitTx => { + // commit the tx without committing the block + tx_host_env::with(|env| env.write_log.commit_tx()); + } + Transition::CommitTxAndBlock => { + // commit the tx and the block + tx_host_env::commit_tx_and_block(); + } + Transition::Insert(key, value) => { + state.lazy_map.insert(ctx, *key, value.clone()).unwrap(); + + // Post-conditions: + let stored_value = + state.lazy_map.get(ctx, key).unwrap().unwrap(); + assert_eq!( + &stored_value, value, + "the new item must be added to the back" + ); + + state.assert_validation_accepted(); + } + Transition::Remove(key) => { + let removed = + state.lazy_map.remove(ctx, key).unwrap().unwrap(); + + // Post-conditions: + assert_eq!( + &removed, + state.eager_map.get(key).unwrap(), + "removed element matches the value in eager map \ + before it's updated" + ); + + state.assert_validation_accepted(); + } + Transition::Update(key, value) => { + let old_val = + state.lazy_map.get(ctx, key).unwrap().unwrap(); + + state.lazy_map.insert(ctx, *key, value.clone()).unwrap(); + + // Post-conditions: + let new_val = + state.lazy_map.get(ctx, key).unwrap().unwrap(); + assert_eq!( + &old_val, + state.eager_map.get(key).unwrap(), + "old value must match the value at the same key in \ + the eager map before it's updated" + ); + assert_eq!( + &new_val, value, + "new value must match that which was passed into the \ + Transition::Update" + ); + + state.assert_validation_accepted(); + } + } + + // Apply transition in the eager map for comparison + apply_transition_on_eager_map(&mut state.eager_map, &transition); + + // Global post-conditions: + + // All items in eager map must be present in lazy map + for (key, expected_item) in state.eager_map.iter() { + let got = + state.lazy_map.get(ctx, key).unwrap().expect( + "The expected item must be present in lazy map", + ); + assert_eq!(expected_item, &got, "at key {key}"); + } + + // All items in lazy map must be present in eager map + for key_val in state.lazy_map.iter(ctx).unwrap() { + let (key, expected_val) = key_val.unwrap(); + let got = state + .eager_map + .get(&key) + .expect("The expected item must be present in eager map"); + assert_eq!(&expected_val, got, "at key {key}"); + } + + state + } + } + + impl AbstractLazyMapState { + /// Find the length of the map from the applied transitions + fn len(&self) -> u64 { + (map_len_diff_from_transitions(self.committed_transitions.iter()) + + map_len_diff_from_transitions(self.valid_transitions.iter())) + .try_into() + .expect( + "It shouldn't be possible to underflow length from all \ + transactions applied in abstract state", + ) + } + + /// Build an eager map from the committed and current transitions + fn eager_map(&self) -> BTreeMap { + let mut eager_map = BTreeMap::new(); + for transition in &self.committed_transitions { + apply_transition_on_eager_map(&mut eager_map, transition); + } + for transition in &self.valid_transitions { + apply_transition_on_eager_map(&mut eager_map, transition); + } + eager_map + } + + /// Find the keys currently present in the map + fn find_existing_keys(&self) -> Vec { + self.eager_map().keys().cloned().collect() + } + } + + /// Find the difference in length of the map from the applied transitions + fn map_len_diff_from_transitions<'a>( + transitions: impl Iterator, + ) -> i64 { + let mut insert_count: i64 = 0; + let mut remove_count: i64 = 0; + + for trans in transitions { + match trans { + Transition::CommitTx + | Transition::CommitTxAndBlock + | Transition::Update(_, _) => {} + Transition::Insert(_, _) => insert_count += 1, + Transition::Remove(_) => remove_count += 1, + } + } + insert_count - remove_count + } + + impl ConcreteLazyMapState { + fn assert_validation_accepted(&self) { + // Init the VP env from tx env in which we applied the map + // transitions + let tx_env = tx_host_env::take(); + vp_host_env::init_from_tx(self.address.clone(), tx_env, |_| {}); + + // Simulate a validity predicate run using the lazy map's validation + // helpers + let changed_keys = + vp_host_env::with(|env| env.all_touched_storage_keys()); + + let mut validation_builder = None; + + // Push followed by pop is a no-op, in which case we'd still see the + // changed keys for these actions, but they wouldn't affect the + // validation result and they never get persisted, but we'd still + // them as changed key here. To guard against this case, + // we check that `map_len_from_transitions` is not empty. + let map_len_diff = + map_len_diff_from_transitions(self.current_transitions.iter()); + + // To help debug validation issues... + dbg!( + &self.current_transitions, + &changed_keys + .iter() + .map(storage::Key::to_string) + .collect::>() + ); + + for key in &changed_keys { + let is_sub_key = self + .lazy_map + .accumulate( + vp_host_env::ctx(), + &mut validation_builder, + key, + ) + .unwrap(); + + assert!( + is_sub_key, + "We're only modifying the lazy_map's keys here. Key: \ + \"{key}\", map length diff {map_len_diff}" + ); + } + if !changed_keys.is_empty() && map_len_diff != 0 { + assert!( + validation_builder.is_some(), + "If some keys were changed, the builder must get filled in" + ); + let actions = validation_builder.unwrap().build(); + let mut actions_to_check = actions.clone(); + + // Check that every transition has a corresponding action from + // validation. We drop the found actions to check that all + // actions are matched too. + let current_transitions = + normalize_transitions(&self.current_transitions); + for transition in ¤t_transitions { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + } + Transition::Insert(expected_key, expected_val) => { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let lazy_map::Action::Insert(key, val) = + action + { + if expected_key == key + && expected_val == val + { + actions_to_check.remove(ix); + break; + } + } + } + } + Transition::Remove(expected_key) => { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let lazy_map::Action::Remove(key, _val) = + action + { + if expected_key == key { + actions_to_check.remove(ix); + break; + } + } + } + } + Transition::Update(expected_key, value) => { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let lazy_map::Action::Update { + key, + pre: _, + post, + } = action + { + if expected_key == key && post == value { + actions_to_check.remove(ix); + break; + } + } + } + } + } + } + + assert!( + actions_to_check.is_empty(), + "All the actions reported from validation {actions:#?} \ + should have been matched with SM transitions \ + {current_transitions:#?}, but these actions didn't \ + match: {actions_to_check:#?}", + ) + } + + // Put the tx_env back before checking the result + tx_host_env::set_from_vp_env(vp_host_env::take()); + } + } + + /// Generate an arbitrary `TestKey` + fn arb_map_key() -> impl Strategy { + any::() + } + + /// Generate an arbitrary `TestVal` + fn arb_map_val() -> impl Strategy { + (any::(), any::()).prop_map(|(x, y)| TestVal { x, y }) + } + + /// Apply `Transition` on an eager `Map`. + fn apply_transition_on_eager_map( + map: &mut BTreeMap, + transition: &Transition, + ) { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => {} + Transition::Insert(key, value) => { + map.insert(*key, value.clone()); + } + Transition::Remove(key) => { + let _popped = map.remove(key); + } + Transition::Update(key, value) => { + let entry = map.get_mut(key).unwrap(); + *entry = value.clone(); + } + } + } + + /// Normalize transitions: + /// - remove(key) + insert(key, val) -> update(key, val) + /// - insert(key, val) + update(key, new_val) -> insert(key, new_val) + /// - update(key, val) + update(key, new_val) -> update(key, new_val) + /// + /// Note that the normalizable transitions pairs do not have to be directly + /// next to each other, but their order does matter. + fn normalize_transitions(transitions: &[Transition]) -> Vec { + let mut collapsed = vec![]; + 'outer: for transition in transitions { + match transition { + Transition::CommitTx + | Transition::CommitTxAndBlock + | Transition::Remove(_) => collapsed.push(transition.clone()), + Transition::Insert(key, val) => { + for (ix, collapsed_transition) in + collapsed.iter().enumerate() + { + if let Transition::Remove(remove_key) = + collapsed_transition + { + if key == remove_key { + // remove(key) + insert(key, val) -> update(key, + // val) + + // Replace the Remove with an Update instead of + // inserting the Insert + *collapsed.get_mut(ix).unwrap() = + Transition::Update(*key, val.clone()); + continue 'outer; + } + } + } + collapsed.push(transition.clone()); + } + Transition::Update(key, value) => { + for (ix, collapsed_transition) in + collapsed.iter().enumerate() + { + if let Transition::Insert(insert_key, _) = + collapsed_transition + { + if key == insert_key { + // insert(key, val) + update(key, new_val) -> + // insert(key, new_val) + + // Replace the insert with the new update's + // value instead of inserting it + *collapsed.get_mut(ix).unwrap() = + Transition::Insert(*key, value.clone()); + continue 'outer; + } + } else if let Transition::Update(update_key, _) = + collapsed_transition + { + if key == update_key { + // update(key, val) + update(key, new_val) -> + // update(key, new_val) + + // Replace the insert with the new update's + // value instead of inserting it + *collapsed.get_mut(ix).unwrap() = + Transition::Update(*key, value.clone()); + continue 'outer; + } + } + } + collapsed.push(transition.clone()); + } + } + } + collapsed + } +} diff --git a/tests/src/storage_api/collections/mod.rs b/tests/src/storage_api/collections/mod.rs index d874b88e22..fc7c5832ce 100644 --- a/tests/src/storage_api/collections/mod.rs +++ b/tests/src/storage_api/collections/mod.rs @@ -1 +1,2 @@ +mod lazy_map; mod lazy_vec; From 8d2ad09fdcf8bce99dd846824f8fe75bbac0a4fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 19 Sep 2022 16:43:20 +0200 Subject: [PATCH 106/197] WIP: Nested LazyMap validation and testing --- .../storage_api/collections/lazy_map.rs | 401 ++++++---- .../storage_api/collections/lazy_vec.rs | 353 ++++----- .../src/ledger/storage_api/collections/mod.rs | 118 ++- shared/src/types/storage.rs | 5 + .../collections/nested_lazy_map.txt | 7 + tests/src/storage_api/collections/lazy_map.rs | 9 +- tests/src/storage_api/collections/lazy_vec.rs | 5 +- tests/src/storage_api/collections/mod.rs | 1 + .../collections/nested_lazy_map.rs | 723 ++++++++++++++++++ 9 files changed, 1290 insertions(+), 332 deletions(-) create mode 100644 tests/proptest-regressions/storage_api/collections/nested_lazy_map.txt create mode 100644 tests/src/storage_api/collections/nested_lazy_map.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 3801848b8f..686f9d336e 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -1,9 +1,11 @@ //! Lazy map. +use std::collections::HashMap; +use std::fmt::Debug; +use std::hash::Hash; use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; -use derivative::Derivative; use thiserror::Error; use super::super::Result; @@ -30,14 +32,18 @@ pub const DATA_SUBKEY: &str = "data"; /// This is different from [`super::LazyHashMap`], which hashes borsh encoded /// key. #[derive(Debug)] -pub struct LazyMap { +pub struct LazyMap { key: storage::Key, phantom_k: PhantomData, phantom_v: PhantomData, + phantom_son: PhantomData, } +/// A `LazyMap` with another `LazyCollection` inside it's value `V` +pub type NestedMap = LazyMap; + /// Possible sub-keys of a [`LazyMap`] -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum SubKey { /// Data sub-key, further sub-keyed by its literal map key Data(K), @@ -45,16 +51,14 @@ pub enum SubKey { /// Possible sub-keys of a [`LazyMap`], together with their [`validation::Data`] /// that contains prior and posterior state. -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum SubKeyWithData { /// Data sub-key, further sub-keyed by its literal map key Data(K, Data), } -/// Possible actions that can modify a [`LazyMap`]. This roughly corresponds to -/// the methods that have `StorageWrite` access. -/// TODO: In a nested collection, `V` may be an action inside the nested -/// collection. +/// Possible actions that can modify a simple (not nested) [`LazyMap`]. This +/// roughly corresponds to the methods that have `StorageWrite` access. #[derive(Clone, Debug)] pub enum Action { /// Insert or update a value `V` at key `K` in a [`LazyMap`]. @@ -72,14 +76,23 @@ pub enum Action { }, } -/// TODO: In a nested collection, `V` may be an action inside the nested -/// collection. -#[derive(Debug)] -pub enum Nested { - /// Insert or update a value `V` at key `K` in a [`LazyMap`]. - Insert(K, V), - /// Remove a value `V` at key `K` from a [`LazyMap`]. - Remove(K, V), +/// Possible actions that can modify a nested [`LazyMap`]. +#[derive(Clone, Debug)] +pub enum NestedAction { + /// Nested collection action `A` at key `K` + At(K, A), +} + +/// Possible sub-keys of a nested [`LazyMap`] +#[derive(Clone, Debug)] +pub enum NestedSubKey { + /// Data sub-key + Data { + /// Literal map key + key: K, + /// Sub-key in the nested collection + nested_sub_key: S, + }, } #[allow(missing_docs)] @@ -87,57 +100,228 @@ pub enum Nested { pub enum ValidationError { #[error("Storage error in reading key {0}")] StorageError(storage::Key), - // #[error("Incorrect difference in LazyVec's length")] - // InvalidLenDiff, - // #[error("An empty LazyVec must be deleted from storage")] - // EmptyVecShouldBeDeleted, - // #[error("Push at a wrong index. Got {got}, expected {expected}.")] - // UnexpectedPushIndex { got: Index, expected: Index }, - // #[error("Pop at a wrong index. Got {got}, expected {expected}.")] - // UnexpectedPopIndex { got: Index, expected: Index }, - // #[error( - // "Update (combination of pop and push) at a wrong index. Got {got}, - // \ expected {expected}." - // )] - // UnexpectedUpdateIndex { got: Index, expected: Index }, - // #[error("An index has overflown its representation: {0}")] - // IndexOverflow(>::Error), - // #[error("Unexpected underflow in `{0} - {0}`")] - // UnexpectedUnderflow(Index, Index), #[error("Invalid storage key {0}")] InvalidSubKey(storage::Key), + #[error("Invalid nested storage key {0}")] + InvalidNestedSubKey(storage::Key), } /// [`LazyMap`] validation result pub type ValidationResult = std::result::Result; -/// [`LazyMap`] validation builder from storage changes. The changes can be -/// accumulated with `LazyMap::validate()` and then turned into a list -/// of valid actions on the map with `ValidationBuilder::build()`. -#[derive(Debug, Derivative)] -// https://mcarton.github.io/rust-derivative/latest/Default.html#custom-bound -#[derivative(Default(bound = ""))] -pub struct ValidationBuilder { - /// The accumulator of found changes under the vector - pub changes: Vec>, +impl LazyCollection for LazyMap +where + K: storage::KeySeg + Clone + Hash + Eq + Debug, + V: LazyCollection + Debug, +{ + type Action = NestedAction::Action>; + type SubKey = NestedSubKey::SubKey>; + type SubKeyWithData = + NestedSubKey::SubKeyWithData>; + type Value = ::Value; + + fn open(key: storage::Key) -> Self { + Self { + key, + phantom_k: PhantomData, + phantom_v: PhantomData, + phantom_son: PhantomData, + } + } + + fn is_valid_sub_key( + &self, + key: &storage::Key, + ) -> storage_api::Result> { + let suffix = match key.split_prefix(&self.key) { + None => { + // not matching prefix, irrelevant + return Ok(None); + } + Some(None) => { + // no suffix, invalid + return Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(); + } + Some(Some(suffix)) => suffix, + }; + + // Match the suffix against expected sub-keys + match &suffix.segments[..2] { + [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { + let nested = self.at(&key_in_kv).is_valid_sub_key(key)?; + match nested { + Some(nested_sub_key) => Ok(Some(NestedSubKey::Data { + key: key_in_kv, + nested_sub_key, + })), + None => Err(ValidationError::InvalidNestedSubKey( + key.clone(), + )) + .into_storage_result(), + } + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + } + _ => Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(), + } + } + + fn read_sub_key_data( + env: &ENV, + storage_key: &storage::Key, + sub_key: Self::SubKey, + ) -> storage_api::Result> + where + ENV: for<'a> VpEnv<'a>, + { + let NestedSubKey::Data { + key, + // In here, we just have a nested sub-key without data + nested_sub_key, + } = sub_key; + // Try to read data from the nested collection + let nested_data = ::read_sub_key_data( + env, + storage_key, + nested_sub_key, + )?; + // If found, transform it back into a `NestedSubKey`, but with + // `nested_sub_key` replaced with the one we read + Ok(nested_data.map(|nested_sub_key| NestedSubKey::Data { + key, + nested_sub_key, + })) + } + + fn validate_changed_sub_keys( + keys: Vec, + ) -> storage_api::Result> { + // We have to group the nested sub-keys by the key from this map + let mut grouped_by_key: HashMap< + K, + Vec<::SubKeyWithData>, + > = HashMap::new(); + for NestedSubKey::Data { + key, + nested_sub_key, + } in keys + { + grouped_by_key + .entry(key) + .or_insert_with(Vec::new) + .push(nested_sub_key); + } + + // Recurse for each sub-keys group + let mut actions = vec![]; + for (key, sub_keys) in grouped_by_key { + let nested_actions = + ::validate_changed_sub_keys(sub_keys)?; + actions.extend( + nested_actions + .into_iter() + .map(|action| NestedAction::At(key.clone(), action)), + ); + } + Ok(actions) + } } -impl LazyCollection for LazyMap +impl LazyCollection for LazyMap where - K: storage::KeySeg, + K: storage::KeySeg + Debug, + V: BorshDeserialize + BorshSerialize + 'static + Debug, { + type Action = Action; + type SubKey = SubKey; + type SubKeyWithData = SubKeyWithData; + type Value = V; + /// Create or use an existing map with the given storage `key`. fn open(key: storage::Key) -> Self { Self { key, phantom_k: PhantomData, phantom_v: PhantomData, + phantom_son: PhantomData, } } + + fn is_valid_sub_key( + &self, + key: &storage::Key, + ) -> storage_api::Result> { + let suffix = match key.split_prefix(&self.key) { + None => { + // not matching prefix, irrelevant + return Ok(None); + } + Some(None) => { + // no suffix, invalid + return Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(); + } + Some(Some(suffix)) => suffix, + }; + + // Match the suffix against expected sub-keys + match &suffix.segments[..] { + [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { + Ok(Some(SubKey::Data(key_in_kv))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + } + _ => Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(), + } + } + + fn read_sub_key_data( + env: &ENV, + storage_key: &storage::Key, + sub_key: Self::SubKey, + ) -> storage_api::Result> + where + ENV: for<'a> VpEnv<'a>, + { + let SubKey::Data(key) = sub_key; + let data = validation::read_data(env, storage_key)?; + Ok(data.map(|data| SubKeyWithData::Data(key, data))) + } + + fn validate_changed_sub_keys( + keys: Vec, + ) -> storage_api::Result> { + Ok(keys + .into_iter() + .map(|change| { + let SubKeyWithData::Data(key, data) = change; + match data { + Data::Add { post } => Action::Insert(key, post), + Data::Update { pre, post } => { + Action::Update { key, pre, post } + } + Data::Delete { pre } => Action::Remove(key, pre), + } + }) + .collect()) + } } // Generic `LazyMap` methods that require no bounds on values `V` -impl LazyMap +impl LazyMap where K: storage::KeySeg, { @@ -162,10 +346,10 @@ where } // `LazyMap` methods with nested `LazyCollection`s `V` -impl LazyMap +impl LazyMap where - K: storage::KeySeg, - V: LazyCollection, + K: storage::KeySeg + Clone + Hash + Eq + Debug, + V: LazyCollection + Debug, { /// Get a nested collection at given key `key`. If there is no nested /// collection at the given key, a new empty one will be provided. The @@ -173,10 +357,39 @@ where pub fn at(&self, key: &K) -> V { V::open(self.get_data_key(key)) } + + /// An iterator visiting all key-value elements, where the values are from + /// the inner-most collection. The iterator element type is `Result<_>`, + /// because iterator's call to `next` may fail with e.g. out of gas or + /// data decoding error. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded maps to avoid gas usage increasing with the length of the + /// map. + pub fn iter<'iter>( + &'iter self, + storage: &'iter impl StorageRead<'iter>, + ) -> Result< + impl Iterator< + Item = Result<( + ::SubKey, + ::Value, + )>, + > + 'iter, + > { + let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; + Ok(iter.map(|key_val_res| { + let (key, val) = key_val_res?; + let sub_key = LazyCollection::is_valid_sub_key(self, &key)? + .ok_or(ReadError::UnexpectedlyEmptyStorageKey) + .into_storage_result()?; + Ok((sub_key, val)) + })) + } } // `LazyMap` methods with borsh encoded values `V` -impl LazyMap +impl LazyMap where K: storage::KeySeg, V: BorshDeserialize + BorshSerialize + 'static, @@ -299,96 +512,6 @@ where ) -> Result<()> { storage.write(storage_key, val) } - - /// Check if the given storage key is a valid LazyMap sub-key and if so - /// return which one - pub fn is_valid_sub_key( - &self, - key: &storage::Key, - ) -> storage_api::Result>> { - let suffix = match key.split_prefix(&self.key) { - None => { - // not matching prefix, irrelevant - return Ok(None); - } - Some(None) => { - // no suffix, invalid - return Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result(); - } - Some(Some(suffix)) => suffix, - }; - - // Match the suffix against expected sub-keys - match &suffix.segments[..] { - [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] - if sub_a == DATA_SUBKEY => - { - if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { - Ok(Some(SubKey::Data(key_in_kv))) - } else { - Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result() - } - } - _ => Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result(), - } - } - - /// Accumulate storage changes inside a [`ValidationBuilder`]. This is - /// typically done by the validity predicate while looping through the - /// changed keys. If the resulting `builder` is not `None`, one must - /// call `fn build()` on it to get the validation result. - /// This function will return `Ok(true)` if the storage key is a valid - /// sub-key of this collection, `Ok(false)` if the storage key doesn't match - /// the prefix of this collection, or fail with - /// [`ValidationError::InvalidSubKey`] if the prefix matches this - /// collection, but the key itself is not recognized. - pub fn accumulate( - &self, - env: &ENV, - builder: &mut Option>, - key_changed: &storage::Key, - ) -> storage_api::Result - where - ENV: for<'a> VpEnv<'a>, - { - if let Some(sub) = self.is_valid_sub_key(key_changed)? { - let SubKey::Data(key) = sub; - let data = validation::read_data(env, key_changed)?; - let change = data.map(|data| SubKeyWithData::Data(key, data)); - if let Some(change) = change { - let builder = - builder.get_or_insert(ValidationBuilder::default()); - builder.changes.push(change); - } - return Ok(true); - } - Ok(false) - } -} - -impl ValidationBuilder -where - K: storage::KeySeg + Ord + Clone, -{ - /// Build a list of actions from storage changes. - pub fn build(self) -> Vec> { - self.changes - .into_iter() - .map(|change| { - let SubKeyWithData::Data(key, data) = change; - match data { - Data::Add { post } => Action::Insert(key, post), - Data::Update { pre, post } => { - Action::Update { key, pre, post } - } - Data::Delete { pre } => Action::Remove(key, pre), - } - }) - .collect() - } } #[cfg(test)] diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index d86f33ec08..fd61bef804 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -1,10 +1,10 @@ //! Lazy dynamically-sized vector. use std::collections::BTreeSet; +use std::fmt::Debug; use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; -use derivative::Derivative; use thiserror::Error; use super::super::Result; @@ -110,18 +110,15 @@ pub enum UpdateError { /// [`LazyVec`] validation result pub type ValidationResult = std::result::Result; -/// [`LazyVec`] validation builder from storage changes. The changes can be -/// accumulated with `LazyVec::validate()` and then turned into a list -/// of valid actions on the vector with `ValidationBuilder::build()`. -#[derive(Debug, Derivative)] -// https://mcarton.github.io/rust-derivative/latest/Default.html#custom-bound -#[derivative(Default(bound = ""))] -pub struct ValidationBuilder { - /// The accumulator of found changes under the vector - pub changes: Vec>, -} +impl LazyCollection for LazyVec +where + T: BorshSerialize + BorshDeserialize + 'static + Debug, +{ + type Action = Action; + type SubKey = SubKey; + type SubKeyWithData = SubKeyWithData; + type Value = T; -impl LazyCollection for LazyVec { /// Create or use an existing vector with the given storage `key`. fn open(key: storage::Key) -> Self { Self { @@ -129,131 +126,10 @@ impl LazyCollection for LazyVec { phantom: PhantomData, } } -} - -// Generic `LazyVec` methods that require no bounds on values `T` -impl LazyVec { - /// Reads the number of elements in the vector. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let len = storage.read(&self.get_len_key())?; - Ok(len.unwrap_or_default()) - } - - /// Returns `true` if the vector contains no elements. - pub fn is_empty(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - Ok(self.len(storage)? == 0) - } - - /// Get the prefix of set's elements storage - fn get_data_prefix(&self) -> storage::Key { - self.key.push(&DATA_SUBKEY.to_owned()).unwrap() - } - - /// Get the sub-key of vector's elements storage - fn get_data_key(&self, index: Index) -> storage::Key { - self.get_data_prefix().push(&index).unwrap() - } - - /// Get the sub-key of vector's length storage - fn get_len_key(&self) -> storage::Key { - self.key.push(&LEN_SUBKEY.to_owned()).unwrap() - } -} - -// `LazyVec` methods with borsh encoded values `T` -impl LazyVec -where - T: BorshSerialize + BorshDeserialize + 'static, -{ - /// Appends an element to the back of a collection. - pub fn push(&self, storage: &mut S, val: T) -> Result<()> - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - let len = self.len(storage)?; - let data_key = self.get_data_key(len); - storage.write(&data_key, val)?; - storage.write(&self.get_len_key(), len + 1) - } - - /// Removes the last element from a vector and returns it, or `Ok(None)` if - /// it is empty. - /// - /// Note that an empty vector is completely removed from storage. - pub fn pop(&self, storage: &mut S) -> Result> - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - let len = self.len(storage)?; - if len == 0 { - Ok(None) - } else { - let index = len - 1; - let data_key = self.get_data_key(index); - if len == 1 { - storage.delete(&self.get_len_key())?; - } else { - storage.write(&self.get_len_key(), index)?; - } - let popped_val = storage.read(&data_key)?; - storage.delete(&data_key)?; - Ok(popped_val) - } - } - - /// Update an element at the given index. - /// - /// The index must be smaller than the length of the vector, otherwise this - /// will fail with `UpdateError::InvalidIndex`. - pub fn update(&self, storage: &mut S, index: Index, val: T) -> Result<()> - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - let len = self.len(storage)?; - if index >= len { - return Err(UpdateError::InvalidIndex { index, len }) - .into_storage_result(); - } - let data_key = self.get_data_key(index); - storage.write(&data_key, val) - } - - /// Read an element at the index or `Ok(None)` if out of bounds. - pub fn get(&self, storage: &S, index: Index) -> Result> - where - S: for<'iter> StorageRead<'iter>, - { - storage.read(&self.get_data_key(index)) - } - - /// An iterator visiting all elements. The iterator element type is - /// `Result`, because iterator's call to `next` may fail with e.g. out of - /// gas or data decoding error. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the - /// set. - pub fn iter<'iter>( - &self, - storage: &'iter impl StorageRead<'iter>, - ) -> Result> + 'iter> { - let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; - Ok(iter.map(|key_val_res| { - let (_key, val) = key_val_res?; - Ok(val) - })) - } /// Check if the given storage key is a valid LazyVec sub-key and if so /// return which one - pub fn is_valid_sub_key( + fn is_valid_sub_key( &self, key: &storage::Key, ) -> storage_api::Result> { @@ -290,50 +166,27 @@ where } } - /// Accumulate storage changes inside a [`ValidationBuilder`]. This is - /// typically done by the validity predicate while looping through the - /// changed keys. If the resulting `builder` is not `None`, one must - /// call `fn build()` on it to get the validation result. - /// This function will return `Ok(true)` if the storage key is a valid - /// sub-key of this collection, `Ok(false)` if the storage key doesn't match - /// the prefix of this collection, or fail with - /// [`ValidationError::InvalidSubKey`] if the prefix matches this - /// collection, but the key itself is not recognized. - pub fn accumulate( - &self, + fn read_sub_key_data( env: &ENV, - builder: &mut Option>, - key_changed: &storage::Key, - ) -> storage_api::Result + storage_key: &storage::Key, + sub_key: Self::SubKey, + ) -> storage_api::Result> where ENV: for<'a> VpEnv<'a>, { - if let Some(sub) = self.is_valid_sub_key(key_changed)? { - let change = match sub { - SubKey::Len => { - let data = validation::read_data(env, key_changed)?; - data.map(SubKeyWithData::Len) - } - SubKey::Data(index) => { - let data = validation::read_data(env, key_changed)?; - data.map(|data| SubKeyWithData::Data(index, data)) - } - }; - if let Some(change) = change { - let builder = - builder.get_or_insert(ValidationBuilder::default()); - builder.changes.push(change); + let change = match sub_key { + SubKey::Len => { + let data = validation::read_data(env, storage_key)?; + data.map(SubKeyWithData::Len) } - return Ok(true); - } - Ok(false) + SubKey::Data(index) => { + let data = validation::read_data(env, storage_key)?; + data.map(|data| SubKeyWithData::Data(index, data)) + } + }; + Ok(change) } -} -impl ValidationBuilder { - /// Validate storage changes and if valid, build from them a list of - /// actions. - /// /// The validation rules for a [`LazyVec`] are: /// - A difference in the vector's length must correspond to the /// difference in how many elements were pushed versus how many elements @@ -342,7 +195,9 @@ impl ValidationBuilder { /// - In addition, we check that indices of any changes are within an /// expected range (i.e. the vectors indices should always be /// monotonically increasing from zero) - pub fn build(self) -> ValidationResult>> { + fn validate_changed_sub_keys( + keys: Vec, + ) -> storage_api::Result> { let mut actions = vec![]; // We need to accumulate some values for what's changed @@ -353,14 +208,15 @@ impl ValidationBuilder { let mut updated = BTreeSet::::default(); let mut deleted = BTreeSet::::default(); - for change in self.changes { - match change { + for key in keys { + match key { SubKeyWithData::Len(data) => match data { Data::Add { post } => { if post == 0 { return Err( ValidationError::EmptyVecShouldBeDeleted, - ); + ) + .into_storage_result(); } post_gt_pre = true; len_diff = post; @@ -369,7 +225,8 @@ impl ValidationBuilder { if post == 0 { return Err( ValidationError::EmptyVecShouldBeDeleted, - ); + ) + .into_storage_result(); } if post > pre { post_gt_pre = true; @@ -403,11 +260,13 @@ impl ValidationBuilder { let added_len: u64 = added .len() .try_into() - .map_err(ValidationError::IndexOverflow)?; + .map_err(ValidationError::IndexOverflow) + .into_storage_result()?; let deleted_len: u64 = deleted .len() .try_into() - .map_err(ValidationError::IndexOverflow)?; + .map_err(ValidationError::IndexOverflow) + .into_storage_result()?; if len_diff != 0 && !(if post_gt_pre { @@ -416,7 +275,7 @@ impl ValidationBuilder { added_len + len_diff == deleted_len }) { - return Err(ValidationError::InvalidLenDiff); + return Err(ValidationError::InvalidLenDiff).into_storage_result(); } let mut last_added = Option::None; @@ -430,7 +289,8 @@ impl ValidationBuilder { return Err(ValidationError::UnexpectedPushIndex { got: index, expected, - }); + }) + .into_storage_result(); } } else if index != len_pre { // The first addition must be at the pre length value. @@ -440,7 +300,8 @@ impl ValidationBuilder { return Err(ValidationError::UnexpectedPushIndex { got: index, expected: len_pre, - }); + }) + .into_storage_result(); } last_added = Some(index); } @@ -456,7 +317,8 @@ impl ValidationBuilder { return Err(ValidationError::UnexpectedPopIndex { got: index, expected, - }); + }) + .into_storage_result(); } } last_deleted = Some(index); @@ -469,7 +331,8 @@ impl ValidationBuilder { return Err(ValidationError::UnexpectedPopIndex { got: index, expected: len_pre, - }); + }) + .into_storage_result(); } } } @@ -482,7 +345,8 @@ impl ValidationBuilder { return Err(ValidationError::UnexpectedUpdateIndex { got: index, max, - }); + }) + .into_storage_result(); } } @@ -490,6 +354,127 @@ impl ValidationBuilder { } } +// Generic `LazyVec` methods that require no bounds on values `T` +impl LazyVec { + /// Reads the number of elements in the vector. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let len = storage.read(&self.get_len_key())?; + Ok(len.unwrap_or_default()) + } + + /// Returns `true` if the vector contains no elements. + pub fn is_empty(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + Ok(self.len(storage)? == 0) + } + + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() + } + + /// Get the sub-key of vector's elements storage + fn get_data_key(&self, index: Index) -> storage::Key { + self.get_data_prefix().push(&index).unwrap() + } + + /// Get the sub-key of vector's length storage + fn get_len_key(&self) -> storage::Key { + self.key.push(&LEN_SUBKEY.to_owned()).unwrap() + } +} + +// `LazyVec` methods with borsh encoded values `T` +impl LazyVec +where + T: BorshSerialize + BorshDeserialize + 'static, +{ + /// Appends an element to the back of a collection. + pub fn push(&self, storage: &mut S, val: T) -> Result<()> + where + S: StorageWrite + for<'iter> StorageRead<'iter>, + { + let len = self.len(storage)?; + let data_key = self.get_data_key(len); + storage.write(&data_key, val)?; + storage.write(&self.get_len_key(), len + 1) + } + + /// Removes the last element from a vector and returns it, or `Ok(None)` if + /// it is empty. + /// + /// Note that an empty vector is completely removed from storage. + pub fn pop(&self, storage: &mut S) -> Result> + where + S: StorageWrite + for<'iter> StorageRead<'iter>, + { + let len = self.len(storage)?; + if len == 0 { + Ok(None) + } else { + let index = len - 1; + let data_key = self.get_data_key(index); + if len == 1 { + storage.delete(&self.get_len_key())?; + } else { + storage.write(&self.get_len_key(), index)?; + } + let popped_val = storage.read(&data_key)?; + storage.delete(&data_key)?; + Ok(popped_val) + } + } + + /// Update an element at the given index. + /// + /// The index must be smaller than the length of the vector, otherwise this + /// will fail with `UpdateError::InvalidIndex`. + pub fn update(&self, storage: &mut S, index: Index, val: T) -> Result<()> + where + S: StorageWrite + for<'iter> StorageRead<'iter>, + { + let len = self.len(storage)?; + if index >= len { + return Err(UpdateError::InvalidIndex { index, len }) + .into_storage_result(); + } + let data_key = self.get_data_key(index); + storage.write(&data_key, val) + } + + /// Read an element at the index or `Ok(None)` if out of bounds. + pub fn get(&self, storage: &S, index: Index) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { + storage.read(&self.get_data_key(index)) + } + + /// An iterator visiting all elements. The iterator element type is + /// `Result`, because iterator's call to `next` may fail with e.g. out of + /// gas or data decoding error. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. + pub fn iter<'iter>( + &self, + storage: &'iter impl StorageRead<'iter>, + ) -> Result> + 'iter> { + let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; + Ok(iter.map(|key_val_res| { + let (_key, val) = key_val_res?; + Ok(val) + })) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index b3e1b4af0c..b77b207c7f 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -7,21 +7,27 @@ //! just receive the storage sub-keys that have experienced changes without //! having to check any of the unchanged elements. +use std::fmt::Debug; + +use borsh::BorshDeserialize; +use derivative::Derivative; use thiserror::Error; mod hasher; -pub mod lazy_hashmap; -pub mod lazy_hashset; +// pub mod lazy_hashmap; +// pub mod lazy_hashset; pub mod lazy_map; -pub mod lazy_set; +// pub mod lazy_set; pub mod lazy_vec; -pub use lazy_hashmap::LazyHashMap; -pub use lazy_hashset::LazyHashSet; +// pub use lazy_hashmap::LazyHashMap; +// pub use lazy_hashset::LazyHashSet; pub use lazy_map::LazyMap; -pub use lazy_set::LazySet; +// pub use lazy_set::LazySet; pub use lazy_vec::LazyVec; +use crate::ledger::storage_api; +use crate::ledger::vp_env::VpEnv; use crate::types::storage; #[allow(missing_docs)] @@ -31,6 +37,14 @@ pub enum ReadError { UnexpectedlyEmptyStorageKey, } +/// Simple lazy collection with borsh deserializable elements +#[derive(Debug)] +pub struct Simple; + +/// Lazy collection with a nested lazy collection +#[derive(Debug)] +pub struct Nested; + /// A lazy collection of storage values is a handler with some storage prefix /// that is given to its `fn new()`. The values are typically nested under this /// prefix and they can be changed individually (e.g. without reading in the @@ -40,6 +54,98 @@ pub enum ReadError { /// /// An empty collection must be deleted from storage. pub trait LazyCollection { + /// Actions on the collection determined from changed storage keys by + /// `Self::validate` + type Action; + + /// Possible sub-keys in the collection + type SubKey: Debug; + + /// Possible sub-keys together with the data read from storage + type SubKeyWithData: Debug; + + /// A type of a value in the inner-most collection + type Value: BorshDeserialize; + /// Create or use an existing vector with the given storage `key`. fn open(key: storage::Key) -> Self; + + /// Check if the given storage key is a valid LazyVec sub-key and if so + /// return which one. Returns: + /// - `Ok(Some(_))` if it's a valid sub-key + /// - `Ok(None)` if it's not a sub-key + /// - `Err(_)` if it's an invalid sub-key + fn is_valid_sub_key( + &self, + key: &storage::Key, + ) -> storage_api::Result>; + + /// Try to read and decode the data for each change storage key in prior and + /// posterior state. If there is no value in neither prior or posterior + /// state (which is a possible state when transaction e.g. writes and then + /// deletes one storage key, but it is treated as a no-op as it doesn't + /// affect result of validation), returns `Ok(None)`. + fn read_sub_key_data( + env: &ENV, + storage_key: &storage::Key, + sub_key: Self::SubKey, + ) -> storage_api::Result> + where + ENV: for<'a> VpEnv<'a>; + + /// Validate changed sub-keys associated with their data and return back + /// a vector of `Self::Action`s, if the changes are valid + fn validate_changed_sub_keys( + keys: Vec, + ) -> storage_api::Result>; + + /// Accumulate storage changes inside a `ValidationBuilder`. This is + /// typically done by the validity predicate while looping through the + /// changed keys. If the resulting `builder` is not `None`, one must + /// call `fn build()` on it to get the validation result. + /// This function will return `Ok(true)` if the storage key is a valid + /// sub-key of this collection, `Ok(false)` if the storage key doesn't match + /// the prefix of this collection, or fail with + /// [`ValidationError::InvalidSubKey`] if the prefix matches this + /// collection, but the key itself is not recognized. + fn accumulate( + &self, + env: &ENV, + builder: &mut Option>, + key_changed: &storage::Key, + ) -> storage_api::Result + where + ENV: for<'a> VpEnv<'a>, + { + if let Some(sub) = self.is_valid_sub_key(key_changed)? { + let change = Self::read_sub_key_data(env, key_changed, sub)?; + if let Some(change) = change { + let builder = + builder.get_or_insert(ValidationBuilder::default()); + builder.changes.push(change); + } + return Ok(true); + } + Ok(false) + } + + /// Execute validation on the validation builder, to be called when + /// `accumulate` instantiates the builder to `Some(_)`, after all the + /// changes storage keys have been processed. + fn validate( + builder: ValidationBuilder, + ) -> storage_api::Result> { + Self::validate_changed_sub_keys(builder.changes) + } +} + +/// Validation builder from storage changes. The changes can +/// be accumulated with `LazyCollection::accumulate()` and then turned into a +/// list of valid actions on the collection with `LazyCollection::validate()`. +#[derive(Debug, Derivative)] +// https://mcarton.github.io/rust-derivative/latest/Default.html#custom-bound +#[derivative(Default(bound = ""))] +pub struct ValidationBuilder { + /// The accumulator of found changes under the vector + pub changes: Vec, } diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 1c3f6b1313..c9f87908fe 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -285,6 +285,11 @@ impl Key { self.len() == 0 } + /// Returns the first segment of the key, or `None` if it is empty. + pub fn first(&self) -> Option<&DbKeySeg> { + self.segments.first() + } + /// Returns the last segment of the key, or `None` if it is empty. pub fn last(&self) -> Option<&DbKeySeg> { self.segments.last() diff --git a/tests/proptest-regressions/storage_api/collections/nested_lazy_map.txt b/tests/proptest-regressions/storage_api/collections/nested_lazy_map.txt new file mode 100644 index 0000000000..d587a9680e --- /dev/null +++ b/tests/proptest-regressions/storage_api/collections/nested_lazy_map.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc b5ce7502439712f95a4b50de0d5455e0a6788cc95dbd535e749d5717da0ee8e1 # shrinks to (initial_state, transitions) = (AbstractLazyMapState { valid_transitions: [], committed_transitions: [] }, [Insert((22253647846329582, -2060910714, -85), TestVal { x: 16862967849328560500, y: true })]) diff --git a/tests/src/storage_api/collections/lazy_map.rs b/tests/src/storage_api/collections/lazy_map.rs index c7a309ab4d..afff09bbf1 100644 --- a/tests/src/storage_api/collections/lazy_map.rs +++ b/tests/src/storage_api/collections/lazy_map.rs @@ -137,7 +137,9 @@ mod tests { Transition::Update(key, val) ), 3 => arb_existing_map_key().prop_map(Transition::Remove), - 5 => (arb_map_key().prop_filter("insert on non-existing keys only", move |key| !keys.contains(&key)), arb_map_val()).prop_map(|(key, val)| Transition::Insert(key, val)) + 5 => (arb_map_key().prop_filter("insert on non-existing keys only", + move |key| !keys.contains(key)), arb_map_val()) + .prop_map(|(key, val)| Transition::Insert(key, val)) ] .boxed() } @@ -426,7 +428,10 @@ mod tests { validation_builder.is_some(), "If some keys were changed, the builder must get filled in" ); - let actions = validation_builder.unwrap().build(); + let actions = LazyMap::::validate( + validation_builder.unwrap(), + ) + .unwrap(); let mut actions_to_check = actions.clone(); // Check that every transition has a corresponding action from diff --git a/tests/src/storage_api/collections/lazy_vec.rs b/tests/src/storage_api/collections/lazy_vec.rs index 20ee80592d..65e08b4ca7 100644 --- a/tests/src/storage_api/collections/lazy_vec.rs +++ b/tests/src/storage_api/collections/lazy_vec.rs @@ -418,7 +418,10 @@ mod tests { validation_builder.is_some(), "If some keys were changed, the builder must get filled in" ); - let actions = validation_builder.unwrap().build().expect( + let actions = LazyVec::::validate( + validation_builder.unwrap(), + ) + .expect( "With valid transitions only, validation should always \ pass", ); diff --git a/tests/src/storage_api/collections/mod.rs b/tests/src/storage_api/collections/mod.rs index fc7c5832ce..f39b880c09 100644 --- a/tests/src/storage_api/collections/mod.rs +++ b/tests/src/storage_api/collections/mod.rs @@ -1,2 +1,3 @@ mod lazy_map; mod lazy_vec; +mod nested_lazy_map; diff --git a/tests/src/storage_api/collections/nested_lazy_map.rs b/tests/src/storage_api/collections/nested_lazy_map.rs new file mode 100644 index 0000000000..80b066c18f --- /dev/null +++ b/tests/src/storage_api/collections/nested_lazy_map.rs @@ -0,0 +1,723 @@ +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + use std::convert::TryInto; + + use borsh::{BorshDeserialize, BorshSerialize}; + use namada::types::address::{self, Address}; + use namada::types::storage; + use namada_tx_prelude::storage::KeySeg; + use namada_tx_prelude::storage_api::collections::lazy_map::{ + NestedMap, NestedSubKey, SubKey, + }; + use namada_tx_prelude::storage_api::collections::{ + lazy_map, LazyCollection, LazyMap, + }; + use proptest::prelude::*; + use proptest::prop_state_machine; + use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; + use proptest::test_runner::Config; + use test_log::test; + + use crate::tx::tx_host_env; + use crate::vp::vp_host_env; + + prop_state_machine! { + #![proptest_config(Config { + // Instead of the default 256, we only run 5 because otherwise it + // takes too long and it's preferable to crank up the number of + // transitions instead, to allow each case to run for more epochs as + // some issues only manifest once the model progresses further. + // Additionally, more cases will be explored every time this test is + // executed in the CI. + cases: 5, + .. Config::default() + })] + #[test] + fn nested_lazy_map_api_state_machine_test(sequential 1..100 => ConcreteLazyMapState); + } + + /// Some borsh-serializable type with arbitrary fields to be used inside + /// LazyMap state machine test + #[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + )] + struct TestVal { + x: u64, + y: bool, + } + + type KeyOuter = u64; + type KeyMiddle = i32; + type KeyInner = i8; + + type NestedTestMap = + NestedMap>>; + + type NestedEagerMap = + BTreeMap>>; + + /// A `StateMachineTest` implemented on this struct manipulates it with + /// `Transition`s, which are also being accumulated into + /// `current_transitions`. It then: + /// + /// - checks its state against an in-memory `std::collections::HashMap` + /// - runs validation and checks that the `LazyMap::Action`s reported from + /// validation match with transitions that were applied + /// + /// Additionally, one of the transitions is to commit a block and/or + /// transaction, during which the currently accumulated state changes are + /// persisted, or promoted from transaction write log to block's write log. + #[derive(Debug)] + struct ConcreteLazyMapState { + /// Address is used to prefix the storage key of the `lazy_map` in + /// order to simulate a transaction and a validity predicate + /// check from changes on the `lazy_map` + address: Address, + /// In the test, we apply the same transitions on the `lazy_map` as on + /// `eager_map` to check that `lazy_map`'s state is consistent with + /// `eager_map`. + eager_map: NestedEagerMap, + /// Handle to a lazy map with nested lazy collections + lazy_map: NestedTestMap, + /// Valid LazyMap changes in the current transaction + current_transitions: Vec, + } + + #[derive(Clone, Debug, Default)] + struct AbstractLazyMapState { + /// Valid LazyMap changes in the current transaction + valid_transitions: Vec, + /// Valid LazyMap changes committed to storage + committed_transitions: Vec, + } + + /// Possible transitions that can modify a [`NestedTestMap`]. + /// This roughly corresponds to the methods that have `StorageWrite` + /// access and is very similar to [`Action`] + #[derive(Clone, Debug)] + enum Transition { + /// Commit all valid transitions in the current transaction + CommitTx, + /// Commit all valid transitions in the current transaction and also + /// commit the current block + CommitTxAndBlock, + /// Insert a key-val into a [`LazyMap`] + Insert(Key, TestVal), + /// Remove a key-val from a [`LazyMap`] + Remove(Key), + /// Update a value at key from pre to post state in a + /// [`LazyMap`] + Update(Key, TestVal), + } + + /// A key for transition + type Key = (KeyOuter, KeyMiddle, KeyInner); + + impl AbstractStateMachine for AbstractLazyMapState { + type State = Self; + type Transition = Transition; + + fn init_state() -> BoxedStrategy { + Just(Self::default()).boxed() + } + + // Apply a random transition to the state + fn transitions(state: &Self::State) -> BoxedStrategy { + let length = state.len(); + if length == 0 { + prop_oneof![ + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => (arb_map_key(), arb_map_val()).prop_map(|(key, val)| Transition::Insert(key, val)) + ] + .boxed() + } else { + let keys = state.find_existing_keys(); + let arb_existing_map_key = + || proptest::sample::select(keys.clone()); + prop_oneof![ + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => (arb_existing_map_key(), arb_map_val()).prop_map(|(key, val)| + Transition::Update(key, val)), + 3 => arb_existing_map_key().prop_map(Transition::Remove), + 5 => (arb_map_key().prop_filter( + "insert on non-existing keys only", + move |key| !keys.contains(key)), arb_map_val()) + .prop_map(|(key, val)| Transition::Insert(key, val)) + ] + .boxed() + } + } + + fn apply_abstract( + mut state: Self::State, + transition: &Self::Transition, + ) -> Self::State { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + let valid_actions_to_commit = + std::mem::take(&mut state.valid_transitions); + state + .committed_transitions + .extend(valid_actions_to_commit.into_iter()); + } + _ => state.valid_transitions.push(transition.clone()), + } + state + } + + fn preconditions( + state: &Self::State, + transition: &Self::Transition, + ) -> bool { + let length = state.len(); + // Ensure that the remove or update transitions are not applied + // to an empty state + if length == 0 + && matches!( + transition, + Transition::Remove(_) | Transition::Update(_, _) + ) + { + return false; + } + match transition { + Transition::Update(key, _) | Transition::Remove(key) => { + let keys = state.find_existing_keys(); + // Ensure that the update/remove key is an existing one + keys.contains(key) + } + Transition::Insert(key, _) => { + let keys = state.find_existing_keys(); + // Ensure that the insert key is not an existing one + !keys.contains(key) + } + _ => true, + } + } + } + + impl StateMachineTest for ConcreteLazyMapState { + type Abstract = AbstractLazyMapState; + type ConcreteState = Self; + + fn init_test( + _initial_state: ::State, + ) -> Self::ConcreteState { + // Init transaction env in which we'll be applying the transitions + tx_host_env::init(); + + // The lazy_map's path must be prefixed by the address to be able + // to trigger a validity predicate on it + let address = address::testing::established_address_1(); + tx_host_env::with(|env| env.spawn_accounts([&address])); + let lazy_map_prefix: storage::Key = address.to_db_key().into(); + + Self { + address, + eager_map: BTreeMap::new(), + lazy_map: NestedTestMap::open( + lazy_map_prefix.push(&"arbitrary".to_string()).unwrap(), + ), + current_transitions: vec![], + } + } + + fn apply_concrete( + mut state: Self::ConcreteState, + transition: ::Transition, + ) -> Self::ConcreteState { + // Apply transitions in transaction env + let ctx = tx_host_env::ctx(); + + // Persist the transitions in the current tx, or clear previous ones + // if we're committing a tx + match &transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + state.current_transitions = vec![]; + } + _ => { + state.current_transitions.push(transition.clone()); + } + } + + // Transition application on lazy map and post-conditions: + match &transition { + Transition::CommitTx => { + // commit the tx without committing the block + tx_host_env::with(|env| env.write_log.commit_tx()); + } + Transition::CommitTxAndBlock => { + // commit the tx and the block + tx_host_env::commit_tx_and_block(); + } + Transition::Insert( + (key_outer, key_middle, key_inner), + value, + ) => { + let inner = state.lazy_map.at(key_outer).at(key_middle); + + inner.insert(ctx, *key_inner, value.clone()).unwrap(); + + // Post-conditions: + let stored_value = + inner.get(ctx, key_inner).unwrap().unwrap(); + assert_eq!( + &stored_value, value, + "the new item must be added to the back" + ); + + state.assert_validation_accepted(); + } + Transition::Remove((key_outer, key_middle, key_inner)) => { + let inner = state.lazy_map.at(key_outer).at(key_middle); + + let removed = + inner.remove(ctx, key_inner).unwrap().unwrap(); + + // Post-conditions: + assert_eq!( + &removed, + state + .eager_map + .get(key_outer) + .unwrap() + .get(key_middle) + .unwrap() + .get(key_inner) + .unwrap(), + "removed element matches the value in eager map \ + before it's updated" + ); + + state.assert_validation_accepted(); + } + Transition::Update( + (key_outer, key_middle, key_inner), + value, + ) => { + let inner = state.lazy_map.at(key_outer).at(key_middle); + + let old_val = inner.get(ctx, key_inner).unwrap().unwrap(); + + inner.insert(ctx, *key_inner, value.clone()).unwrap(); + + // Post-conditions: + let new_val = inner.get(ctx, key_inner).unwrap().unwrap(); + assert_eq!( + &old_val, + state + .eager_map + .get(key_outer) + .unwrap() + .get(key_middle) + .unwrap() + .get(key_inner) + .unwrap(), + "old value must match the value at the same key in \ + the eager map before it's updated" + ); + assert_eq!( + &new_val, value, + "new value must match that which was passed into the \ + Transition::Update" + ); + + state.assert_validation_accepted(); + } + } + + // Apply transition in the eager map for comparison + apply_transition_on_eager_map(&mut state.eager_map, &transition); + + // Global post-conditions: + + // All items in eager map must be present in lazy map + for (key_outer, middle) in state.eager_map.iter() { + for (key_middle, inner) in middle { + for (key_inner, expected_item) in inner { + let got = state + .lazy_map + .at(key_outer) + .at(key_middle) + .get(ctx, key_inner) + .unwrap() + .expect( + "The expected item must be present in lazy map", + ); + assert_eq!( + expected_item, &got, + "at key {key_outer}, {key_middle} {key_inner}" + ); + } + } + } + + // All items in lazy map must be present in eager map + for key_val in state.lazy_map.iter(ctx).unwrap() { + let ( + NestedSubKey::Data { + key: key_outer, + nested_sub_key: + NestedSubKey::Data { + key: key_middle, + nested_sub_key: SubKey::Data(key_inner), + }, + }, + expected_val, + ) = key_val.unwrap(); + let got = state + .eager_map + .get(&key_outer) + .unwrap() + .get(&key_middle) + .unwrap() + .get(&key_inner) + .expect("The expected item must be present in eager map"); + assert_eq!( + &expected_val, got, + "at key {key_outer}, {key_middle} {key_inner})" + ); + } + + state + } + } + + impl AbstractLazyMapState { + /// Find the length of the map from the applied transitions + fn len(&self) -> u64 { + (map_len_diff_from_transitions(self.committed_transitions.iter()) + + map_len_diff_from_transitions(self.valid_transitions.iter())) + .try_into() + .expect( + "It shouldn't be possible to underflow length from all \ + transactions applied in abstract state", + ) + } + + /// Build an eager map from the committed and current transitions + fn eager_map(&self) -> NestedEagerMap { + let mut eager_map = BTreeMap::new(); + for transition in &self.committed_transitions { + apply_transition_on_eager_map(&mut eager_map, transition); + } + for transition in &self.valid_transitions { + apply_transition_on_eager_map(&mut eager_map, transition); + } + eager_map + } + + /// Find the keys currently present in the map + fn find_existing_keys(&self) -> Vec { + let outer_map = self.eager_map(); + outer_map + .into_iter() + .fold(vec![], |acc, (outer, middle_map)| { + middle_map.into_iter().fold( + acc, + |mut acc, (middle, inner_map)| { + acc.extend( + inner_map + .into_iter() + .map(|(inner, _)| (outer, middle, inner)), + ); + acc + }, + ) + }) + } + } + + /// Find the difference in length of the map from the applied transitions + fn map_len_diff_from_transitions<'a>( + transitions: impl Iterator, + ) -> i64 { + let mut insert_count: i64 = 0; + let mut remove_count: i64 = 0; + + for trans in transitions { + match trans { + Transition::CommitTx + | Transition::CommitTxAndBlock + | Transition::Update(_, _) => {} + Transition::Insert(_, _) => insert_count += 1, + Transition::Remove(_) => remove_count += 1, + } + } + insert_count - remove_count + } + + impl ConcreteLazyMapState { + fn assert_validation_accepted(&self) { + // Init the VP env from tx env in which we applied the map + // transitions + let tx_env = tx_host_env::take(); + vp_host_env::init_from_tx(self.address.clone(), tx_env, |_| {}); + + // Simulate a validity predicate run using the lazy map's validation + // helpers + let changed_keys = + vp_host_env::with(|env| env.all_touched_storage_keys()); + + let mut validation_builder = None; + + // Push followed by pop is a no-op, in which case we'd still see the + // changed keys for these actions, but they wouldn't affect the + // validation result and they never get persisted, but we'd still + // them as changed key here. To guard against this case, + // we check that `map_len_from_transitions` is not empty. + let map_len_diff = + map_len_diff_from_transitions(self.current_transitions.iter()); + + // To help debug validation issues... + dbg!( + &self.current_transitions, + &changed_keys + .iter() + .map(storage::Key::to_string) + .collect::>() + ); + + for key in &changed_keys { + let is_sub_key = self + .lazy_map + .accumulate( + vp_host_env::ctx(), + &mut validation_builder, + key, + ) + .unwrap(); + + assert!( + is_sub_key, + "We're only modifying the lazy_map's keys here. Key: \ + \"{key}\", map length diff {map_len_diff}" + ); + } + if !changed_keys.is_empty() && map_len_diff != 0 { + assert!( + validation_builder.is_some(), + "If some keys were changed, the builder must get filled in" + ); + let actions = + NestedTestMap::validate(validation_builder.unwrap()) + .unwrap(); + let mut actions_to_check = actions.clone(); + + // Check that every transition has a corresponding action from + // validation. We drop the found actions to check that all + // actions are matched too. + let current_transitions = + normalize_transitions(&self.current_transitions); + for transition in ¤t_transitions { + use lazy_map::Action; + use lazy_map::NestedAction::At; + + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + } + Transition::Insert(expected_key, expected_val) => { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let At( + key_outer, + At( + key_middle, + Action::Insert(key_inner, val), + ), + ) = action + { + let key = + (*key_outer, *key_middle, *key_inner); + if expected_key == &key + && expected_val == val + { + actions_to_check.remove(ix); + break; + } + } + } + } + Transition::Remove(expected_key) => { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let At( + key_outer, + At( + key_middle, + Action::Remove(key_inner, _val), + ), + ) = action + { + let key = + (*key_outer, *key_middle, *key_inner); + if expected_key == &key { + actions_to_check.remove(ix); + break; + } + } + } + } + Transition::Update(expected_key, value) => { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let At( + key_outer, + At( + key_middle, + Action::Update { + key: key_inner, + pre: _, + post, + }, + ), + ) = action + { + let key = + (*key_outer, *key_middle, *key_inner); + if expected_key == &key && post == value { + actions_to_check.remove(ix); + break; + } + } + } + } + } + } + + assert!( + actions_to_check.is_empty(), + "All the actions reported from validation {actions:#?} \ + should have been matched with SM transitions \ + {current_transitions:#?}, but these actions didn't \ + match: {actions_to_check:#?}", + ) + } + + // Put the tx_env back before checking the result + tx_host_env::set_from_vp_env(vp_host_env::take()); + } + } + + /// Generate an arbitrary `TestKey` + fn arb_map_key() -> impl Strategy { + (any::(), any::(), any::()) + } + + /// Generate an arbitrary `TestVal` + fn arb_map_val() -> impl Strategy { + (any::(), any::()).prop_map(|(x, y)| TestVal { x, y }) + } + + /// Apply `Transition` on an eager `Map`. + fn apply_transition_on_eager_map( + map: &mut NestedEagerMap, + transition: &Transition, + ) { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => {} + Transition::Insert((key_outer, key_middle, key_inner), value) + | Transition::Update((key_outer, key_middle, key_inner), value) => { + let middle = + map.entry(*key_outer).or_insert_with(Default::default); + let inner = + middle.entry(*key_middle).or_insert_with(Default::default); + inner.insert(*key_inner, value.clone()); + } + Transition::Remove((key_outer, key_middle, key_inner)) => { + let middle = + map.entry(*key_outer).or_insert(Default::default()); + let inner = + middle.entry(*key_middle).or_insert_with(Default::default); + let _popped = inner.remove(key_inner); + } + } + } + + /// Normalize transitions: + /// - remove(key) + insert(key, val) -> update(key, val) + /// - insert(key, val) + update(key, new_val) -> insert(key, new_val) + /// - update(key, val) + update(key, new_val) -> update(key, new_val) + /// + /// Note that the normalizable transitions pairs do not have to be directly + /// next to each other, but their order does matter. + fn normalize_transitions(transitions: &[Transition]) -> Vec { + let mut collapsed = vec![]; + 'outer: for transition in transitions { + match transition { + Transition::CommitTx + | Transition::CommitTxAndBlock + | Transition::Remove(_) => collapsed.push(transition.clone()), + Transition::Insert(key, val) => { + for (ix, collapsed_transition) in + collapsed.iter().enumerate() + { + if let Transition::Remove(remove_key) = + collapsed_transition + { + if key == remove_key { + // remove(key) + insert(key, val) -> update(key, + // val) + + // Replace the Remove with an Update instead of + // inserting the Insert + *collapsed.get_mut(ix).unwrap() = + Transition::Update(*key, val.clone()); + continue 'outer; + } + } + } + collapsed.push(transition.clone()); + } + Transition::Update(key, value) => { + for (ix, collapsed_transition) in + collapsed.iter().enumerate() + { + if let Transition::Insert(insert_key, _) = + collapsed_transition + { + if key == insert_key { + // insert(key, val) + update(key, new_val) -> + // insert(key, new_val) + + // Replace the insert with the new update's + // value instead of inserting it + *collapsed.get_mut(ix).unwrap() = + Transition::Insert(*key, value.clone()); + continue 'outer; + } + } else if let Transition::Update(update_key, _) = + collapsed_transition + { + if key == update_key { + // update(key, val) + update(key, new_val) -> + // update(key, new_val) + + // Replace the insert with the new update's + // value instead of inserting it + *collapsed.get_mut(ix).unwrap() = + Transition::Update(*key, value.clone()); + continue 'outer; + } + } + } + collapsed.push(transition.clone()); + } + } + } + collapsed + } +} From 4adf30130baa0152a5738f8f84a89124f8bd65b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Sep 2022 18:01:30 +0200 Subject: [PATCH 107/197] ledger/storage/lazy: remove unused error cases --- shared/src/ledger/storage_api/collections/lazy_map.rs | 2 -- shared/src/ledger/storage_api/collections/lazy_vec.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 686f9d336e..6e32b5399b 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -98,8 +98,6 @@ pub enum NestedSubKey { #[allow(missing_docs)] #[derive(Error, Debug)] pub enum ValidationError { - #[error("Storage error in reading key {0}")] - StorageError(storage::Key), #[error("Invalid storage key {0}")] InvalidSubKey(storage::Key), #[error("Invalid nested storage key {0}")] diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index fd61bef804..59eaa225e5 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -75,8 +75,6 @@ pub enum Action { #[allow(missing_docs)] #[derive(Error, Debug)] pub enum ValidationError { - #[error("Storage error in reading key {0}")] - StorageError(storage::Key), #[error("Incorrect difference in LazyVec's length")] InvalidLenDiff, #[error("An empty LazyVec must be deleted from storage")] From 4b22589549c6ec09de5fed371ace1cd27a3cc801 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 22 Sep 2022 11:16:33 +0200 Subject: [PATCH 108/197] [misc] rebase --- apps/src/lib/client/tx.rs | 1 - apps/src/lib/node/ledger/shell/governance.rs | 20 ++++++++-------- tests/src/e2e/ledger_tests.rs | 24 ++++++++++---------- wasm_for_tests/wasm_source/Cargo.lock | 24 ++++++++++---------- 4 files changed, 34 insertions(+), 35 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index ab900fc732..bf7c99714e 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -17,7 +17,6 @@ use namada::types::governance::{ use namada::types::key::*; use namada::types::nft::{self, Nft, NftToken}; use namada::types::storage::Epoch; -use namada::types::token::Amount; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 180c0a9af4..50f7adf40b 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -1,15 +1,15 @@ -use anoma::ledger::governance::storage as gov_storage; -use anoma::ledger::governance::utils::{ +use namada::ledger::governance::storage as gov_storage; +use namada::ledger::governance::utils::{ compute_tally, get_proposal_votes, ProposalEvent, }; -use anoma::ledger::governance::vp::ADDRESS as gov_address; -use anoma::ledger::storage::types::encode; -use anoma::ledger::storage::{DBIter, StorageHasher, DB}; -use anoma::ledger::slash_fund::ADDRESS as slash_fund_address; -use anoma::types::address::{xan as m1t, Address}; -use anoma::types::governance::TallyResult; -use anoma::types::storage::Epoch; -use anoma::types::token; +use namada::ledger::governance::vp::ADDRESS as gov_address; +use namada::ledger::storage::types::encode; +use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::ledger::slash_fund::ADDRESS as slash_fund_address; +use namada::types::address::{xan as m1t, Address}; +use namada::types::governance::TallyResult; +use namada::types::storage::Epoch; +use namada::types::token; use super::*; use crate::node::ledger::events::EventType; diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 037bdb64d8..af54f4677a 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1046,7 +1046,7 @@ fn proposal_submission() -> Result<()> { client.assert_success(); // 2. Submit valid proposal - let test_dir = tempfile::tempdir_in(test.base_dir.path()).unwrap(); + let test_dir = tempfile::tempdir_in(test.test_dir.path()).unwrap(); let proposal_code = wasm_abs_path(TX_PROPOSAL_CODE); let albert = find_address(&test, ALBERT)?; @@ -1064,9 +1064,9 @@ fn proposal_submission() -> Result<()> { "requires": "2" }, "author": albert, - "voting_start_epoch": 12, - "voting_end_epoch": 24, - "grace_epoch": 30, + "voting_start_epoch": 12 as u64, + "voting_end_epoch": 24 as u64, + "grace_epoch": 30 as u64, "proposal_code_path": proposal_code.to_str().unwrap() } ); @@ -1164,9 +1164,9 @@ fn proposal_submission() -> Result<()> { eros.", "requires": "2" }, "author": albert, - "voting_start_epoch": 9999, - "voting_end_epoch": 10000, - "grace_epoch": 10009, + "voting_start_epoch": 9999 as u64, + "voting_end_epoch": 10000 as u64, + "grace_epoch": 10009 as u64, } ); let invalid_proposal_file = @@ -1393,8 +1393,8 @@ fn proposal_offline() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // 2. Create an offline proposal - let test_dir = tempfile::tempdir_in(test.base_dir.path()).unwrap(); + // 2. Create an offline + let test_dir = tempfile::tempdir_in(test.test_dir.path()).unwrap(); let albert = find_address(&test, ALBERT)?; let valid_proposal_json = json!( @@ -1411,9 +1411,9 @@ fn proposal_offline() -> Result<()> { "requires": "2" }, "author": albert, - "voting_start_epoch": 3, - "voting_end_epoch": 9, - "grace_epoch": 18 + "voting_start_epoch": 3 as u64, + "voting_end_epoch": 9 as u64, + "grace_epoch": 18 as u64 } ); let proposal_file = diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 9df5195b2f..876455fcaa 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1357,7 +1357,7 @@ dependencies = [ [[package]] name = "libsecp256k1" version = "0.7.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", @@ -1373,7 +1373,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1383,7 +1383,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1527,7 +1527,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1584,7 +1584,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -1593,7 +1593,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1611,7 +1611,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1619,7 +1619,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -1629,7 +1629,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1637,7 +1637,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "getrandom", From 3863caa36f4a34b547fd74357cf466708bf34378 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 22 Sep 2022 12:15:11 +0200 Subject: [PATCH 109/197] fix proposal_submission e2e test --- apps/src/lib/client/rpc.rs | 6 +++--- apps/src/lib/client/tx.rs | 7 ++++--- apps/src/lib/node/ledger/protocol/mod.rs | 2 +- apps/src/lib/node/ledger/shell/governance.rs | 11 ++++------ shared/src/ledger/mod.rs | 2 +- shared/src/types/address.rs | 4 +++- tests/src/e2e/ledger_tests.rs | 21 ++++++++++--------- vm_env/src/lib.rs | 2 +- vp_prelude/src/lib.rs | 7 ++++--- wasm_for_tests/tx_memory_limit.wasm | Bin 135502 -> 135517 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 226185 -> 231855 bytes wasm_for_tests/tx_no_op.wasm | Bin 25027 -> 25030 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 214371 -> 212604 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 152558 -> 152605 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 149083 -> 154634 bytes wasm_for_tests/vp_always_false.wasm | Bin 160359 -> 160865 bytes wasm_for_tests/vp_always_true.wasm | Bin 161066 -> 161197 bytes wasm_for_tests/vp_eval.wasm | Bin 161675 -> 162157 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 163364 -> 163530 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 170912 -> 170224 bytes 20 files changed, 32 insertions(+), 30 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index ea86cfd314..d19ded990c 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -12,20 +12,20 @@ use async_std::path::PathBuf; use async_std::prelude::*; use borsh::BorshDeserialize; use itertools::Itertools; +use namada::ledger::governance::parameters::GovParams; use namada::ledger::governance::storage as gov_storage; use namada::ledger::governance::utils::Votes; -use namada::types::governance::VotePower; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::types::{ Epoch as PosEpoch, VotingPower, WeightedValidator, }; -use namada::ledger::governance::parameters::GovParams; use namada::ledger::pos::{ self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, }; use namada::types::address::Address; use namada::types::governance::{ - OfflineProposal, OfflineVote, ProposalVote, TallyResult, ProposalResult, + OfflineProposal, OfflineVote, ProposalResult, ProposalVote, TallyResult, + VotePower, }; use namada::types::key::*; use namada::types::storage::{Epoch, PrefixValue}; diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index bf7c99714e..2bac35a896 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -571,8 +571,8 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { { eprintln!( "Invalid proposal end epoch: difference between proposal start \ - and end epoch must be at least {} and at max {} and end epoch must \ - be a multiple of {}", + and end epoch must be at least {} and at max {} and end epoch \ + must be a multiple of {}", governance_parameters.min_proposal_period, governance_parameters.max_proposal_period, governance_parameters.min_proposal_period @@ -634,7 +634,8 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let balance = rpc::get_token_balance(&client, &m1t(), &proposal.author) .await .unwrap_or_default(); - if balance < token::Amount::from(governance_parameters.min_proposal_fund) + if balance + < token::Amount::from(governance_parameters.min_proposal_fund) { eprintln!( "Address {} doesn't have enough funds.", diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index c8069d5a9c..bd40f7f843 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -9,9 +9,9 @@ use namada::ledger::ibc::vp::{Ibc, IbcToken}; use namada::ledger::native_vp::{self, NativeVp}; use namada::ledger::parameters::{self, ParametersVp}; use namada::ledger::pos::{self, PosVP}; +use namada::ledger::slash_fund::SlashFundVp; use namada::ledger::storage::write_log::WriteLog; use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; -use namada::ledger::slash_fund::SlashFundVp; use namada::proto::{self, Tx}; use namada::types::address::{Address, InternalAddress}; use namada::types::storage; diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 50f7adf40b..a1d17110eb 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -3,9 +3,9 @@ use namada::ledger::governance::utils::{ compute_tally, get_proposal_votes, ProposalEvent, }; use namada::ledger::governance::vp::ADDRESS as gov_address; +use namada::ledger::slash_fund::ADDRESS as slash_fund_address; use namada::ledger::storage::types::encode; use namada::ledger::storage::{DBIter, StorageHasher, DB}; -use namada::ledger::slash_fund::ADDRESS as slash_fund_address; use namada::types::address::{xan as m1t, Address}; use namada::types::governance::TallyResult; use namada::types::storage::Epoch; @@ -14,6 +14,7 @@ use namada::types::token; use super::*; use crate::node::ledger::events::EventType; +#[derive(Default)] pub struct ProposalsResult { passed: Vec, rejected: Vec, @@ -28,10 +29,7 @@ where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, H: StorageHasher + Sync + 'static, { - let mut proposals_result = ProposalsResult { - passed: Vec::new(), - rejected: Vec::new(), - }; + let mut proposals_result = ProposalsResult::default(); if !new_epoch { return Ok(proposals_result); @@ -55,8 +53,7 @@ where ) })?; - let votes = - get_proposal_votes(&shell.storage, proposal_end_epoch, id); + let votes = get_proposal_votes(&shell.storage, proposal_end_epoch, id); let tally_result = compute_tally(&shell.storage, proposal_end_epoch, votes); diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 8f0f049683..a317b509d8 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -7,6 +7,6 @@ pub mod ibc; pub mod native_vp; pub mod parameters; pub mod pos; -pub mod storage; pub mod slash_fund; +pub mod storage; pub mod vp_env; diff --git a/shared/src/types/address.rs b/shared/src/types/address.rs index a1ff9702ea..6f047730fe 100644 --- a/shared/src/types/address.rs +++ b/shared/src/types/address.rs @@ -185,7 +185,9 @@ impl Address { InternalAddress::Governance => { internal::GOVERNANCE.to_string() } - InternalAddress::SlashFund => internal::SLASH_FUND.to_string(), + InternalAddress::SlashFund => { + internal::SLASH_FUND.to_string() + } InternalAddress::IbcEscrow(hash) => { format!("{}::{}", PREFIX_INTERNAL, hash) } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index af54f4677a..777090d4d4 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1064,9 +1064,9 @@ fn proposal_submission() -> Result<()> { "requires": "2" }, "author": albert, - "voting_start_epoch": 12 as u64, - "voting_end_epoch": 24 as u64, - "grace_epoch": 30 as u64, + "voting_start_epoch": 12_u64, + "voting_end_epoch": 24_u64, + "grace_epoch": 30_u64, "proposal_code_path": proposal_code.to_str().unwrap() } ); @@ -1164,9 +1164,9 @@ fn proposal_submission() -> Result<()> { eros.", "requires": "2" }, "author": albert, - "voting_start_epoch": 9999 as u64, - "voting_end_epoch": 10000 as u64, - "grace_epoch": 10009 as u64, + "voting_start_epoch": 9999_u64, + "voting_end_epoch": 10000_u64, + "grace_epoch": 10009_u64, } ); let invalid_proposal_file = @@ -1186,7 +1186,8 @@ fn proposal_submission() -> Result<()> { let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; client.exp_string( "Invalid proposal end epoch: difference between proposal start and \ - end epoch must be at least 3 and end epoch must be a multiple of 3", + end epoch must be at least 3 and at max 27 and end epoch must be a \ + multiple of 3", )?; client.assert_failure(); @@ -1411,9 +1412,9 @@ fn proposal_offline() -> Result<()> { "requires": "2" }, "author": albert, - "voting_start_epoch": 3 as u64, - "voting_end_epoch": 9 as u64, - "grace_epoch": 18 as u64 + "voting_start_epoch": 3_u64, + "voting_end_epoch": 9_u64, + "grace_epoch": 18_u64 } ); let proposal_file = diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 2d3cab9e8c..8902a64ed1 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -18,8 +18,8 @@ pub mod token; pub mod tx_prelude { pub use namada::ledger::governance::storage; pub use namada::ledger::parameters::storage as parameters_storage; - pub use namada::ledger::storage::types::encode; pub use namada::ledger::slash_fund::storage as slash_fund_storage; + pub use namada::ledger::storage::types::encode; pub use namada::proto::{Signed, SignedTxData}; pub use namada::types::address::Address; pub use namada::types::storage::Key; diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index daf35b6d06..abf280090a 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -43,14 +43,15 @@ pub fn is_proposal_accepted(proposal_id: u64) -> bool { /// Checks whether a transaction is valid, which happens in two cases: /// - tx is whitelisted, or -/// - tx is executed by an approved governance proposal (no need to be whitelisted) +/// - tx is executed by an approved governance proposal (no need to be +/// whitelisted) pub fn is_valid_tx(tx_data: &[u8]) -> bool { if is_tx_whitelisted() { - return true + true } else { let proposal_id = u64::try_from_slice(tx_data).ok(); - proposal_id.map_or(false, |id| is_proposal_accepted(id)) + proposal_id.map_or(false, is_proposal_accepted) } } diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 88c8ef0ada51f31c8e0f25c2fa633b4ce20ad65b..2bda493fccaf7713bc2e507d9bb61f548ae799ca 100755 GIT binary patch delta 1262 zcmZuwYfKzf6u#%q%VyFIGd?g#cL*l==`vD*PQD&LNK3Jcs7-fhb@rIRxgEg3HPydanO z0)B~c0EsC8G8C%-tnhOQfa31Q6%`cl&iwR5!rU3IjYC_eMBkAry}P9>7-}x7uo4Z8 zO?S0-WLsPI?hywL?&}H_tvOG^Ek!$ZZi$I=1p0o=dyn7YZ+H)uFBYaO7GNefxlr?_ zY=XWIvuet-J%^KOtrXGz#3cwh=hZEMQ_gHr1wb0V(&B{c+Ps|Zk-PA+7Qs1x1Eif5 z|D&`Flf?#9JBN!mB=3EkD6%y!UHT4iZeuW*0S)g7 zzJ@=vR~bHL(lo8n{OrKy*4!_%tGDKJnoL1wC3qe}g$kLe2w&cm@?y6sjq6c76TS)t zj+8%0c(MEy!gS;fFq}(~-$2?b)^NQwj2EK8fS~j1xNb=TdJSBOKEmxBCJmbg=Ylcm zp)zW${ZQo`v#5qjT&TSSHTXm`i^HF$`qtE4WR3D@}}9 zAR^C+$n%yg*kFJ~aUj!O{GtJ%Atvp59Q%AXKThw5#BR+DslkePYjsaL0Y!7&UL<{UMzl0X?bbKG8uY3 ze%iGh7mJ^`cFYx#I#4HJ7APVVx=qiEYlT_oj9613iTU2{5<6!xgE$5~LdwXP?u+7D z@BNf!Q{QCx|JA}T`d$jy{d6`Qo}3IYreRrs@Af-&TbthPyD@v^1Q?+;xy;N(nm-y@=fdBOC^CZs011K0Gn_useJ= dcoF>0^w7^h!{`yOjzeRKshP2bnK8Kh`M={O-6K^Kpx#w{o z=R4=l?{zm=d$@$8OV?-CO-LIhV!qnc#v;Ju6fJ zW)G(Hglo-*6M97P8Q1VU`0UgAb$~g0Iy- zDGcKUK?vFd1cch_)dc_mj)A>^kjvSl^cb!*Yx9j=>-Vj5&!*!fBBy$919$VfPFRa2Pmrr z1{SrI;&OS3Ay|}~mLj2l9F^cM&W3O-I7Px<2~N1^ro+}2D7W9X?%_~|*CJ;?;7rXh z;mw)_%J;|Gr-8Gy-5Y&~msT-D{_`p^8f1phoOFmWfwQq{h~TA|pzUtP8facuzZ0tM z1NB!FsKH(F5aDF}XhjJv11aRiw`dPX(d4(9oX=?(OANbwVdhm0Tayg(&b)3p5ND0Wrm#J@lCJ>&WYKXc>qaKnUIIFoCvg?||03jS`*-!XY%L?I4>$1cDD{b!)Io0iZII(=V zV~p@>M+ko?sYFj_H~28qxsTF6+xd9hZ-kk+m1?1rz&a8CO*}+_WYkNB$|R;!Nng3K zfozl(IrOX}!EC@IsVE%8GpW}?wX`Ld(GC`(fRB&UbydOWU5@W2?xjRGrzcAPzs~q|`q=`Ord?rRVj`b0gW=NPGjIj>XC9~$ zT9m7nTOJs6QhAc)1$M~qj2uCe*p)@P3!;w diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index c9a102773e28ced047f374606582082da3d839d0..cb4bc94490309c7b58874752bd50018a050d5ba8 100755 GIT binary patch delta 23752 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 delta 17923 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 diff --git a/wasm_for_tests/tx_no_op.wasm b/wasm_for_tests/tx_no_op.wasm index bdab4054d9fe3a54897713c81028396ec6a24f0e..649a1b72f152044560b29408aec863baf09a4e4b 100755 GIT binary patch delta 286 zcmX?nnDN+Q#tF)*_4N!1Om%e(39R)Xz*wKaSkG9;%*(^d!pP3Z#KyqP#K1h!BZY&L zi<_05gKOf%Eos~v8V@iyo&XX8Y?D*M?2y(%4Yv^ALRg8G z!m8*q*d%tAK7ioFR5`_WIGpBttFWr@X0cHekh2gV$ACpH!zdVdIwd43WWY7!&}$tT zsdUXI$yM*+*bchS;O`^qh8lP>v2Sr>_Wl3LcnXd==FO>79_uyw!F4tMXo+1j*rF$P z5UZTwD+~wZGO$wsdtS44_1Jqm*kTk?KE}mT=`*tcm4p+?NBHH diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 3c753377ed91ac3086bd2c55079414182be5aa0b..decd2575af60b98c5b27e1f5f6775753990bf8a1 100755 GIT binary patch delta 16853 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 delta 18654 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 R)t)hm%YA6i7;p2~{|nVN^vD1J diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index b4266e69403f1fadf79e23f7bb0a1bcfda421c68..74e1abc492bfc3d5cb06a4a4b434fdb73043ce06 100755 GIT binary patch delta 5246 zcmcIo3viUx6~6cGzwd1FZ#LOHHYtA=NHz}=j3FTpG#7~pB%n12STQ6bj4p&g62wGg z14-mj2MI@>wIm@z5|BJxM6DL%W27C?$_!H-iIr-c+KRR+ww)2%p8M}+^Fy7^(9xab z{{MH*x#ygF&bhDui(d0D`^>v?%Jn|+Bh_rvCQ=;$)K^V?bW=4U)f9=65I~wiJk>X2 z;7t&9n^X$+rkgr^3DG9qiZu_~YSz@%)+}p$Xj%P=n)`^oV<>r%iBhJclrUX=SX zC^b->Yzen9d}TyWsg6dNQ9}J=7Q_r+je;QQ4eA345>02$OpcR?Ag$S;^$ zcyn%YTKcUf{oMI^x;x_HU5aP=4cRy56yG%aw%cdsmo1oi%bb#VTKv=77gjF4 zr|Pb|%Pgbt7JY%W_+cB%r&s*1vRLZ4^2`WPSS}-0#mr^z6k;8PZtoVTleRMEg9~et zAidsBtzfI01f}{#v=k2McVKHP%r!1x7(E{Y`)W5lYOu#z6{;|KS-c`COwoB!D<{@p zr-sAkQYRFpn&B&ti(4wOMZ(`A5^X|TQ~@#JW26@@nw_xIBBt6(4cIA!Kbme}LA8%tEU?cqqmsvSihi;ZTg?Asx?hpw~3@@ZExGOx_=9&-*WYd2* zTu*=0f{mAzA65SeIXMDzW5JOHb7S{^Bo|OBs}Yxxu15Sb8UfQCmnnyT6~+5+j@nD} z&5B;k%l4;3^Hk4qUVayfF!H|%f#AF}mDtP>?o70q6$6hJ^U}cEj>Al62NtgR#O&Bj zy<$#SMG@gdmcp!X(K(r#L5j(%3ZrUq-)5uMiRu=`_(N2IRa8NyrnKA8_dsb&!P1sQ z8Do8qhspvAV<%%A)b&Xd2X;*y*nh{tDB>^_E8@TcabO}2@5e5UYU>*zJP#*D7xnV} zEeN*t_solG&YBgxa%Y0mq^fL<3vaKLI;HoXF!u4wB?+;R9>Y~-K8%dXmN z4Nk)01U4iO3loVDMW9l08LsLI;jEG4{#BKb5HqG~a1$FkgF+@RmJyZjf{ujgh5*lh zCgCMX)^XoF#l$1L{NHpPXXOA%N11P)ztH`mE>pAd&}%jxDiW*NcqqRBWyuxS+vU~d zSz?2rDap)N0jVjUvY~Epc;<>nm1l|ADxUXLBn0!z_)IVVjW)f+`Zy@5e_$g$yVIPk z>j(^HM8mYS=}3TONO@wM;(*SyS$BN=RA}cUVgR3MG0xzxwHPbnTP;?+y!D8t|5PvE zb_6oh<9w&IVyO1C7NgoXv>4UCt;MMJ_gai#50Bv2- zVzjmSDX7ax4kqh-4<>7-uR|+?Mh<8(8tK+zw9&7{Xk%E5(Z;C|4wE$Fi9ip>4qrW< zONg(E-DRx++Pb2}Xsa!x8?~XW?I9ed)7T0}?sM9%IINeo7>9Kxh(odaomK#Cy{pA& zYxFQgWXAb+XvwH|w-%$`gIbJ3IIP8}_Lvr<+QAUU)nua7!f*&5;6w{m7Ml75@l7ED z+WK58fVRHTVzl+07Ne~#hvAdV&cq^xd6+3lhl`ho5w{+X!bt=-KX#jhhvm+!`HU`r zFS1fBR;gG)hBrxqSF&RLxzk7KWVLg#8xK?-Hq+~t_79@DCEX+-M#IXTGm%QL>B>})?sd2V*; z75ijrJnIm ziI+crbcFCOJmIN((tfcGP3EJ=5R2_Agu$Hu0zC<@<=>30uHYVQYYLpE;CA=A-&b%a zHC6_%6&q_)b{C1ehrK9@e(Fyy+C#;i0fb6oJ79_UulL79VQ+o zyLFtFy5Ll46)yAOEc1!geqY&>7L?AZe30Tg?0!%UoraZQh$F6td1!jSb#EX;etu`unR3UKhvKyR1mq&L7hKM1f>#R>g|FVp9lkkJM z4Wcn~XZpX0ru~AGe&bUr7?XJE9uR@j8Q}Ni-q_&;ab4y>NIM}`vsjt%>_V(zWcJvXJ)>Rc{?R)R=mS+E!!-{y z;V}-cYGC&+!M8*DT&mvhuzc1^j|OjekwuDx8bx}A<5;eN>)yAk?Aj~d_leC}{R(8> zJBM{0g^sn++RTkhV4(YeNMdJ!7dpTySlV80=dZ0OsNk|e}}9;mj?V|O`gTnpis zVA6(GdKr2)6r_hd#no#pFb=$u3&S!R{!ok(=d##FulS7*EunNDR5!WU)@R_6rg--E zVMuy7p8g5mX>!r`L~D3s7`sHm<|Ze5R^_t(Vdy}iE+}pmA0#$n_x53+8{-Y%4xy|C zWn*Bd*<~0ThfeygAh0)w)SkZr?-Q+|U63Q8ra6WVh>nK84vZUM9}fs0 z*!clChm2PQ!yy!~Qv+fQ?EDG!(LVsg&GGDzN;Di0VtUoiV=(f_{bEX{ZAzrR$6iE+ zXt2WFxB`ez9;}$RAq7_vshDpI@Q{;4#r`8WoEG53RapvEJ77jg8RmzT*W+=Pl<<~d zH(v!(X?Z=8iIrIU@VgRzaG}1*coJIW)j# z`EYsEdzj=l@{Q}<2HYazkz!B`=qN5-F8|S%KT}B}3WaBV8{l;7W=6|^w=ETq&bF2G z3>exM(`=|}chMYwbNh0tFLTIb34HWuVerkpwGLUkx902E<`n;1JD#$bbK+zoemT&* z=SG;nKhILRx~AcN<<|9U?t_l~F4(x=1jGB=oa2J0?J{B50Y9vfckEDp;B@$|NSWx6 zw4$MrJQ)cw2i>eM5(*Edv9pnIVb5awoee8+60fYUS-!lcwq{LM=Drat%Ys#VV}g$D z*qgR~}5Kw9mi$7e8m9Ka|#5dg!^*L$vqV{{Y%~RVn}g delta 5207 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 TN+5e@V#~6f&n(+X+n@dyL@{59 diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index 89b669f6af9c1826ece9718b94c1767b2b95d1f3..19ce5cff37807e416555878e60fe60182b205024 100755 GIT binary patch delta 12266 zcmcIq3w%|@nV*?+&b>Fe_a-L+NeB_ny#%;X9^sKh6U9t0@(A*(($*&kk#f-hA!zNk zoQPm)7u(dKyR6zaDoO;>(#>v-Ke5Iw-O~QLHLkd&Re!Q;^cT18Zrbhck8XYJ|C@8p zy*IoH?QY=QGv7Be-^};T_nMjW`tH0h_vLktb%pD)RfbEy9FK{TE^>F3*tU(WvIyY< z;x6B|ilJ2oiPqcj=XXMrkZwzj!(NGs#x~s@#P~L`+B8~P?-{kab>rQuZ*TwX?RQPs zxbF5X?QQE@*Re2F#WPiHEvwhE;;%hB(~TR;mYN1COAy~c~CncEjzthE=Nml7baO#+&~LE zEq`^WpQRY9VGM92E#C%Q$ylw1Uq7U}3dR?A5uq>$J85}b6H)^OkBh$jYD-~tNRz%& zeYdZ1TxpJF(cdXqd}B)fc1N}+#8D$+xt$@O9XqLkdZG9W#!K}! zxUHWNBLd=;?kogRy$Z&Y=0e2-ugUL9%@|7ygb03Q5v3CQT4BK}anxxpW?p8(qgD zc%9nU4?&n1%&ZYi8VNJTqmT~jX!xW(jj^Ov$A%2YmOPyRR`LxA^Wl}FUB@aAu%M`D za7<|~1rN5gye1Z!IChHhBp8xB5t1wsi3!b^$5O!`%~HTo*kh@HSaONnxmn7OI;Im@ zXf}{w|F6dPhj+}zM>^EXlF~mshX^fv)QRXhgqq@UniWYZ1ZhX?6=n2-05RL9IL9g& zB>lWP_O^h8A@(r1#3EQuUV$)7frXO$thD4dZm1w z3J>K{vti(3z)j6II|Wo6s5x0wM<@-%RcZ3~q>*xmMl@`|th9_AtDc`Abu=njtQHD} zB2ds&AWR)wj%kJb(9%>%uCDw6Q1MDb#|$B#2NcUdy@2vF&_O_X8R$ts;Q;a#nmtNm zf|MfJbcNDMtI-$5UI5_$=mZegfa?f)?+T%PzX53lP5J=j2f$$fF(05;w}Ixnz`kc( z#dvoc1RaLh3hd%GdzAc`Z-$kd@+GzbepSdmEO4CW*==}>(+xzS>+zsh>o5>Sjzv6a zxF2xXlZJ+m0k%AOXm|jy=}ClOqTLy)HOTTS!BGM}M(#zMm}6#WcIrOaG2C66s1~Fq z+5tH1Er^Cskx-nX41r;ii&GfDG*p*aYrr-jz+mxo&ptrGzy&#Mbq5Patz6j!P_L@) zv=@+Gae|2W5TvDj-Pg5%ziwt!M|J`e41nbTl;x`fP?o7vB6o~oK*1PpW{@Fro$sPb z$RAl`X`W7t8SIBp;z5Bpmlv~gydD|W#oK-m(|v#JMw{Y!P830=qod2HMXZM3&H7

IoUa)29zi2SX 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 F{~xhol_3BC delta 17771 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˭ zszvNfT$K~U(Mv%{nlFe2u{o0AWVaKP%2POLMv9?zD1VUK5xH93CC3?%stLxR#T z$Uy+Yz)wmq$d@;|%FqA*q3OGj)YFo4t%~PWC&~N7WJ4g?dtK%r^y(vqw$>04#n2GE z#qBGxI7()6I6KO+^!4YOQII$m~|Unv{S+6V9M<5J}MTS9!)C zhH1;t@H#FONBWf66!OV8@@S@4hoN4y26bhLiGH66>ys$bCt7lAsMQ#gSseyulS~lU ztr4H0TxYmau*ehY7%VocicZDNSRJN`8mIF#gj8qT`2Fb=br_xRSJ*d(7WEoiIz8>l zZ@)-m#Tg@e03Q+1E-B7TvmTSC(_WY4U}4&`OGg=m=~Cacd@g}%zSnvOVJjuM%4&^7 z08JP3iq2-^vZd7<>PO00pjm9NsVd85l?e(oi?8E=>NrdL6NVcc8Fif3!s`YT{kZplLeA0w zWE=bd3OT;L*z?HdcT{fhM=k3ny{ZtKQk_X7R>DFvC_z@u zBiRJ4KB6gS{rv=#K(;7n#%`3LW(*m!vjhYu)Ykne0p7IgjdwPFraykYA3v|)dD-|G zjVF*(wMi?y#hC>!#i}{tB>8h|SHoa_Cs(uj2=CsxX@7s6Ox8klg=V(O{{nION4T-c zI&F*`Bc|A|d0Yy*O=uN7+F244doZQ*)Ndj`6K=jQ9Tt1Xsyi)F0_Q2asYWJVL0C1l@AFMn`){4s}}fp4meM4zac$$5kCoeOXzZ zprUH!n6fIz8$)2~T(2m1s6tWhKVi*6zra zmtL?ehqMgJau=w|5l{4bk*1R>D;K5S$Pokjb6D{@G;O%J@wes`T|G&~E*iYJC>P^Bzqr^d;o+x1pQ7Yts&$5Fj*qO&$Nou*mh z#M>lL+w@@R!T0@nOI>P3L3+NELM!}wwu42^btbnwD#U4<#nk)tsy%gdipPyJEpI5hB_ZZ`KkEjF0ZC?XV4J(`iw4+JTY*A2Sc80SYiDPczMu^c^~d;^j5Ma6edpN7i6Fb&SgH8zp+Dt?&ZjX z24%}rkA2Qsu=1;bfIe3~?)zY!SD{X5dF1h*4_`0->hWLbLIW%Xh!^RXx1pm_2;%*vI2={&@Yj|ERffItYRpJ|lee zC*;F&!6cssp9-HTKGv_e@q1V0ZzM^unwF(KW>?(nx!RJ^B$(qm&u0yvwS3m`c`sx4 zoqz0=wpDFQAIGP|C*`wr(!rMh@OzIm|DLJ#(&L{2Wvlqq@BQ}13vKx9*m*Wwx=eF|2Vw3_|!|&8!zF0??3++pSb-qzx1h3{8Nw2-VQJ2*#e&) zGB*^*U)md9R=oYvba?sk>Hn}fdjAzXhF5|s`7EWw%J$xrSJCfkKG!_-$oGHuk?)7a G@BCj5Qc0x% diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 51c75909071d53cd0913bf4956c1b29fab08141d..ca214b6b2a0a2aac5ba59ac40b49bdf7286fbbda 100755 GIT binary patch delta 17400 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 h)EIR~-*xA~!#8<7ufoBA1?WkY(-oXNd2buq{` z_@N!Yv~$h!ceLO?TK*}jp*cIvPgPcairO>Ae)Pd_Z;vtJ-yWjV#YULcVs=t*XSnd| z)#95AQ~Cd>^y@dE-GBV>HsoJrVM=a;0+^jBVPDKZ%ai5c%6rvoi?qr>a~;z@e$U=# z5p}o~p8Xs&oJ#47{|zn;7VK0}G%a3*3OQHNUop z(4v1|d9R`=g%0h&Kb^gUeDxNfSGfhVNDTD%a%!8(CmPP4%4Q<-Zpered1Q9_s2?ti zT|b~|YD;PW``)SeC)HY5?EBhBHae(QO$V^lX@50mNByq<(g0ui{wbzwJN%ZMY{*Di9p+tK%x!pU}c7nDEH#zc{a7Kgs{Q|LUKFtzeJi7i$X- zaK1K(gJSGk|FHP$r{?_sW5My_s;3|7K7Q}%yNbuY^`D~PrsBiTe3aXfKX|52Zv5ur z=(D`uTx@*q8~XTz=j`Lu^B=8*Hy!z{Z~sa2lVd>;H2ACXH^JXHe@}Kg!6f%}{H^D2 zioa?8di!qReItLH_^b1$|3dx%_1yi6dtSc$*Jg$~He>HhTCw-N3&lJ5e(K}5Jop=* z{`e)qm& cykEuNd&+ihMv~?J55N6`m*4(DSp32N1woktGXMYp delta 18017 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 TmUQ|*efYcII{aO~_@n<1edQwg delta 18564 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 diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index f5d0ee8cc3854aac6cc2f224b244c44364027cb2..673e3796ff897249e06b19b6054312a6cf6aae87 100755 GIT binary patch delta 3238 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 delta 4035 zcmai13vg7`89wLSyPHjRH@PthWb?ecycRSjA;~5oB`ny=-@K{EzOIg?B6pD>2f;kxHg4?;G#l*)7!4x27 z<6Pkhahzk2p@}IDMQ{OV&K9bOv6GKab=$a|F-5iUK3-EpOZjz;b2*dTS$#!Dmge;Z za?55{{KK74PziBL@#Kt=XrznJ#o89YLMxTLGK5&p;Q`e8TjSAZ-d(2|Nkt0EyS5KoRSKl4P1m8jl8M*g^ADlZ@2Tm()!$wJt72h8#dU-W$K_z-QZIt zYjU$R8q8b5pP<8eS6%5anV8RR{=`akeXUxl1Ek7*_aM}l@uNG^rI3p5AU%H(chEro z6dp^DhkswmuWy&qh~zrPXEU4Fv4Fs^m%D(3*G9uDv$r%g^8zZrF(7uw)2%n;)AKjHC`NXZqs8Zo z!z_861tr#DtEzahXhhEgyAQB{s7EV?b}hft5$hBiMWv{ut6GXuRi}$P;f8I#n>%Hi zDC(L{HhhR5=Zx7FN8eAf?8ayo4Z`}gB4{J)?T;MjO85`^x^PH ze-L{bjtLZsUaZthmsG*7)VftTkIs7PZBC=HuFM>lPr=3uzi@tQMob-j6$z1!E z+1cIM%0s$_%b8BY8R4S@uIH^2!1XID2G`5m>8&cyqBFQJQI`?Jr&bKRUs^HD-eENZv-est%x>OJ zrPW(`A^oj7oeTQBdcNRIR8=#LJL$%nXLvcStDQja)=s37+M+O88tp5NwkA-;9RH+3 zvuITMP@w#xB#2MeDLCV2EPxnQsUlw^bnl$0xVxisW{Wc;^vRshpp27sWt^89S#u|H zzcX_6T#=jkG44k|EW;fY0Ri(>qF77@I=+iq=Y1)jdX|&3S%n3RMs zQl#{lcO4~2OQTf_|AC@rELfBgE^cSrCeU4rJr`@K>1T|wbMfrx*!d;tX7Nj1`Y?*$ z?xkho#a;B_(gsr%H2<6z(c8`Yd4aL{x~+Wr5yTp;gc+RU34ok|*2fV|xCC+(2#1qO z%QLB|pho<351lPcvA$r&YbL`t&a9aXHM7l?LdRC$i>!C88T#Iexq95{rAl0YyrA&X zN8R^dEWYKJ6<>NHn1uZPGU(w}U+CQ0M64XAFTadFvWBv|R~xVPWC?uZKj^iAhQ#&BPn!V9+C?=}7~uz}BX$mF%&c)Uy;YrSsWf$+QudYt4zZyY*ZYki4c zhwe6`PSE2`l}Mc%=0=t|Bg3tqTC70I4wq&;*F zue^DNHvDH{8g;>_@iC7}BRek>y51oGPk@!#}cz8p9znkInPZwUF6HRI3 zp&v!t?;L8Q`(D49at|Ly+UTppEoSzXy%B4smR7ylVydw>pEL*CkAw&JADN9;wr>3Q zotNA+}aCK>KS8uAaRhRiFEu#T5JZInUE~HaN>N*IbB1Wv3W@v6VUk25^JG^ z_g|Txk*qNVJzWEX%zv?+4(x)ckf(MVbp3R`x{sX zHv1mITrac_G6^ynG6iB?`~N)1T{_U!Kae|kOYYUHI{Mf2=Jt2}3?p*lpfS8%fqYJ0@lv;c8(QCzdz-HfI zs>WtQ7eERjMUVh}@b_wSr~MzsDIPq{SWnl$K*yS{@GgvVlu*N&bYCe(XF(kHaG<0T#XEwU4Hw+B+_C&VQbkdY^n>rQ{Oe|(w5 TyedfbUA>?Dy7v>l_2B;iOfA5; diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 88fd209412..a9fc8f0fca 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1489,7 +1489,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.1" +version = "0.8.0" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1538,7 +1538,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.1" +version = "0.8.0" dependencies = [ "quote", "syn", @@ -1546,7 +1546,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "derivative", @@ -1556,7 +1556,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.1" +version = "0.8.0" dependencies = [ "chrono", "concat-idents", @@ -1575,7 +1575,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "namada", @@ -1587,7 +1587,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "namada", @@ -1595,7 +1595,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "namada", @@ -1607,7 +1607,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "getrandom", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 18de2af010..5f653b541d 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm_for_tests" resolver = "2" -version = "0.7.1" +version = "0.8.0" [lib] crate-type = ["cdylib"] From 5bc4acac54eeccb223ba5d124844000486c78e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 18 Oct 2022 10:52:28 +0200 Subject: [PATCH 188/197] CI: add workaround for release build missing git tag https://github.com/actions/checkout/issues/882#issuecomment-1231911465 --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b926d268d7..5cbc1e9207 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,6 +35,7 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + - run: git fetch --tags --force origin # WA: https://github.com/actions/checkout/issues/882 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 with: From 366c05bdd965c315a1510ea601b766a2abe692b8 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Tue, 18 Oct 2022 11:16:44 +0200 Subject: [PATCH 189/197] fix: rename anoma to namada, remove author print --- apps/src/lib/cli.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 4534d5bc0d..bdc1de01b7 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -9,7 +9,7 @@ pub mod context; mod utils; -use clap::{crate_authors, AppSettings, ArgGroup, ArgMatches}; +use clap::{AppSettings, ArgGroup, ArgMatches}; use color_eyre::eyre::Result; pub use utils::safe_exit; use utils::*; @@ -18,7 +18,7 @@ pub use self::context::Context; include!("../../version.rs"); -const APP_NAME: &str = "Anoma"; +const APP_NAME: &str = "Namada"; // Main Anoma sub-commands const NODE_CMD: &str = "node"; @@ -2763,7 +2763,6 @@ pub fn anoma_wallet_cli() -> Result<(cmds::AnomaWallet, Context)> { fn anoma_app() -> App { let app = App::new(APP_NAME) .version(anoma_version()) - .author(crate_authors!("\n")) .about("Anoma command line interface.") .setting(AppSettings::SubcommandRequiredElseHelp); cmds::Anoma::add_sub(args::Global::def(app)) @@ -2772,7 +2771,6 @@ fn anoma_app() -> App { fn anoma_node_app() -> App { let app = App::new(APP_NAME) .version(anoma_version()) - .author(crate_authors!("\n")) .about("Anoma node command line interface.") .setting(AppSettings::SubcommandRequiredElseHelp); cmds::AnomaNode::add_sub(args::Global::def(app)) @@ -2781,7 +2779,6 @@ fn anoma_node_app() -> App { fn anoma_client_app() -> App { let app = App::new(APP_NAME) .version(anoma_version()) - .author(crate_authors!("\n")) .about("Anoma client command line interface.") .setting(AppSettings::SubcommandRequiredElseHelp); cmds::AnomaClient::add_sub(args::Global::def(app)) @@ -2790,7 +2787,6 @@ fn anoma_client_app() -> App { fn anoma_wallet_app() -> App { let app = App::new(APP_NAME) .version(anoma_version()) - .author(crate_authors!("\n")) .about("Anoma wallet command line interface.") .setting(AppSettings::SubcommandRequiredElseHelp); cmds::AnomaWallet::add_sub(args::Global::def(app)) From 75dbada4144bd6ef565a8839cd57b2a351db0332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 20 Oct 2022 10:55:56 +0200 Subject: [PATCH 190/197] app: set "namada" set default bin for `cargo run` --- apps/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 47fe704408..883e532a79 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -7,6 +7,7 @@ name = "namada_apps" readme = "../README.md" resolver = "2" version = "0.8.0" +default-run = "namada" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 06a9dac408028ec7c7623d5243f429b41edf811f Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 19 Oct 2022 16:20:54 +0200 Subject: [PATCH 191/197] [feat]: Patched tendermint-rs and ibc-rs to compatible versions --- Cargo.lock | 336 ++++------ Cargo.toml | 15 + apps/Cargo.toml | 12 +- apps/src/lib/config/mod.rs | 2 +- .../lib/node/ledger/shell/finalize_block.rs | 1 - apps/src/lib/node/ledger/shell/init_chain.rs | 1 - apps/src/lib/node/ledger/shell/queries.rs | 2 - shared/Cargo.toml | 13 +- wasm/Cargo.lock | 630 +----------------- wasm/Cargo.toml | 11 + wasm_for_tests/wasm_source/Cargo.lock | 630 +----------------- wasm_for_tests/wasm_source/Cargo.toml | 10 + 12 files changed, 264 insertions(+), 1399 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b1f787daa..b4a6f5d9f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -371,9 +371,9 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", @@ -690,9 +690,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "byte-tools" @@ -1157,9 +1157,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", "syn", @@ -1622,7 +1622,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall 0.2.16", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] @@ -2236,13 +2236,14 @@ dependencies = [ [[package]] name = "ibc" -version = "0.12.0" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "bytes 1.2.1", "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=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ics23", "num-traits 0.2.15", "prost", "prost-types", @@ -2252,10 +2253,10 @@ dependencies = [ "serde_json", "sha2 0.10.6", "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", + "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", ] @@ -2263,13 +2264,13 @@ 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.2.1", "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.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", + "ics23", "num-traits 0.2.15", "prost", "prost-types", @@ -2279,53 +2280,38 @@ dependencies = [ "serde_json", "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)", - "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.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", ] [[package]] name = "ibc-proto" -version = "0.16.0" +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", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tonic", + "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.2.1", "prost", "prost-types", "serde 1.0.145", - "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.2.1", - "hex", - "prost", - "ripemd160", - "sha2 0.9.9", - "sha3", - "sp-std", + "tendermint-proto 0.23.6", ] [[package]] @@ -2881,7 +2867,7 @@ dependencies = [ "libc", "log 0.4.17", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] @@ -2942,11 +2928,11 @@ dependencies = [ "ferveo", "ferveo-common", "group-threshold-cryptography", - "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=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)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", + "ics23", "itertools", "libsecp256k1", "loupe", @@ -2966,10 +2952,10 @@ dependencies = [ "sparse-merkle-tree 0.3.1-pre (git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8)", "sparse-merkle-tree 0.3.1-pre (git+https://github.com/heliaxdev/sparse-merkle-tree?rev=121c79424646cc3e917f8616e549fbddee775a0a)", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", - "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?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", - "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", "tonic-build", @@ -3045,14 +3031,14 @@ dependencies = [ "sysinfo", "tar", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", - "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=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", - "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=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", - "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?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", - "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", @@ -3060,8 +3046,8 @@ dependencies = [ "toml", "tonic", "tower", - "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=21623a99bdca5b006d53752a1967849bef3b89ea)", "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-log", "tracing-subscriber 0.3.16", @@ -3538,7 +3524,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api 0.4.9", - "parking_lot_core 0.9.3", + "parking_lot_core 0.9.4", ] [[package]] @@ -3558,15 +3544,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall 0.2.16", "smallvec 1.10.0", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -4494,7 +4480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ "lazy_static", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] @@ -4841,7 +4827,7 @@ dependencies = [ "blake2b-rs", "borsh", "cfg-if 1.0.0", - "ics23 0.7.0", + "ics23", "sha2 0.9.9", ] @@ -4853,7 +4839,7 @@ dependencies = [ "blake2b-rs", "borsh", "cfg-if 1.0.0", - "ics23 0.7.0", + "ics23", "sha2 0.9.9", ] @@ -4986,35 +4972,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "async-trait", - "bytes 1.2.1", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures 0.3.24", - "num-traits 0.2.15", - "once_cell", - "prost", - "prost-types", - "serde 1.0.145", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "time 0.3.15", - "zeroize", -] - -[[package]] -name = "tendermint" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", "bytes 1.2.1", @@ -5034,15 +4992,15 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", + "tendermint-proto 0.23.5", "time 0.3.15", "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=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "async-trait", "bytes 1.2.1", @@ -5062,7 +5020,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint-proto 0.23.6", "time 0.3.15", "zeroize", ] @@ -5070,38 +5028,25 @@ dependencies = [ [[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.145", - "serde_json", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "toml", - "url 2.3.1", -] - -[[package]] -name = "tendermint-config" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", "serde 1.0.145", "serde_json", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", + "tendermint 0.23.5", "toml", "url 2.3.1", ] [[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=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "flex-error", "serde 1.0.145", "serde_json", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint 0.23.6", "toml", "url 2.3.1", ] @@ -5109,50 +5054,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.145", - "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.15", ] [[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=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "derive_more", "flex-error", "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.15", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "bytes 1.2.1", - "flex-error", - "num-derive", - "num-traits 0.2.15", - "prost", - "prost-types", - "serde 1.0.145", - "serde_bytes", - "subtle-encoding", + "tendermint 0.23.6", "time 0.3.15", ] [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "bytes 1.2.1", "flex-error", @@ -5168,8 +5095,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=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "bytes 1.2.1", "flex-error", @@ -5186,31 +5113,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "bytes 1.2.1", - "flex-error", - "getrandom 0.2.7", - "peg", - "pin-project", - "serde 1.0.145", - "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.15", - "url 2.3.1", - "uuid", - "walkdir", -] - -[[package]] -name = "tendermint-rpc" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", "async-tungstenite", @@ -5228,9 +5131,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", - "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", + "tendermint 0.23.5", + "tendermint-config 0.23.5", + "tendermint-proto 0.23.5", "thiserror", "time 0.3.15", "tokio", @@ -5242,8 +5145,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=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "async-trait", "async-tungstenite", @@ -5261,9 +5164,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.15", "tokio", @@ -5276,7 +5179,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", @@ -5284,14 +5187,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.15", ] [[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=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "ed25519-dalek", "gumdrop", @@ -5299,7 +5202,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.15", ] @@ -5711,13 +5614,13 @@ dependencies = [ [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?rev=21623a99bdca5b006d53752a1967849bef3b89ea#21623a99bdca5b006d53752a1967849bef3b89ea" +source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200#f6463388fc319b6e210503b43b3aecf6faf6b200" dependencies = [ "bytes 1.2.1", "futures 0.3.24", "pin-project", "prost", - "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", @@ -5729,13 +5632,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.2.1", "futures 0.3.24", "pin-project", "prost", - "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", @@ -6651,6 +6554,27 @@ 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" @@ -6663,6 +6587,12 @@ 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" @@ -6675,6 +6605,12 @@ 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" @@ -6687,6 +6623,12 @@ 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" @@ -6699,6 +6641,18 @@ 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" @@ -6711,6 +6665,12 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + [[package]] name = "winreg" version = "0.10.1" diff --git a/Cargo.toml b/Cargo.toml index 08fed3a846..4d63512550 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,21 @@ async-process = {git = "https://github.com/heliaxdev/async-process.git", rev = " # 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 = "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 = "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"} + [profile.release] lto = true opt-level = 3 diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 5b2155ea8a..0039516b23 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -123,11 +123,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} -# branch bat/abciplus -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "2e9e0d058a68e2534974ff7d22b9058d4ebda3be", optional = true} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "2e9e0d058a68e2534974ff7d22b9058d4ebda3be", optional = true} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "2e9e0d058a68e2534974ff7d22b9058d4ebda3be", optional = true} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "2e9e0d058a68e2534974ff7d22b9058d4ebda3be", 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" @@ -136,8 +135,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} -# branch bat/abciplus -tower-abci = {git = "https://github.com/heliaxdev/tower-abci", rev = "21623a99bdca5b006d53752a1967849bef3b89ea", 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/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 8891c98a94..07ba934b10 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -12,7 +12,7 @@ use std::str::FromStr; use namada::types::chain::ChainId; use namada::types::time::Rfc3339String; -use serde::{de, Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::cli; diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index cef644227e..2080b8d23d 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -6,7 +6,6 @@ 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::facade::tendermint_proto::types::ConsensusParams; 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 3056c2332a..8cb6842c50 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 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::tendermint_proto::types::ConsensusParams; use crate::wasm_loader; impl Shell diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 7fe778df7f..05f0bc52d8 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -10,8 +10,6 @@ use namada::types::token::{self, Amount}; 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::response; impl Shell diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 7766668971..8a3a74279a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -86,12 +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 = "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} -# branch bat/abciplus -# XXX need an abciplus and ics23 0.7.0 version here -#ibc = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", rev = "99a761657a51f6e5f074f3217426903e53632934", default-features = false, optional = true} -#ibc-proto = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-rs", rev = "99a761657a51f6e5f074f3217426903e53632934", default-features = false, optional = true} -ibc = {path = "../../ibc-rs/modules", default-features = false, optional = true} -ibc-proto = {path = "../../ibc-rs/proto", 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.0" loupe = {version = "0.1.3", optional = true} @@ -115,9 +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} -# branch bat/abciplus -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "2e9e0d058a68e2534974ff7d22b9058d4ebda3be", optional = true} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "2e9e0d058a68e2534974ff7d22b9058d4ebda3be", optional = true} +tendermint = {version = "0.23.6", optional = true} +tendermint-proto = {version = "0.23.6", optional = true} thiserror = "1.0.30" tracing = "0.1.30" wasmer = {version = "=2.2.0", optional = true} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 7258bd2323..b7e12ea4cf 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -161,32 +161,11 @@ 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.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", @@ -329,9 +308,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytecheck" @@ -891,15 +870,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 = "futures" version = "0.3.24" @@ -1019,25 +989,6 @@ dependencies = [ "syn", ] -[[package]] -name = "h2" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util 0.7.4", - "tracing", -] - [[package]] name = "hashbrown" version = "0.11.2" @@ -1080,76 +1031,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "http" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[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.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper", - "pin-project-lite", - "tokio", - "tokio-io-timeout", -] - [[package]] name = "iana-time-zone" version = "0.1.51" @@ -1176,7 +1057,8 @@ dependencies = [ [[package]] name = "ibc" -version = "0.12.0" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "bytes", "derive_more", @@ -1192,9 +1074,9 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint", "tendermint-light-client-verifier", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-proto", "tendermint-testgen", "time", "tracing", @@ -1202,14 +1084,15 @@ dependencies = [ [[package]] name = "ibc-proto" -version = "0.16.0" +version = "0.17.1" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ + "base64", "bytes", "prost", "prost-types", "serde", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tonic", + "tendermint-proto", ] [[package]] @@ -1234,16 +1117,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 = "indenter" version = "0.3.3" @@ -1466,18 +1339,6 @@ dependencies = [ "adler", ] -[[package]] -name = "mio" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys", -] - [[package]] name = "more-asserts" version = "0.2.2" @@ -1525,8 +1386,8 @@ dependencies = [ "sparse-merkle-tree 0.3.1-pre (git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8)", "sparse-merkle-tree 0.3.1-pre (git+https://github.com/heliaxdev/sparse-merkle-tree?rev=121c79424646cc3e917f8616e549fbddee775a0a)", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", + "tendermint", + "tendermint-proto", "thiserror", "tonic-build", "tracing", @@ -1732,39 +1593,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.4.0" @@ -1785,26 +1613,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 +2081,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" @@ -2426,31 +2225,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" -[[package]] -name = "slab" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg", -] - [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" -[[package]] -name = "socket2" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "sp-std" version = "3.0.0" @@ -2551,36 +2331,8 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "async-trait", - "bytes", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures", - "num-traits", - "once_cell", - "prost", - "prost-types", - "serde", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "time", - "zeroize", -] - -[[package]] -name = "tendermint" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "async-trait", "bytes", @@ -2600,58 +2352,27 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", + "tendermint-proto", "time", "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 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "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=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "derive_more", "flex-error", "serde", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-rpc", - "time", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "bytes", - "flex-error", - "num-derive", - "num-traits", - "prost", - "prost-types", - "serde", - "serde_bytes", - "subtle-encoding", + "tendermint", "time", ] [[package]] name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "bytes", "flex-error", @@ -2665,34 +2386,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 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-config", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "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=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "ed25519-dalek", "gumdrop", @@ -2700,7 +2397,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint", "time", ] @@ -2770,98 +2467,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" -[[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 = "tokio" -version = "1.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" -dependencies = [ - "autocfg", - "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.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-stream" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - [[package]] name = "toml" version = "0.5.9" @@ -2871,37 +2476,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.10", - "tower", - "tower-layer", - "tower-service", - "tracing", - "tracing-futures", -] - [[package]] name = "tonic-build" version = "0.6.2" @@ -2914,38 +2488,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "indexmap", - "pin-project", - "pin-project-lite", - "rand", - "slab", - "tokio", - "tokio-util 0.7.4", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - [[package]] name = "tracing" version = "0.1.37" @@ -2979,16 +2521,6 @@ dependencies = [ "once_cell", ] -[[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.16" @@ -3004,12 +2536,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" @@ -3033,27 +2559,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" -[[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.10.0" @@ -3072,23 +2583,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" -[[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" @@ -3115,27 +2609,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 = "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.11.0+wasi-snapshot-preview1" @@ -3516,49 +2989,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 = "zeroize" version = "1.5.7" @@ -3579,3 +3009,13 @@ dependencies = [ "syn", "synstructure", ] + +[[patch.unused]] +name = "tendermint-config" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" + +[[patch.unused]] +name = "tendermint-rpc" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 1231ef03a8..f962cc4043 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -13,6 +13,17 @@ borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4 borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} +# 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-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 = "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 8ff3d9659c..b7c18ccbd3 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -161,32 +161,11 @@ 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.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", @@ -329,9 +308,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytecheck" @@ -891,15 +870,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 = "futures" version = "0.3.24" @@ -1019,25 +989,6 @@ dependencies = [ "syn", ] -[[package]] -name = "h2" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util 0.7.4", - "tracing", -] - [[package]] name = "hashbrown" version = "0.11.2" @@ -1080,76 +1031,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "http" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[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.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper", - "pin-project-lite", - "tokio", - "tokio-io-timeout", -] - [[package]] name = "iana-time-zone" version = "0.1.51" @@ -1176,7 +1057,8 @@ dependencies = [ [[package]] name = "ibc" -version = "0.12.0" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "bytes", "derive_more", @@ -1192,9 +1074,9 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint", "tendermint-light-client-verifier", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-proto", "tendermint-testgen", "time", "tracing", @@ -1202,14 +1084,15 @@ dependencies = [ [[package]] name = "ibc-proto" -version = "0.16.0" +version = "0.17.1" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ + "base64", "bytes", "prost", "prost-types", "serde", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tonic", + "tendermint-proto", ] [[package]] @@ -1234,16 +1117,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 = "indenter" version = "0.3.3" @@ -1466,18 +1339,6 @@ dependencies = [ "adler", ] -[[package]] -name = "mio" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys", -] - [[package]] name = "more-asserts" version = "0.2.2" @@ -1525,8 +1386,8 @@ dependencies = [ "sparse-merkle-tree 0.3.1-pre (git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8)", "sparse-merkle-tree 0.3.1-pre (git+https://github.com/heliaxdev/sparse-merkle-tree?rev=121c79424646cc3e917f8616e549fbddee775a0a)", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", + "tendermint", + "tendermint-proto", "thiserror", "tonic-build", "tracing", @@ -1726,39 +1587,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.4.0" @@ -1779,26 +1607,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" @@ -2267,15 +2075,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" @@ -2420,31 +2219,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" -[[package]] -name = "slab" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg", -] - [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" -[[package]] -name = "socket2" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "sp-std" version = "3.0.0" @@ -2545,36 +2325,8 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "async-trait", - "bytes", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures", - "num-traits", - "once_cell", - "prost", - "prost-types", - "serde", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "time", - "zeroize", -] - -[[package]] -name = "tendermint" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "async-trait", "bytes", @@ -2594,58 +2346,27 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be)", + "tendermint-proto", "time", "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 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "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=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "derive_more", "flex-error", "serde", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-rpc", - "time", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "bytes", - "flex-error", - "num-derive", - "num-traits", - "prost", - "prost-types", - "serde", - "serde_bytes", - "subtle-encoding", + "tendermint", "time", ] [[package]] name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=2e9e0d058a68e2534974ff7d22b9058d4ebda3be#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "bytes", "flex-error", @@ -2659,34 +2380,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 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-config", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "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=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "ed25519-dalek", "gumdrop", @@ -2694,7 +2391,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint", "time", ] @@ -2764,98 +2461,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" -[[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 = "tokio" -version = "1.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" -dependencies = [ - "autocfg", - "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.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-stream" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - [[package]] name = "toml" version = "0.5.9" @@ -2865,37 +2470,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.10", - "tower", - "tower-layer", - "tower-service", - "tracing", - "tracing-futures", -] - [[package]] name = "tonic-build" version = "0.6.2" @@ -2908,38 +2482,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "indexmap", - "pin-project", - "pin-project-lite", - "rand", - "slab", - "tokio", - "tokio-util 0.7.4", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - [[package]] name = "tracing" version = "0.1.37" @@ -2973,16 +2515,6 @@ dependencies = [ "once_cell", ] -[[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.16" @@ -2998,12 +2530,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" @@ -3016,27 +2542,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" -[[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.10.0" @@ -3055,23 +2566,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" -[[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" @@ -3087,27 +2581,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 = "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.11.0+wasi-snapshot-preview1" @@ -3488,49 +2961,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 = "zeroize" version = "1.5.7" @@ -3551,3 +2981,13 @@ dependencies = [ "syn", "synstructure", ] + +[[patch.unused]] +name = "tendermint-config" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" + +[[patch.unused]] +name = "tendermint-rpc" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 18de2af010..9a82e45cd3 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -37,7 +37,17 @@ borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4 borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} +# 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-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 = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} [dev-dependencies] namada_tests = {path = "../../tests"} From 25a159663bcc9cfa8de30cb1606ca1dfebad8a2b Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 19 Oct 2022 17:08:16 +0200 Subject: [PATCH 192/197] [fix]: Removed unnecessary patches in wasm --- wasm/Cargo.lock | 10 ---------- wasm/Cargo.toml | 2 -- wasm_for_tests/wasm_source/Cargo.lock | 10 ---------- wasm_for_tests/wasm_source/Cargo.toml | 2 -- 4 files changed, 24 deletions(-) diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index b7e12ea4cf..ea36b5b9e4 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3009,13 +3009,3 @@ dependencies = [ "syn", "synstructure", ] - -[[patch.unused]] -name = "tendermint-config" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" - -[[patch.unused]] -name = "tendermint-rpc" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index f962cc4043..d6f164a445 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -15,9 +15,7 @@ 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 = "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"} diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index b7c18ccbd3..dcbb765e35 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2981,13 +2981,3 @@ dependencies = [ "syn", "synstructure", ] - -[[patch.unused]] -name = "tendermint-config" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" - -[[patch.unused]] -name = "tendermint-rpc" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 9a82e45cd3..0c3586121f 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -39,9 +39,7 @@ 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 = "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"} From f3e8fd4475fdeb2e78813826fd9ad0904556e001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 19 Oct 2022 18:01:55 +0200 Subject: [PATCH 193/197] test/e2e: update expected string for non-validator node --- tests/src/e2e/ledger_tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index d3eff1314f..f5a8e66b42 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -51,7 +51,7 @@ fn run_ledger() -> Result<()> { let mut ledger = run_as!(test, Who::NonValidator, Bin::Node, args, Some(40))?; ledger.exp_string("Anoma ledger node started")?; - ledger.exp_string("This node is a fullnode")?; + ledger.exp_string("This node is not a validator")?; } Ok(()) @@ -80,7 +80,7 @@ fn test_node_connectivity() -> Result<()> { let mut non_validator = run_as!(test, Who::NonValidator, Bin::Node, args, Some(40))?; non_validator.exp_string("Anoma ledger node started")?; - non_validator.exp_string("This node is a fullnode")?; + non_validator.exp_string("This node is not a validator")?; let bg_validator_0 = validator_0.background(); let bg_validator_1 = validator_1.background(); @@ -1820,7 +1820,7 @@ fn test_genesis_validators() -> Result<()> { let mut non_validator = run_as!(test, Who::NonValidator, Bin::Node, args, Some(40))?; non_validator.exp_string("Anoma ledger node started")?; - non_validator.exp_string("This node is a fullnode")?; + non_validator.exp_string("This node is not a validator")?; let bg_validator_0 = validator_0.background(); let bg_validator_1 = validator_1.background(); From fdf0c7786398f92b1dc846b36a1465f0d5a99645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 19 Oct 2022 18:44:14 +0200 Subject: [PATCH 194/197] test/e2e: fix node_connectivity test --- tests/src/e2e/ledger_tests.rs | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index f5a8e66b42..a998844764 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -84,7 +84,7 @@ fn test_node_connectivity() -> Result<()> { let bg_validator_0 = validator_0.background(); let bg_validator_1 = validator_1.background(); - let bg_non_validator = non_validator.background(); + let _bg_non_validator = non_validator.background(); // 2. Submit a valid token transfer tx let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -111,15 +111,39 @@ fn test_node_connectivity() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // 3. Check that all the nodes processed the tx with the same result + // 3. Check that all the nodes processed the tx and report the same balance + let mut validator_0 = bg_validator_0.foreground(); let mut validator_1 = bg_validator_1.foreground(); - let mut non_validator = bg_non_validator.foreground(); - let expected_result = "all VPs accepted transaction"; + // We cannot check this on non-validator node as it might sync without + // applying the tx itself, but its state should be the same, checked below. validator_0.exp_string(expected_result)?; validator_1.exp_string(expected_result)?; - non_validator.exp_string(expected_result)?; + let _bg_validator_0 = validator_0.background(); + let _bg_validator_1 = validator_1.background(); + + let query_balance_args = |ledger_rpc| { + vec![ + "balance", + "--owner", + ALBERT, + "--token", + XAN, + "--ledger-address", + ledger_rpc, + ] + }; + + let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_1_rpc = get_actor_rpc(&test, &Who::Validator(1)); + let non_validator_rpc = get_actor_rpc(&test, &Who::NonValidator); + for ledger_rpc in &[validator_0_rpc, validator_1_rpc, non_validator_rpc] { + let mut client = + run!(test, Bin::Client, query_balance_args(ledger_rpc), Some(40))?; + client.exp_string("XAN: 1000010.1")?; + client.assert_success(); + } Ok(()) } From d0b9831e1c879806f99e31b5e6dc9bbdf51ffb23 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 20 Oct 2022 12:38:27 +0200 Subject: [PATCH 195/197] [feat]: Make process proposal stateless --- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 77 +++++++++++-------- 1 file changed, 43 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 a5bd420bec..e919610f8e 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -16,6 +16,8 @@ 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"))] @@ -30,7 +32,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>, @@ -63,7 +66,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 }, @@ -73,11 +77,8 @@ impl AbcippShim { #[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(); + let bytes: Vec = + self.delivered_txs.iter().flat_map(Clone::clone).collect(); hash_tx(bytes.as_slice()) } @@ -86,32 +87,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_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,11 +128,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(_) => { - let mut txs = vec![]; - std::mem::swap(&mut txs, &mut self.processed_txs); + let processing_results = + self.service.process_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 512b6900a1c54cc0c02f77c594c2735467b65527 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Thu, 20 Oct 2022 07:22:53 -0400 Subject: [PATCH 196/197] changelog: add #510 --- .changelog/unreleased/improvements/510-shims-merge.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/510-shims-merge.md diff --git a/.changelog/unreleased/improvements/510-shims-merge.md b/.changelog/unreleased/improvements/510-shims-merge.md new file mode 100644 index 0000000000..1268bf87e4 --- /dev/null +++ b/.changelog/unreleased/improvements/510-shims-merge.md @@ -0,0 +1,2 @@ +- Shim ABCI++ methods for tendermint + ([#510](https://github.com/anoma/namada/pull/510)) \ No newline at end of file From 836a6dea18a3cd338465a82af3c8570de135f1b2 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Thu, 20 Oct 2022 08:00:39 -0400 Subject: [PATCH 197/197] Namada 0.8.1 --- .../improvements/510-shims-merge.md | 0 .changelog/v0.8.1/summary.md | 2 ++ CHANGELOG.md | 10 ++++++ Cargo.lock | 18 +++++----- apps/Cargo.toml | 2 +- encoding_spec/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- proof_of_stake/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- tx_prelude/Cargo.toml | 2 +- vm_env/Cargo.toml | 2 +- vp_prelude/Cargo.toml | 2 +- wasm/Cargo.lock | 20 +++++------ wasm/checksums.json | 32 +++++++++--------- wasm/tx_template/Cargo.toml | 2 +- wasm/vp_template/Cargo.toml | 2 +- wasm/wasm_source/Cargo.toml | 2 +- wasm_for_tests/tx_memory_limit.wasm | Bin 135502 -> 135502 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 237898 -> 237764 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 212404 -> 212270 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 154989 -> 154943 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 228604 -> 228606 bytes wasm_for_tests/vp_always_false.wasm | Bin 161050 -> 160341 bytes wasm_for_tests/vp_always_true.wasm | Bin 160341 -> 160683 bytes wasm_for_tests/vp_eval.wasm | Bin 162008 -> 161259 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 163344 -> 162871 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 176398 -> 176352 bytes wasm_for_tests/wasm_source/Cargo.lock | 16 ++++----- wasm_for_tests/wasm_source/Cargo.toml | 2 +- 30 files changed, 68 insertions(+), 56 deletions(-) rename .changelog/{unreleased => v0.8.1}/improvements/510-shims-merge.md (100%) create mode 100644 .changelog/v0.8.1/summary.md diff --git a/.changelog/unreleased/improvements/510-shims-merge.md b/.changelog/v0.8.1/improvements/510-shims-merge.md similarity index 100% rename from .changelog/unreleased/improvements/510-shims-merge.md rename to .changelog/v0.8.1/improvements/510-shims-merge.md diff --git a/.changelog/v0.8.1/summary.md b/.changelog/v0.8.1/summary.md new file mode 100644 index 0000000000..cc495e5584 --- /dev/null +++ b/.changelog/v0.8.1/summary.md @@ -0,0 +1,2 @@ +Namada 0.8.1 is a point release focused on standardizing Tendermint +compatibility. diff --git a/CHANGELOG.md b/CHANGELOG.md index fd88cff1d0..b7095801d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # CHANGELOG +## v0.8.1 + +Namada 0.8.1 is a point release focused on standardizing Tendermint +compatibility. + +### IMPROVEMENTS + +- Shim ABCI++ methods for tendermint + ([#510](https://github.com/anoma/namada/pull/510)) + ## v0.8.0 Namada 0.8.0 is a regular minor release. diff --git a/Cargo.lock b/Cargo.lock index fb31e45611..34ab8ab550 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2911,7 +2911,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.8.0" +version = "0.8.1" dependencies = [ "ark-bls12-381", "ark-ec", @@ -2972,7 +2972,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.8.0" +version = "0.8.1" dependencies = [ "ark-serialize", "ark-std", @@ -3055,7 +3055,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "itertools", @@ -3066,7 +3066,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.8.0" +version = "0.8.1" dependencies = [ "quote", "syn", @@ -3074,7 +3074,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "derivative", @@ -3084,7 +3084,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.8.0" +version = "0.8.1" dependencies = [ "assert_cmd", "borsh", @@ -3118,7 +3118,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "namada", @@ -3130,7 +3130,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "namada", @@ -3138,7 +3138,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "namada", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 49e17a2b7a..45cdb9aa5e 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.8.0" +version = "0.8.1" default-run = "namada" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/encoding_spec/Cargo.toml b/encoding_spec/Cargo.toml index 625f7a83d6..e6ca933d05 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.8.0" +version = "0.8.1" [features] default = [] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 9f32c04b4b..0061419311 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.8.0" +version = "0.8.1" [lib] proc-macro = true diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 21d4cc0b21..82522bc3d6 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.8.0" +version = "0.8.1" [features] default = [] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ca9df13cf9..6469694ea9 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.8.0" +version = "0.8.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 0f927adf25..dc3bf7b8aa 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.8.0" +version = "0.8.1" [features] default = ["wasm-runtime"] diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 23b73d6659..992a2146e1 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.8.0" +version = "0.8.1" [features] default = [] diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index e8d04e3da3..f2c8854f85 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.8.0" +version = "0.8.1" [features] default = ["abciplus"] diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index d9dadec9be..f270a17d9f 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.8.0" +version = "0.8.1" [features] default = [] diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index be92cfe2ed..f9030a471a 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1353,7 +1353,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.8.0" +version = "0.8.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.8.0" +version = "0.8.1" dependencies = [ "quote", "syn", @@ -1410,7 +1410,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "derivative", @@ -1420,7 +1420,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.8.0" +version = "0.8.1" dependencies = [ "chrono", "concat-idents", @@ -1439,7 +1439,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "namada", @@ -1451,7 +1451,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "namada", @@ -1459,7 +1459,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "namada", @@ -1471,7 +1471,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "getrandom", @@ -2526,7 +2526,7 @@ dependencies = [ [[package]] name = "tx_template" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "getrandom", @@ -2579,7 +2579,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "getrandom", diff --git a/wasm/checksums.json b/wasm/checksums.json index bafc05c130..01140354ba 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,18 +1,18 @@ { - "tx_bond.wasm": "tx_bond.a072c36568bcdedabee4d48b8739c3d49ecd38b0a48dbd2bb5cac7921b134548.wasm", - "tx_ibc.wasm": "tx_ibc.c86d31490666c222e6d0be41ea533f3e07b04f313641988f5bb793c66944455d.wasm", - "tx_init_account.wasm": "tx_init_account.f5043c92f0f7b1dd75ba66e9f8cde3f463d1777d358993e7f9ec2c17d8652dd4.wasm", - "tx_init_nft.wasm": "tx_init_nft.18b751c8ca1851669450779769346922b42f2e968c8503752a027cb454e55655.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.088bf0e95880dcd8732637a03af7c066ec8a4823bef18bbd55996af5795eada4.wasm", - "tx_init_validator.wasm": "tx_init_validator.4cb99abadd6bf823cfc1703c9f5430b2b2ae27fd210cb442b36fe25d0d8dddb2.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.a57ed7a2e1d7b7bd32dba3892dfcadde4f59a9f2be607ed93b00371578bafb6e.wasm", - "tx_transfer.wasm": "tx_transfer.992b3388311f57f880a74f5746b85fef88f4ffc467403c7428fb4e586310198d.wasm", - "tx_unbond.wasm": "tx_unbond.4accf5399c1c12507495706a3278414f15cc5037b82646b87fb7714cacde37f2.wasm", - "tx_update_vp.wasm": "tx_update_vp.001f8ad42fea81fcf55fc955081200708c17914cd336034d97b399edd6d2df1f.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.59c5e6ee1164608698774ac331c20375f947a2fcb67af969abbf2590aa9b2ac1.wasm", - "tx_withdraw.wasm": "tx_withdraw.f1c1090800212e67403a05facd7c70850ebd25affbfe92b9802eb87f82feda62.wasm", - "vp_nft.wasm": "vp_nft.80e6f79df620524fd505ba31f1eadd1a331bed0bbabd950f49f4c1b6334a34dc.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.4568de4187bc9c02d5488787e5796c93fc8b57d57be96fceffea533eccf4f8f6.wasm", - "vp_token.wasm": "vp_token.fc98bbb2095fae1628b75e5fe6b09ac58b3d406c31da6db4d72c8774a2ae54e7.wasm", - "vp_user.wasm": "vp_user.e411c42520b39bf905562ca38c85ca1831029ee4070ae715f9690ea17d33efec.wasm" + "tx_bond.wasm": "tx_bond.38c037a51f9215c2be9c1b01f647251ffdc96a02a0c958c5d3db4ee36ccde43b.wasm", + "tx_ibc.wasm": "tx_ibc.5f86477029d987073ebfec66019dc991b0bb8b80717d4885b860f910916cbcdd.wasm", + "tx_init_account.wasm": "tx_init_account.8d901bce15d1ab63a591def00421183a651d4d5e09ace4291bf0a9044692741d.wasm", + "tx_init_nft.wasm": "tx_init_nft.1991808f44c1c24d4376a3d46b602bed27575f6c0359095c53f37b9225050ffc.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.716cd08d59b26bd75815511f03e141e6ac27bc0b7d7be10a71b04559244722c2.wasm", + "tx_init_validator.wasm": "tx_init_validator.611edff2746f71cdaa7547a84a96676b555821f00af8375a28f8dab7ae9fc9fa.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.3f20f1a86da43cc475ccc127428944bd177d40fbe2d2d1588c6fadd069cbe4b2.wasm", + "tx_transfer.wasm": "tx_transfer.5653340103a32e6685f9668ec24855f65ae17bcc43035c2559a13f5c47bb67af.wasm", + "tx_unbond.wasm": "tx_unbond.71e66ac6f792123a2aaafd60b3892d74a7d0e7a03c3ea34f15fea9089010b810.wasm", + "tx_update_vp.wasm": "tx_update_vp.6d291dadb43545a809ba33fe26582b7984c67c65f05e363a93dbc62e06a33484.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.ff3def7b4bb0c46635bd6d544ac1745362757ce063feb8142d2ed9ab207f2a12.wasm", + "tx_withdraw.wasm": "tx_withdraw.ba1a743cf8914a353d7706777e0b1a37e20cd271b16e022fd3b50ad28971291f.wasm", + "vp_nft.wasm": "vp_nft.4471284b5c5f3e28c973f0a2ad2dde52ebe4a1dcd5dc15e93b380706fd0e35ea.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.7d7eb09cddc7ae348417da623e21ec4a4f8c78f15ae12de5abe7087eeab1e0db.wasm", + "vp_token.wasm": "vp_token.4a5436f7519de15c80103557add57e8d06e766e1ec1f7a642ffca252be01c5d0.wasm", + "vp_user.wasm": "vp_user.729b18aab60e8ae09b75b5f067658f30459a5ccfcd34f909b88da96523681019.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index d207438d16..a86dcbb07a 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.8.0" +version = "0.8.1" [lib] crate-type = ["cdylib"] diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index e01ad1b22f..ca5ff03922 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.8.0" +version = "0.8.1" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 5291d81390..7f48f2aea6 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.8.0" +version = "0.8.1" [lib] crate-type = ["cdylib"] diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 7fbbaaac3b84cdb1ddd6c695de571de7de45fa5a..dd2372605d64f675a874dd0604fa352f0803151c 100755 GIT binary patch delta 678 zcmZ`#OGuPa6u#&F|KE(=QGXxvP{-zuI+HotJaV*AC+8~Rko2GjQ;Q@kB18&oSCLT? zv{MfhEqbVg;Gz~cv!cMr9z$fyS+S)r>lBw-GIx}6}aeWG64#O>e;5@kX1E(+`iqAxda79d; z(Vb=nUkD#gxgrqNA6*v!s&sGBl@0n^`AYyVPI%sf4+p)6315596J~tZz^C8(W(uHE z57ivtP=Rmio`A&RP!6PC4&66Bo0w2Pug^2PiV0~+kX(W-alD}x>hN*HZbCD%A8K_j z^4)+CrlJADBhgWRISqi4YUu_|=L<`{ved#_g;p#FZaf+rw7fy)GO#~ZhP~N?o_xiH zWa={56*TjYU~l{rY(jrxgpQg>?1hp*A*27C1M2q3JX72bXlM=s1h6)Fj__u36$1KV z%OxOXe_D@oI;yYjgejTuh{?7KZtU+;bXJ-thqOkuhs;jR>A)LzdCNK8J1-H0O{k06YxbTwfv-49{?G`+%0!s_`Q z66>Je1tNABXEGoB;h;gDvKDSjL(Sc#269;~5wXL1TrFGBhyy))EH^o0Vmw=k%RPrk TbyIfeKW<%}&Cbnc;nusqE_9>g delta 668 zcmZ{gO-NKx6vywm@4e>fn{nQ#^XZuPjZVJCbUtjf(V6lpGDG%3MHsE@gNW%vs9i-$ z*`k%&g`kBMS_m#8xQZ49f(B7x1(KT<25zDtszuw5O3MzH-#v$W&i$Wrmd5p^aeX9H z3Ij0ErwyQucAbtpbWoiRSD8X6fI>6?72G8NWc^T}3LrO(YM_^lemZN{Z1vXGL2Jrw zdg6(l>GF!o=JJ5GJ=U)9OBP$M19tSa^(4h%_O+ctK5e^(f{pF{C^*)Bkp&HlFcsyB zb%YVO!RSBfO-JOiDR0=Qz(<=RP;>nTvq=nPr@b%7 diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index d89c21a89433f90146d8de7f555b3284a9006712..70f28cf41d2440e4b66be21cf360fffaf5a97e35 100755 GIT binary patch delta 26923 zcmcJ22Ygh;_W#VzW=nRHYWa z;)AC&c?#G7Ma2p#D*7xSSf3*LEC^5ef6u*plN-SI(7(U?`LLNY<;UhOuhZzj<=1t7y{b^2+LxsYBAr zhA-v?GaIH2FD-D7ntnrZZC(AW*>mQ4ZYmt!xF^qWXPIH+_p7q1TkSbF{UHRzc}8? zZ(jA(o1OPOam*(8`c>&K4qoLr9>8y3m2iCBo-OaM^x5;o@qhX3>3nj4HOi0m{dDVv zZU0zyS`#y6|!udD(s&6iY@0oHjtRWx{p{sw&-b*DM~f_rV%|gE7$JE$e~5RM#Q{8+b;b)}*gD+qidV1HaXh{?U)z z)~?t+rM|>jRynomRJn>BzkIWevl!MFzx33snaAS|-&lt8zhQ>^k0W6kLxg#s?9CD8xjMM*Dnq(uhFm?5 zrvxr~fw9SNF_!%RHx9{X%vZiLk0*M{2=PJFrQsS5fQPK+vchIKXos)W{9S=AV}!7p zvvf7Wp0Zd?XLL0pMhBVS)xlvVv(>g^Fk`!bC9Ex)KM4TODCL(YaJ%I~8zrq~l<_bm zEWhb-Sj>6mhOFjFVaTvd(`AIGiXdh=)lMFm&)ZmTqmdo9+vU0WJk}?76__Z47VuOb z$5zHV$-D(TysN^H!zN;+_GCYAy%wI{xqzo|U-^#(JiFDQH__85OMr$#nVjVknWvNj zb3bWb$V=k>mcv+rnXwKF3}L2D0JJAyb)0>gynP{0;s@leg?tcSD=#eMl{`Tg6xo!ud}nof-EL%Lf6w+0RXa120NkQJiy zILW*B>0H*@bU??;9xj{ZQ7q$LYVq|q`E=_Xnx1Neiq%-qcV<-1FG zU|hr;#@b<$voB};_|}ZAU9(u>U)AD2N%OgwW|}RrI+_yz{6U(nOBDXKU-BnunTIYl z{;k#1vg?o2qVX@ZRsD&p@UG3%jD7QyQeeJY`Y+`roXMF>c~$FYU_2n@yh+T>-v|<( z`!7W@Hcgdu%OGjH6=R((vv1|m0XyGNnlM8oWG-m2oOmm5ZTR|rx$sur=oy`-$sT$u z@Ch^R>eZYvB-?6wPzMiAu$uaIZ7v_Y#K-h_mu7g-NUJ$Gw;AsLvE6EVM3-@0dZ1~W zuE9Xh%~snU=%53d(7&rS*3?I*ko};Li*(x1Z&Gi@%rEQk_3vg4HI36zG;mam>5z`% zL4{%VFMBpqGM}B3G*~T{GGxIrp5&pLy+LdC zd1H``YK;RxH9{_BFjh@Kg~4uqTi?FQ5NA(JZ>AyR09jRLUI6=`J=s-036}SCW4(T% zurl*_$)(E}a(jU3M;&oPGK1_Sg4#2n zwSOQ-eTOe&1Nx-cd7je0T0Ed@Ffz2NsE|WMne_tHKayWnfSMbTh)}B*WDe%m-y>7) z|M%$3YNE5bhpUsx%oVzEXYcX}G(V>sOa6XtwYgBE5EzFFZa1}2r%dfX|DOStXsle? zlRS=FnSFsy67oNfM4{!BE=%d8g^hXIC+Tt<<9NLnW2NYHa4V~+vrbF~cT2VWJBu~* zKY4~zATksN(;(Emn2Fh_x8j`mS{Qg6m=WQvYaJ(wYfVus); z!UQ?U1Z5!&^AkwsXEM@?=?Ns$vqGsA>Sg3*!|8P8a6CHs{^KV0W@`wE_d#?@<57q{ zNk9|P5JrvY1f(^hdn2t7?NO+ntW?vec8u=8*et3sILJIFQxgh!?n`~5%`av&XQV!C z^s!XvYNU3AX=CO|x``fkt52M{P?wQxH3gabc{92bvgC=TcIFX!`CG$7%xOC3DQz|s z&sS$d(_(q59R5BJnL-F0(|~RTQCyH|qt16Bo%}D6ZljKm5P(i?)agkA(4UR^J5K=m zvQb|SKe!#g+m9lsrc!W5dj=Oqni_yndxk|=E#q~@hU^|=f$u|J(tvvKsA&5ET`@KP z3}xCgctVi9UAhLLzQ>0-UO|IGYFy-ra<~D^0zksL61-SJ=}Eo4eQc#_#nCNsP7S*P zXG@vv@3>MXuPTzt_#s*UbJ_Rm*a?txZL7W_wdbk2Cben)%~CrKX_8tG>Dp2N&APTv zsisNoWwzt5c4wRey#xCKy?TuY3?h6r&2}VEnx;b;P%faR0hI#K)Nn1*ni?KK+B+V3 z!`(dP+Lrz;ck}q^inrp>c`K6vGjmG9XyUN7H z@o|hr$o8vwTEwLn8M_{Ja{3y$VHXnO^&N~o_miwz&4+pBV0P99qBJGm_A8`%7t;Al zj8@xyq<{d1Z{U$3ouFnX0VwN>`G`)zhi~Go9LJGy9>s-OKBoQP%(f zL?64elh!|)(ay3xLvE%9Lss_k7;{%07gAsJ@w2DuGDbwfw8CUxgpYF) z{djWPCpwM+I1gZGAFGX=A4f3K$vi-De9{QeIX+5n*Xs8$hZ!>68z^$Jf*j+K9!Kc< zSk3uJYoLghP3(S?@Xzb@3SZ&hp$!9xA)t!((}S zIqn{w;a74mV-I09%elq$YNlLq58s56oOOI8&y|bU@pgQtd~6->?Z0WIw_YB%y_YG) zy?mihd#vb%a^rd)E+4p;M+H=DV{D#*a3Nf_cbRGUIP3gY;MsPt1c*9x{w~z6dHFP$&fm3D%63uN_m`D@-%fGA zj!@aJeOtEswVj=CYES21Lqo__J4toTyf1AfnoR+6+F4puH*>ZAd8#jXm}QZ4-pAYU z9r9NE8FJ}2WcTC7g&Sa3E~)8;LyT>CPfbt8$X@rRpPGd5k^!Sc{emShhiaM`t_N|U zZu#qdJgHCP1B`8e{p`F1;l*sQF*If`V?Pmd+Vap#3*io%kWNP_cK$4MM|5-P!TV4u zUH9`mF674hc`+}RJ{x()74fMdI&JCfjYnC+(Wy5cm4I|p3@8`CAB(3Due;|Nlwhc1 zT|ARJH}X)>@;qoMy*yTSHCoo{XxXKw<;bx;;v;y2jCtTHq}_NGHYFrBvndzpCN>oUxJEX~oe%I#bo#-ycDhT~=@EUW zC;vdF;74(Ys2I-Ji-`7QA2D)6y)S2Cm2CGQPxV+KcxE{N+3Ss#j3%!L@CIX~tp-to zeGUMdjj@66<}h{wKta1eW}AzW^Hj1MC8ts1zzVFCoJ|5l0AxR5j57NtlTfHn_EBS~ zh3PXsHHM90E$bXJMqW{enHT9+g6wyJH&wTmkt=RW&eFO;@2C9a!54W*2s5wLH#}sF zvUHFmw(x#ad!#g1&)#UUTFS5s)1K^2mLS`6h}RFq;;#Uk0Zaxkx)dAYl>lZD5Xid) zo(8ajfUS{e9zuAxQmcn7ewH5c;ufCldGA403bQ>HON)NzS*$kN@gIpog9Vmw2L&Zg z0zg2*K>>;Ll+FwaRN`S!cmyIYk2NTPi8Lzf8my#yQyM`EKj2ax4}J%mMl9o7{uRRQiAD9%#fdCg1uS5BFIKwdf%& z+j!Ra7U8~~maB#PTAX)1Y6-r|ftQ{E^Gw*p0R75sJe3cV`?v9E`POD0oXQLtSaJ;~ zIkpNeEZ`;B;)xG@xafJ1vDpMX2LGbByts`o;g86Lf9C@|{XW3So4=8Zhc_}7bDpvE z+XNSed5k@O0zexB7k_`0vBwD*1z_Ml=++wmtf;nXEvENnZ=0LCFn0S}0M4P>++~pM zpS>kjn;4t$8-Q=I!AV`s*gt>uR!a`t#n>lr0$K|)A70Pc6glx>9^?7;7|1$j5Kn`U z^wUV!VCBb3raoGjeij;h2p=qcxY=+&io`%j&RRk)P@NebfYlbK3mJRkUB-O=1M@ur zwZ-gg_{kWMPYlNJ-9X0f1CW{}VA+#^07derhk56aXC7yaP^8Yok+WeDu{BKgeuRe^ ze)&L-dxW?5biE6wj@0;RaPSux3I)^Oji_FR#nUj0h#Ka`p&-U+Y6F+VP>p=C983#2 ziL6ne;QV8ZJxJv~K(g!~jE#Mbv5q-l$SeC9YXeRw!2ULj9X;v=a$r5&ufT39Mf+ej z{a*J1vp~k9GJZSnADfCH*-mp`m20qK-s6oQu#(J_^R{CouV%@$+j)xNsE`M?^H9@I zm>zH1(Yl<{ob^0y{O3W&ddf@Nd2}j{eUXE~ZKe^vO$gkWc@&ub118;^z7gTyj~vN!QnsqEeqMYYZ}#M;l(`qrYD^cw$qT7%>BSBrA79 z-UK{Epb`^GfXUk(Gi;<&>viVG8F#tdvG~&BvMGdjP1> zmITIb11pu1oNAY;?uWDilS1`}q5i8Gklhc}bnwlf#DgBG(hqm3i24ChbYI5i zfCy@EAJk4FjXKPb2i_Xzfo1Q2lz_$DI53s5o2g5rMU0y6hxDGUkQ#JDY5OF`Mp7?G zuNj3cI7E-qD;pSFcnq@+K>TAYEE}v;zXZ&~yAt;KRWGnSkg?cTyg>VPj2$4LGv`LS znX!~eTMW|dAWe`5_C1j^30THp6a@4GzzkN>#QvjIQid%e%S_5?^qkoUV+m4+g>&N; zh{-M*csQ`B%9~aNhCvoWK7@=G)-m=bP%1zv?9I#n1TYGnz4i|r7XN^N*#sD$R{~IS z9>7)rYhfCGMYZJRoEu&xD$=flyGF&^QS6}@zVl}7x0`cfAd4lqQ)-8~07|A807&o43dGhrZl?u~*Kfjw7%O_^|; zwc(#x54ENd(eN|(F!mT&s$d(sld6sY@8 z$e}w$AsE4V3_Gm>z>gq$+HiD4MP6}epNKxdv9v<n}mb*qi?JFap3BE#-$u#x|>I z7ON?>W@$cuR33c{VF6$1|2XfMS+&>OVJP(uXM+Zo#sPfV7rhgZ0pR*uu`bKX$9ZIw z|8fLtVbRh?$7#5gTQuK2CpSILd1+vFuPYXwJ3^?;WV(5hIzFCkEAnQ-aN)|I#jJfC^ zff0IOobDiCAeTSxY`gWwT-#@KajIeZ2P`UL{o z(17WL#qgd94aOq>wTq0k`jCtEDG0`#LwX`K?4~9<|Go%{B`fyvsHn5=z?BM7cvj>K z6fY8r=*8$yZraCV`}ur|K*cO>47-!Di(g|GItyIe08GY-rHuWExegtLND^k|iL)pj zMYJpf^9^4iWqj%YV`Y->=i!44SYtBNxnVgK61R*$FM~t~QnNfBJ8 zn?F+OBfA}DP8I*hVwKp^ejb|o;_Gw-fj&?cY{QSzU%MK{+CsJqz^pC38S5iApzpRr zFx}%*sj}fB)g@gL5es42An+OGP!Pim%F12?BPdVp=NSo=a9<*eOk6c}HS_4g9dbs% zs675GWAkO^6Fibv%IluM0er`9*eZvk>z%xV?O=b%H32h))wUA=DQ8j_oS*OO2nxJU z(scpmhjlP4p4EH=22XpEORTNTqLT)~7Eom29q>te5LvLiM6k!3%(om}@ zB)u6;(g>@?tb^NJR$8j%S;(s|K1syfDBx!h8X zG#wWSy8klc1s?1%_rXkPPupF*t!1^Y!?vpt$+yh7POE@9dK71}E!yls?DdK^Or3KG zK--lN7SR9~q5*Oo>A?z7HByz0wnZi^5e@R5r+79`l&?I+tN9Gs<7wPNJDkDe<;!uZdUUt{bdPbjWXu|cX5Zj`v4ym`RZDP*g@Lh z*4&sFz}OccWpE9AEXfb@Fg{2|9E5r->DkQj{Nhk^m`*>xgVU{BV&kp@FQJN(^A)Y6x{Y_!E+aR&$DUDto4`7%PAsAke)v`jT0BP@W zYe-)d=29U5Z1b)lxo-y?IH0^=51wM10asui0p&a{kPZyC5>VL&J9Z4s!;TTCjqN-D zb!?V_I^oRs;qmGjO+qLtH2Nz+m|UcFXEX{Qn_@79RQSJRFrKSlgaK!I;)sSp6H29*DOEn}xCjr?!x;EF=EDStv31ewxs5RCQGa~LNr z8|1dbJem8+*AMeJ9wfgxjA-@iuuvysf#AH=^l?XxctRdvep3hk|JZgfR{r(S=?nITV+w>k})6ogp^s!{qk^XNsovLa! z9ck@KBTRZ{&w;xT;D!p0HJa)$c(k%T5ou}o0)TXn^B zX6&1-Rfcv>g0ec+bi#g>YWv1G$N=n%fbzmTm-34?| zsQj3MFZzGq95v~0AdU?SQ!gOGcqz|+y@5zIyq!AMV3GWS`izKF`e%{webTJ|yMKGA zZplXrumh+DMxg!Js5Avv?>~*p-b$7q;VA_a^2q&`mNfDFM8q!M!U<#}md(`*8GGrN z7wC2X?&-g}Y}|U^0$jVuZ+;6u;)^+mI!gP? zJj0**t6Iqs14+O}Tn>1d5At094rBZ#?5)GFRQv~Y^K)GI1z}5h9O-ftY=!U9?ri{n z=SCka)q>Isxwsutu$j_9z!>!;rixmW^5#p@$}XmK0R)|@wc)nv!5|Mz;^{oX{1!yB z$;ac1aeDp&<#-?QIJ_v!cHK%sRm^YeCX?UBgUwIqGJL+mowu#}j0EM*TZ=}^S~P+j z{!S}PBZ$==zJh0j+y!TC98tf52U~8{m#)N8WSg6hO};uft2wklYl`|`@(lBF-Ae4c zko%hlw51x}YIeFLW7f5?o%S)GAJ&Y+*TenHhbQYYHo?W)t;?{E|1X`ADB0(AY{UEh4IU`z*a2=y)6Fh_nfsWB>!zQ-f$2`w zW%xqP>>czOhl1=2^zbY3@aHD!a`ZP_I8b1SoKffQ>Ge;036se8oQhq2W?E-$^# zA2XaiBp*M@6Fp;}Q?)&;7~8`hT_F9x;teK5BE((L8`Ax-OqjBGEQQmd`(Z{i7u8#^ zeWsEp5l?8ljj`2KvI?7lh~11WqLKsHU39)tMO_5c%&^i9Yo++^#jqoZFi8<35!Pph zP*5snwue6`ryS$mJ##i9C~C#~6myI=2D@|wtKKN?E-Td zWJ`M#EMMv}9CWXBW3E>ZrrKcHI@ z!rX=s(;8g{Uu3nnQ2BoZn_6h={>yPWuWXY zHDPV}qw?Z$9_#sVC646$8H?TkL$ITWu|ppNcnCZEN1?_d3UoG7&Q=uXLEZe`Pyr^A zoe0blgv!eMqzai@9JR*=9P!vtx`5k={YxQo(F=K~jaa`G0I}Wnvx(cfu#A>{r%lQ7b4q|g}q5haG%v9_%@|K)pBy0jM@WoMUk-W;?oE zijr8oqO)JueN0YJAi*0^7K58Gl-@^a?2(_>*K6mn)9aqv6Vn>f!fd~d!mfcjT*d=! zr_tdT1Z?6x9FGHFh~Xr7a4%yU0p2Hpu84LA>If>OE25k9qsOZ;KX=sX;#fv`HD+RZ zb55M{Y7A?^MC$SPn26{SJ&N7Bf{QZY1P}4pM&ryG)BsJQ?E>t~KmxY%V8P)QZ4ISTNM+KQW>Bbi`D#YAI$HxGKVLW#I zYz9y)re&KO!>?#_WfS{{x^818TUw$hivE2RiosL5ZHdEeZ;7Kc{`YYZi&YdYq^9gL zX`6N#T<#BPRQe|b5W7lWSomS!?pEUNr=d0ntj$467x55BEGFs%fXu8on-w0)IixX9 zX^!*#U?#AwN_yXFdQ!)33UBPt!B-7$c&;&at&*tiUTyCKZvy(H_=Y(e)M(Ei2yf(4 zd(Z!Wd;?uyJ-qP*G^dalgYZTwXe>qg^6*A0l)`~jV%GDht~b817HM64BSaQ_#C<%> z_ATUn7s{d^;8~8GUNGGN5}@WONNZ05UCt&oKS_lY`TOH)ZtkT=NSCvpYOfQP1iGEA zSDj`$0+6}X9>=YgSQD8{?IE+N znm=#1v!m6NzS54uY_3DcOR3}AETOg-khhV53BDnYbP&D|fF|L`kZzLj zF93M{l!*U%31>KD(@VHI{y!k%5rp8clkl;+r9eHEAra3)Cz^;4MEY_O*Gc$fs_K>S zSx8?l;U$HPY^J~V6|;T$vFTdNg+WnHWsr| zf)q2?>!viRvmw^sQmV^oH=GAiFX~?MQTgr1JfU|rwv)x5Am*I{Bdh>Bv7u4!X=Oct zQ{uN#{1bfDiM<*7`ZoX{(Mi(nKqKnYXy;jQ5^<81vh*Y$YWVnJx%VUvisEJDDV~(| z+cx#u@gy5J=Zv7P)n}3&*LdS~88=SG?bPjUWcewc8n^caZ>u0~rp=W0ObVt}a`!2o z&qvGOPT{uQ-oMF|Pk5UDPcHaNaX9=MVhuU%k`q7Sy*!@DFi-Kw>o1ZWcL7gt08{;a zY#yY?A${Y6R;%r}6Qd^JeXG@xg(gb@xKFpU#Ui~C>Ea1E&~*UVN5CiWs(*9BQzPIa zw>rK9@C5<8td0oU6(t~jI?^`7lMlZPKoR-gZU9*ToQtfcdidAc$^AmU0T9s&QJ*d5 zC}!J(E^`5uPUe2LhX9NNQ2qb}X$!E-B49sHu-%1{wFHdeq2_WOndP%YxW%oD2bRZq zL>M#A*4LRMV$JJmgYSLHyNgK6=h+&FKstT80gr7TI^9S8Ji#62)`OdCluqG+=15&e zRS{yr+jK~)?&em@4?x;lYo%cYY#yYer*x+w%zm#-{F-MOJ}}CWU-K>l{Z6CPNCd>( z&mzwLDu5(}PMDz(bQwM~z@7}>4QoaPHA=xL%6obrPQ>3s-alZ2o>_Ar3|E_5QS%MTq>DRvR>+XE zIPY5|yPoCK4KMAM51r*{eBj=rXSvzHH_CU<@wR^ZQb6x(R*Mx~9Fu|H@iqL5y<5NI za}9jS-qas>7RUQW<9_5BaUPgU(-02lNqw}f{C(Fk_KQ_+`jJP)5id=J*W20yTN=_n zYRpG08iz0flk?;uwSUEFF$?2+b}z?z;HnLi zyMN|krf)ZZAggr#%%gHodNI*BEot7K6%J$pB1}|LsFeWIM|&`APMD&2zI^p(kW)=n zK@N>{tyWdx!9gz)Dmkf@07=nU9BeB{!c04>a0SjJyPxM#U0E9y6EPwUX=Umxl1QKu zkV7TaS_v>cQjWOrZ*0vkJT!?Rz}*URhZ!-i_SL2o?H;o-@$wV6+sNdl^E}L_tB0}s z<*D;L2A9OutfB*0@@3R7$VyPNOb1rsqNGWV`h^eV>*R)Cc)F+4Vf7-i5qFR6|GZVb zqHM$!WqaI6$ovW11)-9MutlVM(Mn0r6}Ybku{BOXweTIdmrNx9AoEQZxb54vshE9m z<=Q?6Qr3VbKDa$?w~SZY` zsPDV6&zV|YRaN6GDs|O3r&QHf7Q1R@GrHE^P*Yat?m_u^j9dU$d3jZltImzvY9CgM z2pJh99ENXm)=U!V^5k`*orgZ;Q(NsWa#p%$)%B>aEt})cWBn&Hc2ikJb$L>U4vp+~ zR4FRQSLuq`R*0Eeklm$`HC~5;Qg_jGHs1<=MET}id0A~8qwjW2t*NST*0>s+)vlVl z+8z_@b0_67`W^%btN}Z+VG-9bMx3Zx#@aQ*p0qZQCnSJR_Amz z5k?;jy9_b4m;+x+C96|bBU>Od28#GDgJI9SwB|N4x`^ejEpk=6o%NMvMODRaMz3g< z)n3NL4s!lLF~;1l5FJ(2$&~>j8gK1f7$~9==qRY9rmDe7oMH4@dP9w?y1J~g#93QE zg<0jL8$A*DhI*|cg>#S9$i^hSyso^$!GG#I|2u=GAlr2@HKK?sYsSF zal+dEI&6tCo#j;x?wTT3t(#p;VysV1NqvR8vTjT^`vz9eJ271w*=iX-OiaS1uf|~_ z*>E;VZXPCDCEU?8qa8aovJRcVd3T+cQRFI0eg{5br-;i7k{5`AK>E5tO`S3~O14e9 z3PdaZyj)Zuk_7IyNWYF^V!|C5>JayAHXbrpTUX5JV+7%6^iIyQB5>;z!Y|h*Q(@T6yJ^_bT#Z_?3H&W((&UET!iQCnZ*PRefV(IF=@p-WbmJl0V)NcTmHtHoq1}o}g0aGt zSee*COq4y!g*AElU|h>~HH>i=DUM}PRgIg`OR9>+RJZ`Auk~0FH;~-&A{X?fvZ1W9 zn4N^psc=^mmDV^*+?7zea%JM!1$66`5g19jv!}?>6j#x7MF36m_Oaq!3w?KrWLNfy z#o1j!jIb%&L?{U41-65|(Nk_1C%TBfP}~C+F`2t%?s(BA{%fa(A5(UQY%r`kqt{Z) z-IaI^e%^QyX+I5GYiCy$FP7tkZMbL-_S8WA5(?zCF6zzg(%c!g$s==cWohlKxOcaqZw5@Q0 zBXzI_#V(Ch%o6TY$hkd5A)hPrOGSdrxIx6F(%aCkTCWNE1QJ3vw3c;kn&6xq7|F4+ z`UVk^HmuoJtBVTslhJ8ZLs=bZO|`42tZp`ole=yZ;a&HDkh0368aJ7mCn0VnFsr5R z3bzx@+?YW692@bjmVF7{RJiL(tBPyce`paL5>1z!y-HT!At#R&gEg3Kh7W(rul+@9 zV}v$r1DhxxnIyUy-XA8vm?W}VQP2bHhpV=ztjt+oUG24Y(HN>CR4iD&F#`-BqZmi` zN?@McH8n8VbaUA&rOMKAK+eBW^t9if2g6z8Dyyqy8GB#8Q9R9kKZg*MLZaAna^+-L z_eq_ZJl59`k$EH8ui3SXj!mm(x@+JNKw3+j74_w8L!34pP{)76Qos@nklQRGJo0_; zhs;hfJKK+z8+9c_1fDddnBOHoa*2_=rfKGEa(f5i7up}@sRpK}m_27F(J5y5{@32X zHex4_dL0&3T{Z?n7*(+%Q#dSF9>^E_<>bM_A&(9bt%C9xU3>!;_bf$2EXp!7RPM+Y z*0vwvhCoqOC5BSR=uQRZlYAHU9r9H*gMd&_gggbgVx)+{?N=~C;i#rdbp5B)Raso_ zcGkFS-8D1atfooCvb#W$=*|C1cLVDp6Q_y`xJ4XOg0=H5nO7pFb)kcyCM9Nc>v z6r$U`nj>ErmrfvXRtNI#5Oj1OI#T;Zj@n->}U9`lgyt9 zyV^U?U@VP|)kc?#xjI^gao5+@!DqtEumE5zB8+r=?k_D_2Ao@9IYU`3vBcV42@P~ z#ceWrHNja{Qdw1_EW-3=x2{7hQQay;s=#F(IXz13G+gK+?-&bB!l?QLvqbQrxT+qj zw-Z(d8dX59W0*W#DaIM;9Fmr*pEBO@&Yfai20>RK*u`uL66LNrv)QzGg~Rn=<8s-j zTC@qI%NvkT?_?i=%|03+GCQw;%}@skZ}upJx~!7ckJ_@LT9(zcEN6Ft7#)W+sD@@3 zEwU4P(#sTz#E^xfBVh9Unk*LPv82MqPNP+^tIoxi_0_GXxT#h@L$rn;wSI=^$jxNZ z+VUIJp;q=5BNB?i$YQrLO<0&p>PlG~m=={(ct#r~R_RpYSuc4oUOdB}kdM@gv0jM=n2zCZVk^ix4TCG{ zFOm{2z)bY1tEwm~8m(9ydZo8mT^(p=yI!umS#(RHThnAC$=jyStC9*)x*HU%xU2;8 zvsr#TSHwoMmtkcU13R7EFN)KX_sG76-!e8&;Nzr(w_FF2NQ~U0u}`u7;hURoOk0)dGuq7B=y9 z&Z(}la^Rql_oI781?f36twCbonvFdkdQ$e?zD_n4ERN_Bzg2eJN@Ngs+ zV631@KEpRPWkIkAv-d(j#2NOPX7Vojh!|c@Zb+P5vp@uPp|{!ejva*<=p_V_>ojHQ z&ux>(=)Nk&B-k8GM^!B@nSZoEq=^F>+HcGvR=O98nn?NtC_M0@()!BjwJM#N2_5sW z<2|=fB;~X&1jQ5w@;c2Gz^8b1urG=@m$Q}Nn5)=T4X~D-DUbseiOjflAi%4Pj6ToV z6cQmvG)yjEB=T)mNc$8h;I=MoFWdry!PuIXN0-wOEH5nLHEt(X z6IxQ0XYSXI>{4EJb)>QAhdl0*K#hONCY7bKSIce$7K?gvIU}_f$sjF4mTo zvyb5^xQdFfw@rfaQN6J)@Eq06B5j&FL&&PCYuVe7VO1-YI2<&<`v&2 zWDDe?J49+fddmwwhN7N~zK95hP+&1LK@BfH1xC9nXESwI2JqER-U~>^y1aB z-JK$nACeRA6fwPeVc1iuX2EXW2}`TarV<=s^y(huAA5CDW`r{<;ZxHsSA`9#S=%EY zzf*J>^6%#Tlv8n|=&J~d9hq8I0~4xv+2x3sR>NykgFX$wUR*YloyNeG0EW6wrOVvq zBGo<N7de|ry0gZ}J5k=wI+VGS{4Q0h}F>qtJDHzqqpV3z=6|sQ4_LTEhil~6z%~WGE z7ACi?6tTf{*ymjbR5{$yL7to=*2xds3tzv@ptVXR`y-dU?JkkilU}n0`OTc%3gVm1 zbuC*4&W@nT8dO@;U@Y{fsG^$H$Wu~eOGh`63@f}n3wGOG2eSspmpzYaM_(uKqYFxj&+f{?-t1!bQ@Y*ugP#G_l5u~BBw4hw2slFzpZZ6 zLj%}v;H?^!Vt74}cJCA9jU9z=3SBkObePdWG`2~!rBqx*@5b^?^4iwPxmm&%L+|-D z1?aLGdo<|^{tpj%s;iigvKY)%CEsF#pjN2sCw)p(cnZeRD}UI$(|@m^k2%uEplLPJ&I0Ht;C84YdFZu_?rEzlZArZg25trBPl-Sb zpb6}dL?7J4WT|@k5X5R~RSjaej4n2)k%M~KWv!SPJHB}yus1r{d@6vg@zDESd(dSj&GwNOCO(vM(L8fvdBv0G}Z|#TWZ k*WV+$@g>rA5A66wWEoCW>KS^(x!{-}C$3AN(kL&))yN`Fy<0nf1(>bIzPOGr#fJ z(SWBP3)s_zD_q%-$_JVl=YAWixz}Dcz?Iv3P_NQUP4yeT=1D0t%V$ll@yx57Gry&J zLRI0|)VULu^FsHex=Sh=o0=CayzKHT78OnK?#omDQmwrC)rQRa&il^IyUWbC?2`?q zBtCH8EjKO;;8*TT*w)$Mbg~4Vz)}@PKPG4Y4MX-W-}ma?tF0(E>!D{He9gXYPffG& zD>ppyVxN5vowN(SW<$59@;5k71@d(p5>DN;Z|v)z`0ab>)Ox>teNKzv_x&$?!&~1)Y$!he7RKt#j43~H&J=$;Dq5#AcHJvT zI>`Ohs`xQ|#+2JmA!5FU5c7qsJ0dOj8Q}B{N;m5^dBb9!9Q53ijNS8h#v9MVx3<=?nSZ(G=Lqg=_V2j-V zhnp=n`@8v!?Lo8Q>8Sn%0E9{EFHhhOYeqLUt|e6Sa73)x#x|^$Jj;&EwnE`Zu-p&UTPhnPd&rn@}g`HOW}P88ySwA}z~!W&9i2j3rnY>$y}3 zH&+ADL*NbZj$7rLWjv98E%z+rWB9xBhh@BuH^}O%c#QY2qxF)E^_dXlxH`iC7lk>d zr5WIfq0VnmehWJ4;|{go)tj*ci1$z6wxERoP69{`wt;m%?qjG{-NpR1ftI;0yY&Mf zEXCCrY=i82^?!&b}`6l?y*fbp23a7UbBi1{lzGUn*t+JyY& z8lIl?t)Ydicm0AbXAKG6pHghro@ughHBa%1x0PNhl!P>Iln!^d!&WuIZjhevaN^9_cCv1^6WB&lp%vLL=jpXC%D5rQ=2la|AIFHt%X>*^pI4-ZOrkYffEWoj6$Y0 z%*ZX&Njn{L$S?7?I>xCCW~>UG=6AN4=Ni}~zkiA~C5yGuKj~8cj;y^(8Wr|mR!KgM zv>5o1MshZ1qyH^{ppJq_9UY+fOrUHhRdkZdK@B-oH3Ul$D#%GHC=+q0pCD2{Wr(Y) zCx}$fI<-_7Xhq>S!|DC|;dl-F{pVHe6}C`f?<44x#-k#Ant)cM!Hhc635e@R4@O)? z+N)w+rWVt&cCPA)b)QP)2U~WfX+xpk@hkmeEI)Q{OGw#a^0RI=lt}3X)y6FA4HG?n zm0!H&Dnmk&%^YlL@g=A{nl!_lVOehEzZ-d|z!7tZNOLhLj#-K>>r&C>qY66yx~XkWJO2k*A0J>A{ZK-E;`$y?)I3 zD=HLG>f&Pg z$spYmw(`zSD>&~wvWqhP%nQKadML^Zw=CGq@iZ4*$K7*btn&Icz&|B{x)o9yf9%#gPhM$V@;C2}M zI=C>x$p#EmgdLp?Dj!`kbEdI}bgxrgh01(gg!V-PTL_@hG7B+*9wt1}2^kLl95m zfvVM$N`S%YQG2^sqlY;MpzXnEB0DqKxfbyfYHo&$YK)}q$nI38y)+Rfu!JQjwty5`CLn|K^wC`&f+?*4mkX6!DkW!YDoS-M=eiEriQ zGW#YzkuR0YZ{ivJd-=dkd~m=g>wV?&xZOWpiktZ|zjDlNk=$|%kC3?PV;-##x`g2X%&^ zZiLIB&zE5i-NdKC^!YMF&D%qHU;QTUs|?llI!<{%4{cxX=L`q6sfRv4hlP@zcACms z_)ywMr0bcKl3{IE+`_f;=c&Bl;noAP^j6-LpO>rfXY80?kvxo>p54rt#}_|(l(8>f z)6(zR!&v0&TKtijjLlUrN|Y~L!Pt5&&XiH;KWw-B`Bt7dk+YJ5LX9awVSs-KB zoc*+`M74At_Ukgn?lU9a4Y`%@Qikn{Y0K?!8*-)VHlD}F$t}0>3cgYLZQpd0{y&Y42w9FV7|o>1pa9huy{g@*x`CxA=UZyjY9!eyfY zmpw*Yj$b@3(sKt7i&8ofkxm|C%uPhS31mA-)IKYhZ{l zkOzks0|MePK}o`T0H>$r(4hv69)Z^;ku;3<5f z47`)adhdcy;eZqQcbMXW>^&KK6+mG|5VPNhob!~k7da8gaR&KvdMAN`0J0u3MO!8V z&_mYqrZDR$W5WAbj@uVGX^JuwaiJBp%mKsFk@X7Nonct~$Z|KAWa{0Z_cQ+1u?ARo zD_EW|HgnVzZLO5{ZG4z_S#n$DtSwfXbu)HwddS*p4Yq#=U;k*>4l3{pfHDA+tFW1V z0>AThk=$#yH2F69skVUdu}ZsT@u;x^xGdadot zF1Fh2LBb?;vI1K<^4kvjz&Qm-sI7oG}L6N|_@mmLm>g`CSynZ2Sd@#k~ z`51!wYVhKBL6P)8_b_HXOr8g4OgBS;$q_urI?$Lfn)jj7 z_ECedWTM}!Ypn6kg8=FQVEr`Ti#YYKhH|$^k}`KXbq9r-cX6wyT73&uzxU#+ z`&JL~ZneTUmGyAMIWHCXVp}Ok8}k zg|Rv38S8e9;6hmpeTntGTUUjPf8K}o2$%%mx&w?2eGz~S#opS;SoAZ#I+yol?B%}$ zIEP}#p<5?^=gT>`m9aa20q_MjOqbq(?Fj)?tgina#?q+LMn9l%3u8OK*D7HTsRdnI z|C+^EzZZb)IYk^=%vh5w+QFlyK88AP!K~{x!J^!T8e%jU=G~u>AYgki<#uRrakhxD zm{%F|`w6OiI!cR$Xl*T}#9|me`HKuV^7mQO?jYvqw0JSWY%pD}76Ww9R?k*m<2 zC_Bp78cIr_q^{KCi!PY)Z^@*cyoYz$bvR9=rVfFm-$P@l?cQTT*=nrL$^xn|-p$yp zprN@dtxfev7q30d*sRk?ngo1?KFHYTl<$W|Ub>&LtR*{8E{DFOP{jf^iN!Yu;v9^k_TCvSh`*Jb7po z&>eU-8mOv4pMk+QqQ47gb*a&_Ovxb73i%AWNy%3WW7K!h&|f0*O~n8Lz@uH>po8VV zAjx!(^xwrJQutli*1!VoJ00@|k2(tXxCu>RAKmw#R5+WGmdUZZcp*P5Z{Ec_dV7PH zF2g)HGHqOZid|wp7|Sux%00MCoz?=FA1PJk1y2fP)?;d1_e!()T~5I_tRiOwbFG< zumZp4%h{H|*eOs~&B-odECPH$b(CGigJVWOk5VfF^Cmvo&Dh7lD)~onWPzL}wKEVZMgXP|6v#Uc-O@w3{@Ku|;S%dM%`{69n6|GncW9w;<1T z=%||uWsp5!AjW0GQWp<=2lNHEkJ`i7Sty4As3>Oaz?S1AvujXimZqgcPgWDCk(+)(D4y2s$oG~enyQ0+m(!c`-%@3T?U>}b7a;-JjDC%7{)3wH>hQ4 zhMLd=U~Z}UO@e(X61K!^P1!>VVG{a)(6t3v?*Np(#k*KGBS#OVZ}V`+bB2Vne?nX- zh_$saQ=1578uC4z82ehjvllED4unEVxM>2${U~OD;ssEBD?qrwIo!1LLB^g(df;Sk zy6j#pJ`C}Kbz1rHDF3(aj1`0G6c4$AvAe+}R3>kO*3K}r6El>tW0-y_^U)^8&LEC5 zUZ&)M!luO_Tmn!DT+U6SQy6=axB2CAI?qHMU1^jxGjff zZ~!&opT}4(QDY{`PQfq;7zThTHj=;LlQmSvUqyVL*G)oTog4i7KJW}pO_T-+JgztR7U2DMrKWG3GV?m(4RFgVjxsZyxKq9q= z@9xqNNg_mC12ja`a#3>`OQcG3{JD5<31hds3jlqK88>4+fLJ9>!wOvS81^cdlq5d@ z??c956|kg_AjQA-g|+}e)TReyGv@Lk?-fI!v4*^kEJEn!Xy}0Vd;lG7-0)8TH2jRs z7$l94hChEFW50r?YIAZoj0l}dX@?n`PJ=};rgVdjSMC7aO*-Zcu^?#JH zw}Dye>)hmZMl$vi0mv9SP%A|7Q`a!|6_|<&)enP<1@nb;reF$~cyBg@{#&Hbou*kQ zV7Y(>sVxA$0n+!QBg*k{Lyu1A0t8D9;-*{j80&ggi}!c}PTcE#z%0qwXL88{JW|Qp zCvScLeuzo(^#^#b(u4bb&4*E6Fhyt_sT{!jLw$X92QX?C)@iU&DvGfW09Fw&V@{UO=%0vgIECbO@!N(yJXf1a-Mq#rc#d@pU^B{N4dh%B= zZ?QhM5mz$yFr`GHDpPu+q!Q$ZpGRqZdJjM1rGwZwLGw6!V&>kvld(gXZRhI_E@J-z z0F&ihiiNfNLPS22p8Y(``@u(uPs7fbi8rS(cKugy=u_=m7l2pZ06?`Xmov8O1ptv4 z{Ryb4R|pubj-dyJs$e#m>3J|)z}q;e>B^N9q?#U?!`P~~kdV$z(}J-b`hc;K>71Fy z-i7KP0_ZxI$UtX5%4=rS*ousZpBU@>78gB|;U+qV_zVc%z*d;bO4d_3XHc<6|?dV$aspHjakmv3|V`C#|7K%Ah<7XVA>4qVfQk=f3A~QZkM!BWM_-fU;>q zJxKOEou6m}46!3e?LMP{u@*>0VLFdDZ5zzkYBUM}`7aZJ)Iw#KL6+r`gFG^HBQ!+! z8nVSMMHzAVjZmNR-h;e*{{t|0qRP!&BV5B`sC7yyhB{)8GxiJtaQunAi*PLmgG(QS zwM;E0gCF8~-m3oKvt@>L`LM(o+pV-kSSo>q9?(&ud3uHp z0=1Dvj!_0Uvp*IoYeHIE3916f+{e&vVnG+{FdueaDym%`*l6e}ah%OOx?5W_iN!YS z00Z3hM;^_bxmd$fi9*FTdn0NOM!efP5$Cwm*iy>b3}#(w@g zfaPGN+|h6gDFC2c6^e*Ma`7P^0e9oZLp(A0)o-yt7IM?~hj7XUp@@xai;qBjQ7mIS z9XuFpOhJn0c1nR=6B(yPJ!470+sv*m}ht5FoEKip|591p4I)q z2KnA$2*<+%+X!Az5oXCZFnNKQ$Jw7nft4XJZSo+bbo*pGfC4K|Kt0vDbDZFXVgHG5 z=;482&eK%?@^Jg3;MemMN3YJ8G3>}t@LsUZdd$H7sjqQc=zL`6P$mHEvVMp1{GJ#w z@`vT}WcxN~`o#p)@c1A){@6}HU03+PFf=baNuWNq^8mE5S+BrQ^-$o)J8CCR31KL( zC_r`l-JI$j*=RF-l6htbLp}SI2)H4AzLSf95cDEtoO!HE`uwOrz;@*o zG7{pm7<*5KcriEG=D^UQIMTmrgj(>Wf6+w8-&*?gaPU6GVS$(q%dy9Bob;h=KE{*y zRC&`e9?vh42aoX-uQCD*7l&@o+sq-o^)Ug4!Z#b>|Bu4=wd&LN_S9bBYjlYvBf@L( z8uaGHDf>`T_Re@T@NX17_4{x1d?|`tSkc#$qDLpB=vR}XNBmzYdMc_bdc^fhn**QZ zeY~~}FzYd;Q%&aW5Jf#qwRAFn4`S$nuIKE9bm~F7p5vT~Dl4&eQ1L^CY&sav%hC%& zXlFu(olj%ZwovH~u};zp2O=TU&>EIy0Q8WWjjKz>Q(N_>lJP85KbCNk-~Oc9Ud+G0 z8*4V+v78znu3fQI7hH7xFYj0qYQ9dLPh;i$i296-Qu}97j7>|m{rmnMV=!xaKn;)s zO^uF3{i)HZYFmT<3kT$N3P)s~g~`Ffo?5*l|LBe;mi=-^hp) zJl8zzNtAw3PCLOnM}2r38a2bx#WC$CgW&&y-8jg_!^X+$PvErgU3v5b4~LKO)f2pX z0CnhD&*5xIz+PVlJ*T0NPmt*@LjwNK|DFElV@DB#-zJz!UGPc7cSwO%&+~jvD zR5rzzaj^z$@EOH}(PG8JXqswQ@wR*5>OQGzDPxaOv98>HXFj9`RN~!uq{Tw3ht3Fm zIqnGj0B+TTco4Rg^^9SKp&}NW!K&le@DR)AhUxYD7}xsVG$tgd*ZSI3THUS^`u~Gk z*1bSh4}2Z(9{MEuE+OpK@eu1FWA1t^QTF=^uqDz4XS0kc)T^TWKX`YGmF!$!=&+0j zSQe%eiB}m zRijOB={b067HKnDdJi|7_H4(L%1i!+JJo^23z6D1Q9ZC+tTs))R{poMWIU7S?WgHa z&Jq;GznUf5f?#a^znLZPA5W7u{{QhTN!xS0vv@sfPz zIsSk*=P{Ut*w~lN<)%s1jNL^Z7)7<-gHJ!WZb zm!oZ_taqSKbx*JhdS@Q$M^1#~DPf?KnBN0?5t(t45AYt_!r0$z@Hk&)viE`-JC6L| z%T0lyooT-dAWA*090KS(Wi2wL+c)N7$wXsO^obmm(?gVcz-Ji*&8>&vD@?)GQ^Zcb z5dM_ezwO&J67KLP%@OTCPD8@)KTc!qzoT1(yMq7OcXX*hUH^{Gr(A#qPx!BXN9P+Y z4bN)9S^=snSd^l`1+#GbiYa4&g@}+JyufFh7ZzhTK2eT;kym*i#`2~IWjjx@jKCtI zhfw<9;$_U99zyWJ#R&r(pnY#)*$m#&gW5`5jt*k(=mVcqhJ+w|;ImGvp%Eku*U!nh zg*_{C()IJNP`Zi$+~p0*0I(Q9*d*d6$V(@~8l*hH{jH&f1kqKAMwxX6rtqJngj;?w z2wCt>9%1>C67&fPe;Jn$UN)>C;cg|={Inr~UuAQ&lm7cd%Q!LD-bDNh5q z3x0z8AjKjYcs}(GQnn*IuOF;3*h>U3kyHv-F+nJ-eqpPksrzM@Z^kK<9l1-noyflu zEEltkhuMkr+W`>S?O%EU9VeLp$?Zh)j|c#{okVUc46h52+DW9wH_D73Om`BQ2P59$ zqDcH(3QzGZ^#21A$9OHX2WsuoZ_HI-v&n{pc>2WYxIyCL=@X|3zm>RiJ=)qJi92h6 zeG-RB`)eMIA5ejMaV`Q-uYjH7aHZ!Ax?G8zID8EBnW6jG?BF1Rw;(ST*LWy?fa2I{ ze{U?8;dIakuzHB?0&ZdU?8(?=P=~8|kUe4$CXRruJlFX#dWT1gC3ta{^(_K?A%X7A zdeC$v<t%69qkb%OZMU|Rc=cl;T=6nsM?L-#5q;vU>aB~RqJ0p? zlnLC>XeWCHn0+(>+j)pH96%X>v;j8fJZPW=i1!#}vzH>i4)M;FaKAob7;ESE;>=z@ zLqgo7U~{!$xUrK$Ei(;d4TN*YVVv7Qa`3dTGArUDsyKr5UTOTXW!Cm>l*jz3HmC`= zqkR)q9WSPd@5wW!huG@hFyGwYz%>EwnX|3~^!-v$cRNw{{V@A*%)k+fm-A5P1WeSc z0Mat!?E_)2oI@P*l)mf&agE1YNf|M~XE>G&lI?5Tqia47?*VfMgZo|H3=6n2P$mBO4Ldy(6)2;8W;C zi2EKS#@x;`opS&j2ao`1pN6;|66oSON&9KaBtPPxmv&2|u|c}HPIByR?SgKu8)c?V zAJ%|F&TA-!*=K=woY-usqP!orMk)Y^vi%;!2P5vBg_jaYYwE$d${J@T&8Y{{p1KxP z`-d8A&#^vrT7u?uby`pH->Xw-&*#wbO6vF;YnZ(V=xrfjdWTTwEFgXWfX><{5pQMf zj{qdT?geifSowd<+6?ZC_N-0g{}ZeoNe%orSsN7cg*MjCL?=3Hk4F4ouyz?0^|AH> z#0{(+&$WzHF%63?IV)e7bo+CS-GW(d`ii_Y)@ zew%#y3{O`0cKPKQo*K|?HcX^=+z%OR3yqj9Q{Lr+y!*|`1Za2d?(*NyP+ zb~2^d$^6#LzMz#|qxZr7+wL@GzB;qDaaQqBkaed#d6tJO3B~fGvpkUwbo}4rTR_J0RG^%ntbLxJ_T=Zbbg=TrS~V(Kj1-ppd9o8pXfVO znzR3h5BT4ekignDgsVPQY!0PXp8f}X#4kzzk9n6Klr!wW8PJ9O1bu9p6h*TuAYkqHYy4Bp7~fFI{_%={-K=!M0SRwW}79N z*+0azG*(W?x>Eb6le#-lfo_ZFYM!_Mr z`!U82{tZB)s#S`Bm-2xEt#b2TTo0m*1~uahrCAO#_LtX?c0W|qqs!>+qFX=ZIf2?6 zQXzKEsvWrMDVO}f!{v{k@-$!0GZxOywB;0h)|xX4_r0dUbK|SC895PJYZFoD1XdpkgNCeww#O zTvfu@62_T%EQh_N1>Fg-1f3m`N6?J7#8-U^sxLNF?*>l!0UG>5)%{L@=dGMUUHkR6 z>Wu@94L0i{w6opUW7t_chY5_N517Ted#WzdCpjs9QAr}Yc48pq?|G>=ZKyI*2ugA zH+^KlcRa$}?^gIfI>_$d@#vh7d<_v{oTu7n08vAfQ=;dfq15|H70AHvczDT{ z`HbD@Bhf)sVl*5Cz@p zy#@JtG(TWtA{4IVyX5-wJcj>6-g}+N>O*LQ0z`{6#i<1$DAT}SuB)pCb*npWTM zoZ{h*g-?PikT12|iL22M0aWh%8ttyTnqx`R?z*cvE%PuR7^Iq0_&9Dm5`a1?DaPT~ zgzpmY>wDYo2&i>=pijpXS47onBqK+U!U&q6C8!t16*l*GDG&cH#ZiZoPfR`qt(^xh zYiox+xWDg@d`Gf;=to|&e`F`+%1CAJ#{EzBQ>Ly~di9c-WlHB8?@@xBIoa7cy)!c_ zDl%RDdRDl5%L%1QtSmaO`0elagtB3=A7OV~zA!>W#M5I)Ee+L8?%eV!S3@2nGsaa@ z<0*GFxmmOPv{*#S_>sb?B=(eCtUtf_8nVjsx2M~cW~dc^~% zG~~`u+Z~Y5Yi1rJi>=91I^SKM+nkff*2tJqq9|-ViZ<5QR5z8nTCw<8E*>Sq19uhc z-S^I1#2%E}Mv0F7)q@1s}mMeHcpP=m5TFM8LfxsIxqdKz-OG&W&Y z=+cgs=9{Lk_74~%LO7ox3-d)}#FOjn;5p@GPAolAakQcusx5x1hZ&PZ4)~r1ILMUf!i5!Pps%HAB_{X z9+zS7i3zOnw746}U5#!wGzqfWP}yATu4|f-#d>wsho9AN5j!C_jT1A0?04ftlF~a# z#*G)9Ze+TG+M&eigg@gko8ATJ*;3WMnJiH0V14pe>_B;P;-`0W0d z@gh;+R*oE!EhfkTy+ntEqZsX2_d>Q7EY{dm!LkO!hyq)e*1DR?tJqX|TcMcRJ*IVf z`eZF)Lt3HUVD+0YaBb>(VIs%NPDLW7Qz8b8X=5QHpoKi1dCm2VZj7j$O)Px9oLM9y z(w-lVFBlr#u7>ieQld*~lWTU3n?0?~2TK7m^{ysNW*vyK1r6i);gKSdz&Fdcio^=h zE1k(LHZel(oFF2ShGyuP^vnX93Tjt)N>x@>nYxj6%9kHb5FG{HYLMgOM7;bWM%Yc{ zzm-|dB3c#|i+;RWW-SmQg~0MEY$dg!^@SL91y+sCwTxr#wA9|1h{wT~zTx*-#OS8z z+A3k3emWbH`J4N|xfJhmd zP^QxWlN1RxKnjP^x4Jd%I!4bLL7#X^f2Rc_Ft)~pb>%Ei#!VA3jvDBpIn_1b4t9$i zK21E#TW*{#a`7Gh!0DoUUil8_~8<^5kNE^g{qf18~(}c?&@_eo+68H{X{*o=a$~`kgh?l-DcQux( z3LJ;y7!R~zBdcwl7R>Vp8uauWnk% ziXpi)ElmxsayJ8tY$`2c?86+ytLw@e+@xr}Krc|u^;Pa#cPScjW3K7JJfe9ci$hnn z?xreFMI*~pm2)@widlrn{g;S0K1I_MgJhgl#6<)^Es?&dV4Xo9ef|4Fepx2w z@F!)7OH34ab%5ryPuK#;eZw3QlE)qDddKh@+y2$5VmFUcU`A-m#1*aVMtX0lJX$V3 z_osV#sOVlmvLl}xD+;CEEjpwI(+qg(=DV@J&{Yo16&V~X8R%=#Uu0IL2n(S%z4WQq?3=6Qv`TSF$mo3BKXljBFnZ6z zeJR7WJEBrN=>xwYQtk9fy*g%wd(GM!7+)niD%pMHpem6zQGh)gnj4!+RpDgA&|o<= zNLK`06&1b}jUEJ~^{qK4Ex~7DCAaEMDB71m?pk+aqpQ-*X30mZ#3~U7g|lS162iyI zS=FLb{AxJMTMYqnOX@2y`w=L}7q%;*z1U7nuA9t;QW_?sHvwHW)s=OnO&(+4Ph$#- zrxtj?XzS#xt3-la94ajP9dpHGF7RCgt*FJ3K4qWBjsd$OvLVLcO=vu$^4vTz#Y-lT+H@YGL$z=NwN%i>sjmn%KwrjIM~4mNrzDdOWqP6kYokp+&48Y*jp8 zJ-!(PdpcXs$m$DSy&S_OovGSAIT)_GpkgSV26a&lq7UB<{bNbGzec3+*W{ZuVz0mj z2~zCKLnnhG6`p3S!=+Fb5D`3gCHWu`D)Z_@iHVLhF#jrs7-b3XTQc~HI<1J3JI5Zt&Eq zL}z{F7xkix_@30kK#?u*q=9A$6vYaEu}&ZAwU>(G1o~*C!mUmiR;J3PDmHVNjw;Nw zQ~R+xCvoNTZt{h(q9CDX@3e%R-o5kk*e#f{a!+lor;fc34L}p^uj!u|Gsl5GP)smO z)O>nANEb8+&n`$+ZFQZ#sUSa9gS);48eUIw6V+7?)vB>ZqI?Uq$2@k={;^Fk9OECt zkbUZL9mNC)RPAA(K&iVL8e9w6B4CZBQcj2w(`4~{k&?8T6h7I! zv_&AJm!6~mr46iFL>BctGJ->ruLo+i%@AxTzzuEss1K%<-gUy1Lf=WJzv!$a^^+AX zqDL6LR@=(=rQjyE6I1X)UlA4PvEgc?rvY{-t?(bplP#i*Zz9_)D*8N7RdomnNENzr zVd<-4I8r4+oFNY{5HbFzK+9rxV{=Ut>zXG&Tp)Vz2W7-Uks#n^CXQYrA6_WBEBF}y zqlLm1yx4#;rWap9$+8||qkpS)mB*~zZ@gSgOs5xIh~a2w(yE@070_I1+V~N4jW~5Z zUM{{|q{VvDL0pWqsTxe-p=ous(1wmxk7sg+zewzyfy5zAp4#g2$*L-%51A{pb%8db zaC^$kWRcT_ZgZ<*2Xi~S0E2^$Q&g)hW%3QGlJ%kNk-=bBnm7c z14JGu^0F5EhMQCyZvi-JVw0<>y4#7{sM(gqPw4YK>)?5)AOWy!!P7R>30SiI7r=|v!q;F4XqcuqO>mXwBAlT`} zB7&ch=NF59LsP&ErKJWwDAib_fzb`^S{IBTZ6M(wPh(9rsJH@ih1FQIkeBzCe7Xn= z$YYH#MTj^oTJNsePs9ow6OhpoKk{5OJeWj)NzL`xCelZH9@tQGu&>r#4D?wAtud*p z;gEw5i0E=H`6Spbbnj`zC#EG!MJgWNU$Ru3jH0(%!CU23&2{q{HAK@AdgWo`J9n8# z%$^R;S6w|m(|Ie}Q0+a{=RqvZo&cF#6|Q>V-N+(~HW2(i8v^^*ewY*{#WWpF$t71PA#&kz5hU>blRWGcN!_<$Q1uP&QY=Qa zCab*m2t(E>*)^2a36N8sxfxg-yhQVdzfqL2%Y%VP&*rLWmUQ<=7)nl>=9ah}m<&YM%N=W`hpU_F}4EiReu(u$M3f*i^0> z@<1GJEkpm223rlvPt`qyFjR>1^0&`RY^M%;xGIs;mq!%o$ zI!|3`9hqA6_N^ut8Yfq16!o;grKiqD4s@Ajq-9}aPj|qmxIEKc^p$~0Vs$uuqw2F! zT33vo`SSMFqF>x{Vlr*0^mZw2HGDoUJa#4r<%+oAPcfmisizmfsAW9K0S8E4>n?mP zmbPEkh?H*hIU9@>&Df!bEHU;O%{epE66iONodcC!bqkp?LKdzS5du$_%78qP$M2Ne z*NU`DzUkUJ5Nsp9vS_pK6W~S+d$wl*RO(}M^$FEHf%K)Kx@%YE8>Vd?>~gw|t5QwV zbsx&izlgr$QZSpo9apJppbdf4s|N9$>IQ5JW-r8|u?23@dKiJ4KWGP9uc)5S=wUO} zl+)HZ+rdqBYY=Ef?vHI2zPf<%A5leU3z5iYQ(HZ9qhmdH5Z1LmxhldctG zr<`ux%4cCO|0Xz~uwGrBe?$~}*G5lmtJOu1&Z~Y=%ncM+BRd5G6ci2{QczknWc-+6 zrNas*kDtT}unpdnB+A3!5Z1<|R%)rPfOQf_Vv|Q3hrxk)jGi4;83Igssr+G`h|X(j z!xps9uhenRf^|PDjD(GJf0L^F=zP+*VrVS+E^S^zOAj<~U@K=;q!4HY8C=x3Itp>?ufweN|1`#Ik{Ue!rcW|$K z_68BDd^1{JB1I;RGfD31CoCxsfhHPLTg^U^z`W6mbVU8qDsU>pNwpko79QSPzILNX zn)Of*a3xIPb6DJcx&dw<`d=@MF3Gj3khHY(_a6b0WY9ME`&yY)biuq53JsM=mm1RXyo;; zzjedDuikt*cgOK_R|oAlUM0ki^Xp`ia^kBU;n#!*DVvY)=zd+@#cn$hS@16l|3dK3 zF7J*JX>QKqe***AmR%pkh#^5~YrYfCUS-~rri!Af+Hzk+wO$RsA|Y5%0zK*wXhQV4 zLrzE%N&W?PLCQ)Y)bQCvH42e(7l5ydh`1z+OG0lT@>)^yvq0T_!RYMzbBJDi16dD? zAgw$6SiU6Xme-N6x`!Hh!27ls+aMF1u~zA6PjAhT$C5-syzTEqM_)zdTZP4yPjnTh zAI4xq6uIYG5$^9lfanVzZ@0D2l(x>5Jr3KAliT31t-%gkiaDcDb=XQx8Bvef9oA@5 zM$GguTd)a^vf3QZ55^H~L$^^GX#UkWn%O#p@CcWEPmflZM3smFWY049VYlVjax&X0 zMIpnUWy*-|riPKdMcO^0r#<9NOzauga5>l`60OR$Oju0I-; zJ@c7fa+60ycKOf5mIpkdtGG)3;t^S5w9M=*B0|ntM|8;{j62P4TPUY=78RmIKHOQP zic96;&SHVMTIP2VBgI;IO&2jv#LKt4h+2P8FQNmG$N`gMUGF6sC;^_5;JS0H8Fss3 zP}g_{6hykdNHZYRpBmx*4)srPU)~63Mt@MyK?DLg!U%u|K=&~GSkD9JjVE$IRjTz; z6B&mVJMBy42VF&kJf19KH2f;jit!VjjAnlwzZKk90KfG}x8k=Az!~GWDHW_NUqW<0 zBsgoDy`zDc#>===qIh{IMbwHQIjO6N_x}}29!_k-^d!=)n1+H)zsK~ngbWZ?9_tEm z36dRB{~|7R7}B8HDx_O++4$#iX-O5yT~0zlen+>>j}q1WQ!B8|mS3hqf}fMqy9r-v z?mslS7Oix)eW^0ZZ`mD7^rbHJ_NDT(ZlbI5^FitEF5)`&*{`)BCDcX~?vR7Ki=Htz zyol~8NQA#sNfeAevM#WR@CR>}OS_9Y|L4ODKB8c9P?Yto;cXccvK-dICV0HZVLg@I zRzCi+AnSQ-yaUg;sSevd6FmBm%VDiBH8`uM+gfI-kmq0OaMt!Anh(=3dXOX0`g4}i z8PTZglUx%_iZW{M2qN1hrv63`c0^jk2emcHo0edmX~KA1L6qxu(-g;F)``fqrxgYH zSr3tsyv)=?){&qv+qtHUwC5Cu{aHDC1YkdBcipLkX~nkHt-J#pc&O#3 zjM3wS!xov-*3sxV5n}zpgv^91J6gAy;q)*U3!w3gN$n8!7|Q2^EMs!Jx*i**jjtDv z85E8Tqbe%&VWezk@fjT}PA5K)4&z}7;a2U?94{Pygr+|Jgg?M@MJt|0|3K@sXp*hF z3H7XPL2lbQCQ9W$EF8`R)Yym~C#u3_{ZMC_K7Rf?9qn)SYa1iwY~di+IFlsge-evg z`%Y7qHb^@Q^LSA5Z!L_wY&iS_3_8Ax!}_&JOvVrHW`87;+UTEhUPvcuZ;-}D{*Mh( zz#i=fK5UQNKWAb56M$fh!q^zu!92|nIoS|-0I?Brvk@u)00ZP^1GEBZ7@sgUKHHGi z3{Myvp2J$L$N(vSGoAjwpN`)o-+$h~E_H;mc!$F4=J{wyXEM-=GyntXI;0Jxn~~O# z_G?&g)2bO*yF>MvB84X8R=|3L5`YW?OsNLi)G$o3c(s*p8Yqx-Ws^y=t-{-4jJO`!lADRvSX( z?uyY4Gyap)of;|~h*CaRdpa477zTiXlTpa@Glb!tJbWGl7~080&j;|QhP5V}#k(NZ z+Q&5MNihz4gvk=IQ6vhh^Fx{zW2EuZ;$7#P%EzPqQj{6b_*r4Df11{r@%|ukUy24B zxp8Tn+k^C8q*;>Bh9n>6^xP3V2D&-PwaVh#cUd0g`ltUleYTg_(V@SW*#A;sHHV;k zM_pj8Kl}faN1(}c9@jTydNBY)rdyCUWcmQoZHj0Q1iF5SS=W=;FQx8F?r!$PBVNGYzH!&WW-`^=^Z^x-DV0yE57`a{YrGLDo~I zD#MNj@xs&kbPcj-E}PYC=?Zeg&cx7J?=h)t!N?@t+XbGZQbkwY+tme36NKNpyY`Ky ziH_au)MUr$o6F(`s7~6GT=nG@gRO(^MD)G8#|8>>yWMe`9>)@*d0jj2YwE zK^n%*?Jy3sKdpam-)zD_L)i6{8Ohl|a+i6841cJ$L~RQU5&8r=IKcqVmTW8p#xvzX zL>gESj7JxSUrh_nlwBgh_MHibZqEgUxQ>`I3gci`u?DPx_mdex$I)Lf#)447OZZS*tsh{$~t`fElP{W8Pc$B}N)$FUtj8jnu@iFJX&c5~_Y zHvj8S3}*XT?@?Kpi(3&yxonU)TP%@>2Z<3O3z`GvIl{R`W(*b=qr&#VVk*Kb)*+(5@X4V=#47*& z_o1Fm(16#%cJ_jPgXROij&vDbJ&!-&>t0$J1ta~#xwuc` z8E`5xlwGq-u4@w`d6I1kj+|chcGYb{ub;>DRS{*MAnS&T9%8ZFfIky1I)&^}!ZPtj zuqu%L@kuDh0X@GbW;P{|9ygchHo`1XKlW0h7xXkKymO1}EjtVo$s@;ZBDx82&H#4Gl7)7Q_Qq*-RmPjlN|ZwnU1{CH1b2{k@x?Jku{oJpDQLusJ*5TJ%Lb9)?SNHzCxs1 zohyfr6y5xCU>*Vt+Z(&=bHnSz?_Ta476pZnJK)B!*oJQ`{ZEuUI1O&zUnmvP8|G_I37KVt-eMY{UB*ouJ>h z(C-mb20Pi-&rOSR_YXqkiL*pxge~7xEo+Y@&i++DSvE?HELXdlI^1Y?*i*7i8Jp~3 z&gBT%uaCxX49o+t4Z!qD?0oY9>}9|$28Z1Y;4lL%kW~uheou0%JMAHMRk}ta5_H>b zx;$q2?e=!#uCP0tytRJ_N}P3(J=)FDhy4H$d2n;&;TWgW!@{(vLjrc|sU3bBRL~+2 zF^CT~~hgw$yd*hi^kz`w7f~PM)faHF2MznC- zpD}0T32m)(-i=A*C14U)*puAx!yya+usm8jBF*D#&%^^A-5fPo%!Qjnji7;6c4jN2 z*1VaUZ#nbk0k!6sgS8v*F`kLr6WaxnCO(fPPrf}yr23inEf^+iwLMPb{cZrQyg$Hz z!TS>owDI1ah*nqHBW*{ZAI6h)wLQUf1VofE75gNoN@R`1VwycMdBBcXPOq9b&sa>#)cgH?leK$y`yPtqDW?(0_ z2v6^lA$j6b(OX`ZC-VHy9>m#P2a8afZb1zEd!n9Ks5s%Gz zTZQ^{*jl1_3~{}t8;CyoLDt|z$zT5p@H(NWFJSO3A0S=c* zybh%bi>k7iak{aL=q*sm+JjG1>yWR8fll&$WK9DCPd$JRKx}IeI{CIgEE!7s_5tOm z?;tw(VgQ)*5Ygs+0pR=|M1}icMYVcMqTmR>6aWSRjs7xof*9TJ9vsO!j|wbU`eTr7 zXA053P&VwSwc51A@cwMAY?**_zC!uR1Q8Xq`#KDxCD-K(OGqJzI_#)r-jj&QE7F}W z;>uTQS(xf=Jl{8%M9o0-Ye-`1JA5L1HL@&6pVqT72QgQcIPmi!ljg$P;Si(hz**=9 z0dHj8x1Q)S6E461@$VRH&E0^X5ic(T6YyDr8n`bju1H9x}j<%00LV0Lp14PtL?{mI0I~ zTfrbTcnnb$_o8Ax>H?^FGDAN>43`uV-5@WVgp*RnG8UuIG_=+EcwV&Jr zFW^%kvRkO1!k2j9kd_weGnWzdhN_}KsOyTb%**AIMMBv|un2iJJ8XjSa2?xE7{_=x z!x@Ph&7JffoMDr&>IHz%DSYgAFR&ybS z@?}2&4`{z>9bBFl13-Zran$Dnz`uaa$QJ+%5W>>4fXK&N&*ViaZ?^)&k&+RF9YBa7D4M6krc+N3rZLf zty)DD(HsWKL$G+RAbR640F14^^EU`5WGdwxEUP!|Mi_>HAOPO`h`#z2u4T$GDDm!r zFczSQ);AvtZwO$oo)3F<{sELQK6Bz=IgSK?l60bd-vD6bw%!0$=K1mTzrUX-8zj|w zi{6Tm_jiG6-p7g7@?24kl%6o?e&se6un!-C$;ae6?Gu)U$6#k5Fx?Ld%d63>%}66( z1*f?Vr*FO-Aye>}>tu~0IuGnZJ}Li#lBs=4pyC*_J6}bT&%qtRw7V|?@F0->@+ZAS z@P z5>&HHcos^5#zSXe&SXQ*DtT<0D2e#>6xQZKL(&VE5q)<`&YLb0{T)E!*w3)R>I)W? z-A431*cy7!CDa=sf!~8Gp%-DXT(TGe?~ef#1`B1F3j@w}fTSx1fjOTb`HGh+v*6A7HuDmAnTO_B?sj44B~S-XNM)j^0U$4#Ll^ zprU27M8=H((=#Y^ezT<=V1)X-4)o0K`0`pAshvRJM2r8PGc-rgGb6| zNLhwIfm>+6toJC+U*t4;ehmx5CC6a#dx|vW8m`V{w(Plr=rfQxy|=Ke>y0Y!X=#*X zTnAJ8uADqmL`8r85_mLGC}Vgw_F$CW;=|E|Tsl+47H<0iO9hBnK7!~OP#9^{l1vm0 zqpeGzw~&%^x*?yG#|)Izt``x#`X=|DdM_aiGhQ)N)R>FK5PfCTOi|q+>4US~o1j-o?A{q8(hJmp5 z1mdu^TibUNZ7T;B#)E;9tzTvsAW%E2z;)0BXAZ_1W#5t3R)d=WvOaF=H#tAm{z3pd zZG8P8Uz$crp6IZ?)1$4M3yV^fT%kPsTQpVV{Gsj3?uIoC?@)A7j8^ z*mj)!wrjHk5#Qgnp;n>DfKZT{yBTOKQelGpSbZ|5>e7<|c~W!b&9g+7^7#(=>MY?? zzI#-ToGnuLZzcYQrLTt?9L5>dLog_d*CLuIZ=NmU;)ZR9T~Uy4*UaL0Xs%`0c|;$6 zC;u^9bV+sp6F%f*Vd?W26dgK}5Yv_(gLF=URyS^rSSN(Me~yUF6Swjn5i4sKbgA@* z23X3kBAScQJtKr=`ZYxN2GXks==l?oUvYt+&*_1%2_(bjielV@Y@92mWv-KOkb&Iz zRAD*N0fr5@jIW2ghQZvNJ|AN}#U@8{KEmb6xgy6ulC5Om$uEnvy$XA2Jo#3U=$wWM z2O{7WAyOR5H$zSW04+SIr3HCnA^|Un{3|h{k$ep&liObqYj^fR{}ub z);_bBfe_%gg(LJ7YOdl!01&L-p?nH+*og8`Lq%6-I)MEQREwlAKBqXzKrJMnr{*V5 zlxc2x0Q9-pJ7TKFlOH5H=?6Y_Twz%UEe5m-X^sJ%HUzYh3;%Nn$bb6#Hl)QD9|{b{ z@x=p3pXRpEX=98hQP+quhDQG;#)vs-A;z#*Fv0FID8_uO<``K1^4|7zIexxag_D8T z=HrU(BQmjAEUcRLE3|!(P;P}OkKCC}bSb1M`E7{R?cjr@b_3CHu5y;JSXV&8Ib8_H z+zUVD7d?MO15r7rkw2^v-YXP<>&FA-1)PRUVHqxeF2=!)N2ZpD6wxdvl!zp8y*#%> zbek_mgPlp};H1O4Az<;CO>DuhGQoe#@8v0pIL zV(pEo8_!HzXX^?RQTZ}Uw#h)~ms#BJBkKUnE>|CID&~{qS2K;ypk{NSb?zzsaS+V4 zgA?4W$@VyCA@tl9JfN*=_dd7?WIpE9?){jL8I5~CZjQKnuwj@P9RHv1nOe;^7H399 z=@%CLfr0<+jYX~#7}PxlYgsmr8566GUx4&E-5vjVeAlNYvw{{N2f7+p$$iCj*ZOMy z%YC`N?J53&}pt%avL<8(RJk_`u7s{krucX!|Px;PJ=dw0?>gE4V}y5LLgW z1MuGztP;mDU-e2zLx`MPE^=LGUrSU3M=)oFYp=YgTqKHKa&Ng98npBo1W=@-LL`d{ znNcC4aZi6tg-DNyhnqd(5_EN52!5`^k2>IdqI=}!6(X$z4_CVeYwe>Rcr)^;3Ng^EC6zW7sK`OTnLytE- z4A+EPSBkcq*4mBsrVELF;wC+W^R{t74>S@zMT~6&M6=a(Lx4KLeii2g9`!*uNA^Y& z@3@NXMw6o>t`On23r!h8pW%+2&z#}W?#Q)kw7Oj*$l)KfvTp;j#v__VTIf0O!Omvv zn?<<&F>~n}tSQd>C%`+_C+D!`O){FI{-+|%c7th24PGfa*j%7DTzI{ji?7ZMHnnjc z4YH+~;9$Q9vHeig)@kr2IBOr8G8|`%Ap1u7R+UJMWj@Y3AuJ2wururCfpwX8fps$E zTtu<+m}@aQJGe&(R)WrsK>z=@-0+(?B-*p%PjVxMk^f_Eupz|ULVm*y{~xnMpa1`i z8z%PHopyMN+9T(~N0d)2!24FNJK<}x+B_-jvnK}1@khKMf@~L?_%B|?G-jDHf}wY= za&yL>FxO5q{9-3~YR#q`@w#xdZxZy0gY4CEel!|uU0S6)yp9u()vJ~g6?|KJ{7 z*`vy^_dA89;+>!H5)?ue>MhH4)sIStAv=tP@GHzA%3>4h;M=jAfp|qdih#op5V;tL zU({Oc7mwTrbhzY6Yz!`*t9M<3u1P869jxY)e*!adJ1WIY6?nf*f!|2#z~1mb{vi+7 zh{68Rx8VH6fw0s07U!cd<9ksd>;g;2(7OQ~1`w+q9_|Hnl8csEGMt@;B2odpjOBOD zxSsLEYUgaWoPZ8;cGyx&n7zWZfJQxM4QYFu%a1Bi&sw9~zs`k>-@nd9?cY3GhRbq) z?aj09K;3xrjK|j>;rU;_dDhG57DL%>VGOB5u*Mt&W9DM1B4r$~P!CAy6D8KgQ{X}r z%3pn=(m%3qTT`V)q}UE*wPi%`>j+g~pYep_b%Z%4xP$&Sf-PmBQ3Ha&z1(if2<4Yw zX2bV29yeZoiPkIV1c}7u>=}KqmqCqCzMQ=TwJRCG#m}&vsJa3`WZ;Ub?E%v=qwEwR z_Pb0OY7Yh8fbANSP=riVqHL#3iWIh4MB7eqhQW}iH*p={4bzeklNxMfjSybLTU#jomtah$9cfj!tGpw(T_2(Nep%8 z10V!vJbvu$hcgg(JbZEVdr%;TOZnpH9P{k)D$VwOMqM1;Xs^d4HbAK#K@>-vnSjfb<~__cyRWi;?a<*5UjZ>1L$6 zR3ISQ%{138A0}G;SDMI}I4#V2!c4|#5w;U1UUx)z#bxGgM-H*HZ!!;d>OVGdtv5#H z`6tY`x9_7g{!jJ6U3gvXyQu7RCS9yBzao}U^&8wTM0Fa)U)A6a3$WH?Bud z9t@*}Hzr|B;>R~dOvCsc@JOJX7xVIg&ey3^XhGT!`vZX)n>&t)@AbWU2U_H zv>Ct!5Q&pIijmHbAS|J9&^6u7fsS?u0Nr(FAw2@=(0TZn0GmnU3BBB&Xk}ArJZvrv zv!{&@yVqVs1NQT5bZ6MlI!^!Ie!^T%$H1$2;4AEr&bNTxMh0dDN4Vz#@rM8yvb-PZ zR#`pWI2Z0|6j{;CI&HNIS=W7Bg^x-YCx73Bi$y;asGB6 zm59F$8?pbA+s_hY%%Iampll$%KM5RX%yZ#t!qz2rFeTsI_^5aHaz7=f1nP62oy zPN#AuMpFlLhbQv0e0doHXA$zFWn!YZTlQTpN^lKs-Extfxwl2XW;w?xY~Cqo5_smg zaLKpOlreWcF0VHBke@CW-I50XJMR z7zFABM`3~^G)lhZ7bE=Ji{R~ekTqIOasM5?WCHMZ2y$*mIuGf&w>un88*0sG;Go0d zE(Nd{K-tk=&H|*HkuIHu!`D#&S{OJ2S2+v7ZU%l54tENG0}O0)xF^8ZKEdguvCiB5 z2x8eg~%*=R9F&AW6V@&({(qnG>aCzZ{xQEe6uD=k69*@Y!FBD~S%-6!^k5OY!e=RH< zRMGB;@wKpm0H9q9t7Llp(Y3H;7m4ZkF2!pXi8}-1$@fLPR8f)JwJ(r7!1)pP>7Vh~G5o$XJxEW7HiE!KUO&PVN2=2aX>ZbNO;jllN zWi&vvoxca6KenYN1S;-RqFnomH<0@yZm|7B&ppel?b(m?aIGW*c7osPPdpCq<^@xq?KAY%~U*s=qMbz+-U5SnZ zRUC{5eE_3wJl1O*_ENOVL_6+d2GVDn`uH3+zAp*+>sk>xEay3*2CLplf4k8I8m@KN zm!aW`Kqqxbx0pIPaxL!e$Wx56RmNT?;uH4=YF>ewCrveHKZ+B1IpsPLZQXb)F3{K| zq$6&i2aAEVe1bN^JfyW5+WDqY9hc14OV%4D;PAFhIC_;&qNAXPZzRfNt5%|iG&$vlQ?aa zj~^$xQRlwR<}^A$caNj*{W!t8?bp-yeL2|QTkc|qmMxovaNV&= zzt3#JeP-98LQDplsg>~8bodgrR${vb*WBPESV~asgUv*baR~ruP3vuMS3`^5a}e%l zyN0CcJqO{cwd;Cl1Gf)4q4A=MvKeb>VYL~{m|9Bj;$A~Q|0WxiLzaXo)o?qg2wCm(f^MDXsR^ zddtWMk=6^c1}>v*V^C1(EnPrSkW&5Ychyx5jkE;gm)HAhit39O71b5jH#Q8N-IR@c zp?&&+_>JCrfIQL=PgFnTvMbf-D_ZC+9om$gL-;U>Jbs5V$o&>o`S=XTVK&sA^Lt`1O1-)2LifW1*ODk!H{Nf8`X4+Pm7aijPSWCYjsIF$(Qa{l0=Hk?V;w#!%r(Cz< z$^D_~SlQ_gC3^H583tZ`vw$>z(5TE;R0>IN)P%l)*foU|)iB4)$_&c@YJmcaiWX+k z8YzBKJks;9;=~)pD~>BO#`CTBI$s0GTU%Px&GkzLK)&7S8Q~E_JzPz?%u9MyXlx|7JZgcj@)`ec^elsMs!rW z$>!&kAUS`#a``UIKSacLf}CiUqp7hx8=9_JF4#2Om6adtQ_50q#*7P#7tQdN5_~Z# z_0@ZcUzF7>sYVs*D8KwxNeXADzqA?^Swq%)pd%{VuO*q+8`PKPX*-`DLC(#SJxdi#+jz z(mRy5qS`v5Te$_y^%QwQixL*cJCvd#XdaA!oNr(Yrnsd4q*5pb$$$N#q{&k!mGBHZNa6hbmxDE4?+|BIvFc3{zm;J7`(sZLIW_ zHP9}$(P^^R?LhI8R=w>Hv&?s&edXm1uq#b{`{a;>)vNK=;PZndAHP>g6%&{ei5BXT zMRLsDO121*TYprt@G9uxAC)NQ5lmQ|eoV>CHHZle_ zQhXp{9BWY2u%vf{L2pi6`0hQv(S4(SYXQNl3k;JRqhvY0`7;tG+fXsN^ImD+OmTc zVXyMGcDoggy^A$vc(lWS4GrR}L6ya|W!2uIdT)cbexa8J$yXIMO2yHTY<^BTsfMPJjBZgn zsn|!$%-fX8L3}vWs;0z;pzY*L!<9cjWr%?8B*kQqy!oIqD`qUTZj`sWn)uTV-t&lW zy>WMP#dk_lM>gpEF1j{tyc(yigbRYyPL_vpK~Oeds>H}UgVeBT3Z%Ba3C2@XLmG)* zOSxA*)@KW>djtH5J6_+KveP{QZ!M+OI>K_k4&v8%8ybo$y!27FbXnC^G5ke0&FEFZ zqw&GQ^LkJ3_m-Qjs%K0XSla3`aNN;Yo$1GR7!_$msl<9kePLC+>~0h>Dd=m+jM$+> zc6c7_D4dz^gFNEez^*5rQp$zgU{@o=t-PvEle@oF937tqiUsUSjKi0b!5&a#hJO&) z?5$D?n54FxR#nvc>a`^`j9(l#jZeo1NhMfKRjZ)@Lw70nskq$0 zmU}cWskxE#E;=mpHDNg}f<=K6MfKL4!)oQGY~QrvEff=7y*l*)!n^>XU-ox~>-s6)zkO zRizE|xoN^!Pi+ti-MrxCO8MC?C0BkDqQVSD)3F-dfpwdU&6jTKq=6xvx7oX} z5T^*`!hoJ%^NJE2aRWG3>Z_^o)lxOqP3H3!HcfNq=7S(GSP)UIl)sN-XdHBizhqTY zRckmR9E>tt{D2;uWHMKSr$u@*s%KI!f)QCjS-Y-qt8+y-2emXauIup4h6+ZK0s>U| z=rfr1;`;jHCA74Ny8I0X5Yi-=Z)uipSx7e^Ueg3U zhV{Ez&WcdeM5??rLe1=YFZ8BiK~-JRqRJ}xar}+dRzHNjEEww6O3lPD9r%yuUxKbE)mO&KoLF^GGGCU~oKVOCe}$DbgY7EbT%fY53UFhd zTph0_#?q!YBsANOH(OtyIhbYh-N)R1CGi9*oGj` zwI2S$_ibA_MxR5q@Pq2T{xfYI7 zA*ie2uSqOl;?{TjTH`|yvbuU-DV7v!kUbLA?w)bbdQFP;oeA;HG;QyquTba6^AgnQ zIsC0BJqE#3<kx+l9oTqNAdmruT<#6`RT=?iR~1JwDvw_KK}CdTn4dR~vU`NV;krM~KFP?0va zxu5MM>*I09|K zh`t8G7l!2ix0O`6p_4iada5~bojhuO9DfiL(oIikc~&lD-tR05|VF^H zt9&m-&4z0o)m2SV@ku78PfBpBN!rId--AuZ2g>8bVA7S*x~#9va?WT;yPLb74<)gnApcvA!)zPq9iK?}>xxdQ7#% zw-_dJPgQ`?dT@jIg*(VUw%n}E2vF9-QRXYH+SGLm_VFY+Ax#}HAr({)?2C#t--thE zrFp02RrPSxH8=Ve1VQWIgz2G~9CTk+wUGFGTw1U}U!La5m(tX3!FPjPbxjSG)K8vD zQ-`X3^}Skjd=WN1^C7M}H~N4ZhhP|M!Q*mSo32inaj11morOKM4~(B&r!A`dWplXo z4Za$$VXOGl=33wq+`t?g=rzzFfAXl@{Gx)~N#jNpjhZ}t(lj~*D|=#(QYvn54He~f zApTUP#%u^--MU_CT*rnsybydCCwuf#6T|tWFJRqu;i~E@uZ&hV@U}77buN(d>G-*& zqqo|37{C1rG}{PO3M3c8TLWF;8#GWM2v^9=7*|_b4daT9+X1LVX-yqv$O-+_EO}3F zHFZ!U`&QmY7%jM}{8=yHN*{taWr*O`SLtJJU@e7b4J92{TMMI%H&x`nwkm_gW;r@j z^{BX2A``bL8R7+beWtoF@)Jm2fb^j5YFLTd#!8vnqD0A8S`>$y?=c(eEStecWI>i1 ztv2g><;afoFi=H6ptQ1{@ZuhCpl4|j#14FYz|cABu&$39h5rx)UeyLU`DMh-mdnTc zs4>cSV`Y9nHIt{9lAi9#>I33W1VMD^ya{(XV4F0R^?5mz99+S`a zRa53o><#IH&v_gxZ$PcFW#<1K!5r7)4>t3XV?^LN0RJSgWH-5i_~Nwg`BXGv+eH&V z3vIhk%!Izl{2@J%T@@BRMs`LLNY^~{-b&YU?j zcX;$?*Au_EwyYK*lk+D{nOHD(#>@#L#^>e67mk?o_`M>dBHBg6Hve|t4L9z3`?U+4 zK{V{M=AR#YV#WTCUZ%$v)GFffla26s#wm}Sd_44~&;aGsw~wdZQg@ZxLPY#8 z6aRuZE+32(-Q0G6a$kS4efwvTVn9H*7ylugJxaZEn#zkRYszXzSLwC!E6M@|#Zmx% zIb;ROv2h~Kx6Lj{St^7YI+LhcAyOUy@NE$h=VWruj5~jKUj)#Z^G` zjtq+zoqXBc!)|NY#Mb7NJr3KU39WF*qk#_FbaTQa)nSvSgz#tV4(nJ`LgdsCTdoNX zx7r-euzaE|Xg0h%swV;{?jpwTsPswAQ#9Z;b zEJzf?#6Edbq8KB*^4&yHiL;=haE5^l?n-&(|5@Y@35yz#py1yfnJkm#4UFqKp6?G41V zxr|PMJjsueMUChu$9EPnzJG_3$2zuRdK&Q-OoK5^f5h~hg!B_up6CpA=_lKz{F}Jc zp-Y2m%MfqDW&K~rr8z|;B@TP%9CZ8fX`-io&@yZ{$!}7?!N16=iUm0V4F( z3Zg)?k$IU-gwi^>C{@(?;&Y98M8<@GaO;-R8{Xd$W*ue1X!Mj=>k1RbV+zAvM+O-U66IeTPvrWc1qJz4SJ6G`F;fef z9|wfk?lvWK+ow3}Kg<4UBH70UR|3_{)0Pk?7itE;1%iL>M#TE9Teae{?KM}gRpMN+ zX{{)9+r_GuY+ssGO=fLCg8fa?P)99Q9c25_T%<0blVU$EKS~pc=ZkPcZexJk$w(hD zBYnz@wB7mVNXsj`iJra>Ovq<$2yodCniA5cDuHCnGm$v+;eaGtwke@tl-k4Q?9)nt zf_7?zZJ=qQlRs1()~|cEmKg7G+duTfK2GkuO$pJmZM$0L4%5J0Z8aqfA0r&LF(#HC z9xZ~b-AoB%uWxVt)P&sF^bi*_pb1++-UOSj*2RhA9bXYr^*d)Jy7yGvv)Jp&4OM>F5twI_V_Fq;=etmQZ_^>{5XY@k< zrvO12g|ITRWAZdb|UE~O6_6~-f&EwIK&S0PgX#fV&b%+~CuS8r!+NWW?MJr}t?H=Bn zXg`<853x0+8%#koepx__?fGu430)qx1lTJ~CA##1W+PjHX`;tp7Z7I~Y)VLWSVL^R z{0Z9pC(pF@unjfy-x|@u7GW~0QWrw-0!%72wWH`_KW$PfEjonAeIXh@#`sT2cWS7# zBTD{SO>;6DkqiI@C!>(zV+h?lx%<%!plc_0T@2tab!(jlvKb-2Fv?oyA0wxYk962a zn=}zCMWT>RYH3wL$(oz-SrjvXHSM~?ju8CEk0QepS#27%8s=5tyE z8c&br@&->Y0ATQRGvWqMA3?lT5beQS%3$fU6ich6<&60y&{hEbnIsikl z)N!1rLmSUBE~bqq1)60CAY(u$5H|)C>24X29Nk-VJ}<*yWp9x%UbAFSyTy{(0-(i` zImE#ES~3*olfU&AIT5eEL^KKpW}jh-P_`hVe!L0xk{s7Z#I(Dj8A=g*@jh2$`~8ZX z*GJ@v0=cb^nC+{Cjyw^9tj_VyePBfjB`Ubq;&9%;!U$mET_P$t84{NPKwEii+fAZB z@g9-regO%ikzJS(U>%&H&6)O7m>X`(@7W4>c3VPh?{{xa80zci2(V_Fsto!hfES*Y z&^f>o&Vbdd=?aI2oR6ZjerRIf!eMc`wF^A+QbcFn+SM6MV}w7NyY@Ft1I_CqBFX(b zQWo$?Cx*MSO~P3i89-LfG(zE|a}=mHDbd220j}{`M*pbBdo1f|GsZImG>lia!Pwuy zIi=Bl%!Gl4uP0h39j^?tq<5xHMSdn?gIpNtT968O7W@5dI-ypI00-*I*R&USNdeyjbZ<(^+> zwhX;Ry}@y!qs?qokMoGw7OR?z_!mUEB1d#m@*bCu=7==cWw#TR!$j$Gwe?N;X^z+c zC(5<`#B}k3Jl0PP3EH;OpPwt7$7T2a;wlu_+FwkDd&N3HWQm<}-~h4A_seFY$83VK z-w64#2jm+x&;BmVWmxsxfA)7hv^)w#{QI-=zVD%#Taldi>#(-uPNMJq@!`9P z>W}E@jTqS-{`i>LMBflbiSkj`5Osh%5phy@=N7h5wi_gphON4X=q@-sv#&))CUjuf zm)nSTGI6?#FxOIeE&~uxLvA@dnPIgtt+{XB#ksO!kQk&ytd&~_i4svH!*fNqinBc| zBTZ6Z_@<^qf($Q49pWusjAa1M)1#uhrk>3ek?8sD^X>VBsb|8@prz+X0O#8?oK9i# z^1;FyI{5`Sn3#sQgGz^(xSOOL4By^P`QBhLNz}{kL(V~y3(rB5;KWv%q$1ve*$@Ed zi<#!GdvgeAQZK(b-=5c-dfs8~`OshJS)<9VL&b!d%9Wr)=(Jj$GMQ`H?HvIIYL8?s z7%roby^`eV@kNk9^%{zFaO#IBocR1N;umkEL^GbV! zoBbMx0Kli=W}n6hj;Du&X#R{?_yJPdo$apBd>fIR*Dq9yXK);Tjx(sQ1zwFt^a#eU z?Gp3oud@eRcVSpYm{A^QTW5l&&V}pcGjl?OaNAFt6GjVd<#rwg-lJnNh~@S;_XI3i z#Q?DITJsR+{lB;hz|LT5)!QKq(OyFaLaa%jK zaZ!!?8x~Xf@JNy9TW)u2jM2o|-X3NP?{D;l zgd6R#rXwXBluX8M(8-oW0+(N957$hI8~{F$&afr47FtY+LgfD6n-X&CND(jc<>8Sc zb#82Az^^qDwm z1BBWh?6>+I09_TKKKB^WQU;~~2-{Bd;voPI)OkQ+MgJQBZeUMYRc_=iZE0(@NP^%Bkx zKqG&L=I-?}QSV-$`nt!lt$WoEtldHM;{iW#DKNS70Fdb>aQ+U*fGO|uaq^8wvUJ}o;|oN1>MQrcmPS_J8K?$dIZFSu zmgrg)N&n!|STsqs?vt|%#CT=cJ91M2j>2)`|fs(u#4f z;-3KuaIZwej~*gwCn0iynDJu_Y=JP`oA(;N#&C1}$fs7h8PT&7HZxWh zG6tZc+W8^uEe?`Xhdv1bcJkzS5k6uS7-0Gf=%^jK(SkA0nGX})`XxHvE!5s%u-`t? z;zHefEzv>7~!H1MYu~$FhHWBe?U+?J<#}h6iN(V`~jD59U zHxG3`7|MPqp_SPRSt~^xWk|^dEiL&V(MBJ-Z-0%jIKX;i&<)4;#^~`p#P>Y_wm>%= zzik#4UG4_)X;Uz1n0t3_A zUk2dI7Q&KN2t$Bzy9T(qfSYKUTsOtd$+`%5-eL5z|j8-DD7!j>pc_q>P43q^4^}8iR@y7w6Z?*I7(Eb>F z@`YGUzt~MQlYsyLn>Q2XK!GHmfWYqV3&j9}Xic91H-E~laRYnz6YV_$V2bbwbyhUg z#K#&W)H&(!G#bM{eFx;0hs47lvX$sc&{S*A^C;1*e;|hkvH4k|qdZs?BP9(AUD*h_ zTYBsyx*SxFKOij6K11XIW~uK9%j*$v_;MWSJ{2>~g&4T|I-(}bF&E0rBYGP1g>+JW zViD>!2l98bwn3hFjUJga@}MxSc!X79-AzBcOUqqzr!$en0mJ#X#wE=VgBB`0dOzF za{j9vGDSp2CEkb`#EO?XHO^>UyCd?Pykv?`3)*yk}8S|Y+({ErEICuis zr3vLGF3xndI9I^Z;nbeOvZg0W9M$5;nSTp3D?@N`kWglzCAbP8>vyJhlL}JozxlD#`qvNrUorKRG|pjdpVrz;(jvrfJN$<_1Ctle6w%SwVp$okAlw=;|F>7Op%x}^9Q2X3Bt1I8El#$6|s@6 z@ezpU#%g6hpDEUev2yV&5jA@32HqaRKuzT3APf9%*#fzL0KF#+5tel~5&htgugKQZ z$05D^GCiH+eW4Zd<%hF`M;w(yW{W8q$0U|*AeWyaEFZTcdJwqe*TX*Bh{@vkrReK< zmS}A+Fk9}OEpmPH22o2WC=Ig(n7FFIDmprsp}>(2FofV$4(3}VrvZQ#38<#IZ6d<} zFAV#4ygu{z%1#E?zbxFjFbk5(arEk5g<)3!fRoq0!XyrYoXfN)T#U2rtzvFhI z>wbsyj}#W`66^{&J_%;G7gh*#Jf{z7fY+7dNFUS)+Z5c+=`p|$P9ILk(k1tMaH4au z{N5vy#d|Vhj))T<$?Q3zi!TfM`cNDiIPI_=@T)hT6IR!IOz?lAuDLw~zqWRM0^T`+ zE}`%ve73D7t#^K^I#^TH@o3;*sA=x>UufxzP~_Zdx-aG~5}mM`ehxThApXCo>Cs%& zucnI;_g|FUSt9!S-dF|m9AY%hVl6Sr{xn-B>k}pt^PQI}h>{WXJ1_1KR5=2T%f%O% zviZO{Ak!!e37ZXpb6=f>^I|R?80%&swnsw%A>y`N07gjdh5JJ0(@^btkoh#yI394b z`#d3<+l%?v*HNwJi;dI5!}WWOK7Y6W>%~Sc;7FG zP0J!`fE;LQOa-?UovO99^51UDmDWu@=@rR+*~yV}l2CNc5z1mx%~5O?E94L(?{_hGW12BYuhN%xGg;0AAmYAQ^hVK0#f# zK;BX&1_r#em*{HwR+&hOdhBkfURbL4W#62SHkaU-ebR0bAmWyzB0jHLW zZtb3g$yc)qtL@VX*q_TA%f%R9hqvI9hluR=y;f9x6= zct}_#*FiyWe5p{!ff?%FL_7p7ez6lL!d$yTwBF0s_BKsdKml^GuEP1w8054 zZ3n=iEyf#M(XsYDoZ=7alQ56$&89_xi`WjC=2R>fp|)+Ngn+Mbqi&-)Awj!Q*QU~n zHkH7Re^Sf-DUdaSSSh*%-vBFYI%B_5gxXJ;b5~(iaeg)y^w0&0Ar(Wb^!$N5Qst;hri`b3beTd`i6e*^DF$g9l>LkR!3Q{ppC zlW039e{o788P`9bk_^u72T2g{uM8a z0NW1JOpDin>y4&_KuDPDc5}j>5Z9Mx_!TTMw!Nkl@uqOJt&;GCgY4_Uz(x@7;9D+z z>v!!#*yPGlRpLqUwEV3K$9Boj>tY^WiY?!-SSvc8!N*eYOsMNF*M&Yj9h&SIR>B-; zgK&#Ys8`(!?E{_)e;WRUq0K~dIOk^gHq*D@o*L)u!lvM=*?QA+&@?HNu;JG2@yw@) zzD1$P$pT-#DX)gr_Q$jF*5pz>cYY8{Lh#9LFVCusmgB#n+(JwEN!F*$T6G~SwfZL!UU!v#|F6FZDjcihwBHEnkrgm z)QiyBo1iMzqWPey3bwe))i5zMpQ~~v5FGN|N=R7{d`NdeC7hWATL1qcbrH#Wtkl!U z))AXcBg9+mab5ky)<^)6ch=J}U=$%yT9&jO}f#d#=z!yo=&~zkc@=F#HQn$PcI8>i~)Y zqz`boQxIQ(c+XJ|XEfp~5l<|KpL3>Zc!{5OwC*+$uH%#tYqDv8u~RzOl1+qcUnP8T zu7^z1*`7ToZR=bFt<<*8IjXo`HFXJP z2lajb1xE(DKDQrZ8$_p&2?IaIjli)2@w5CGiJS}Vulbvoa9O`2V=dx-N5ceh!-k8vjZ=l;5U+Z=-pTA~fUyUqHJHI5b%`0}=2 zyn zol{{>M{7`+K22lRG4Mx?2?co_I$p*dFSmy|oj`9r12Y0UxK{!39RLhgK7@D+E1v*x zUaaiu2Q!i0iCu+J&~9oaoeO^YBE>Mr_Ld^}`cG&gkURD-K>}HMIypye)8fx zdp@s@oBzCHEHXwAvg0BV=p)++lM;)$K}6X3rkxRzvJ{u|2b9>&bj?vhoxec=9${dt z;&9rALmdNP777l+9N2LXf*i!iR%cqkld_d|_7YP2wO9D2tE(^6~KpZ$SK#O88tf8vs}2fdyOe! zb}??EK9wZ5T`s!BUG<{BR*10i&dCU~Lb;aw`EpSpD&;7j=qz55)jrWRXtoFRiibfl z))Aa9H~Pd7-{(azgc6W6TupZWgjO;Dc-sXyKSz8t;fh`WV59akLj(-y6JmP}~%iak) zcQ&tI1&{#XqN^O%4`4zY;Uer{?l-|jFsp;miV=$bu!Fe>JDBsbs)rhF(0}6oit1`D zXf^$)PaU^1xM9aNdW5L}aG=@mEw(N0@RX1BQkRy;zDI`<8*9MFXCkZCu-3jKg~ z1FV9n^(QyLEJKA8Sn$h>L{RV)0QMxJi|?~VlQSBKqyj207D3J+0HXoS-2h&4T@1T{ zft@13`O{piXbfB+!fbB%;%{5b_M+Fv$jWGFKmif zCKGNH-GkPrU<9u@><;v{L(aGn=i!I8e|Mv}ToF^Y*R2v6LdkqqZdeU(e1UvzwTKbV z$S?6H?gT1Y3r9J7qGB-AWPt9!QQgyVaQ?eIuv&y$hjqqPPP@##SwzR@pbVG40OgBK z<-dknUra(CTrI)^mhB^YR<61k?Jx6}uS5AQrt;}G!-&+|m&a}vmVSj8H4fYZuCp5) zf{vuaE>ZD_zmesL6O_DxLzFHm3=XXC0&-x)dJ_>UG~(jk~(?&NVm+FPERM5otc<1^uqE|y|Qw4sA+WT7Z*SB&S{`hf+i+Oq=COK>q|4y=FKZY@5-DyY)xXOuPbk`qJzWqbW;A-4dahYqKSlHSQt+^n`|DasGr>5N7zy%FlwjW$8>QbcdHc_>G z<1SGfsXTP+_6|oxr{zkYKJxXOl|*^xD8(I|)2mlbRz^l?X@;j??^17;yu_u%ZZDXl zj9Sv4kzP6oSG=ogYwL?jDm?W?b84GvNAsl%IX91d9*lke`%pa{WC@tQ_=-5`sGtg`X%LQXht4b6I6|T~$)=-iv7))(#!Y z?7NsQ>jxvJsKT#rwY(jTs^|jSS^`7}fb)NdhhJiDia&R@2pPm2~^#B_x zFZB*c=z~PItr}~K=6Op7Hs$0J9yO7b_bKE1^Ai~jbyby(MV`iiTKQb!H_gs!nd=!~lPC}OG=HG1Y$dFg6>is)G&Sm$Yk)YMQ4pZ(O9l{K`` z*ax;l#cT9CzXCB{9amJZ375j zQ&QB>2m#lqKN#Coon(C>g`dJB)6i5+`&*Pn7G`vL3t9VQFDAa1*@m@yr6=^w=$lJD zb)EFeNhR66*kd${R<8k`@{%YeB7|a1Bj~l5>ifzfr@B{ub4ux;;{CVncfFwO7EzNi zW17ZjYAnlv=$BM@OXfmVJs?+Qs-g1K_sZnt!((vi+cWnEDK=|DGxoYbcvr_WKajQ_y!k-h9~|6w#r*W)8w8Xl&H*npxv;rriAz+Q;m0i zD;)-=Hsz!zH05;7r7oykR#^o`LpA4*%F{}(K63w$$^bPnpX9!Wl&vQt4(@JFOpjQ2^ zKidkKCO-U{U)jiVRp%+GY+Q)TJ>Q*HB66aE2$;9t%UUWC0$2`hT;Z+u7NMFKGs|zu zF(n)52*^_HZLFv*ZJ@7N!xzNxowPP9yb<~R zj1ryxA~G5lHV_}#*3R?R!(0F>mlstxRZ(e+K7*uwtE-E_vbLjYmCJusBBF#@JIJ~N%1BuoraHt8GX6JZvg*<2Gdr|5lvzFWR4I9bSe=#9&h5|r zraUHMvatB*%g*K&=3*_1qX}Na{ztnai;CU_nivIZYty)FzoN*c*TCr|wKW(?Bk|P@ zOeq^SbPVabG6IjVlZH(U`T9vEb||qkNTaJEZG6F~!c$XP;thj$fv2Rgh&v^I zWY1GoSzc4rSZl`RSIotvX$7@l)v?HcZCg31)nts973= zda6P4CA-==jK6#I*03p8q$zh;o3wY0Lk%nE`&~sv_2osiwbis5J@}WU#rU=cThH2> z@lBx9)mX;#jLg2!*+Vf#UaYk-&cztD#TXdZ)@utge?o5Rj*|MzMGiGZ#oZtIp-HenSnf-Zr$UZDmFLoh zlo_v)w<)_1;Vn)%_W`9#FkcD*`}wD|XTE&erFPF=0j;1900!>MVA#qUUOgHrOByJ? zWiig|3uzvJDc4oW7+zXu_T+CYG`3&`#>dNS4z}nd%vpK0hw@RY)YIsp?`3lkhPt~u z7T@=jUKoj{sCX`L)l##VcYdHuN_+)mEA?tqg5{^Yv4U0(GjN27gi9zA%W`LV$fgdK z3GLPA#5Va+dv#j)K}=FfZFO~R4Xwwb%2RnmVh!)Sujqte13JukRg~={61E7 zWsR{zVDDAE7v@!o*GNi71)cTuYIHx-d_J_qT;c<(#`=YtAuM0tuSCd|hn48a7 z5zMYJHnselkHai`u_4(YN@Xp@fjK?(^`3>aMXn1`uSq$?s{mWdyun~onO|}MiCY(2 z!ogIFVO|+N6L?iOL42X^p68{Z2CAn~9)D0t6f@RzP%{!g0wXodt*k4WUr`B@kY99d zv0#e8LewB9b>Is(RdR6$wMxZnds1wMrtXHtRg+2>*DDs$ufyFm)|nv%Wc8ofAB&RV!v=K5u6u z>UOyDmE0i-*}kzDkUl9cE{;?Tz0>dKO-n)={Re@k`M&U;q4Hl=u8$w}ec)tW4U6Zmyk z77{kL@s$IWR+eKHR$~t#3oS}?6lGTWE%3Q)VzK9+EkpW3=0~YRy6^*TW;W5DS>jhxx$?0nwR2CtN912IbX!LkF8*E!<4g+Vrr2a>WO4IY?!xEF=|jLd;LUn) z1k4e(Li>T9^{^b$P|v^pW9;GGm9OqpI*G%wG8$U{61gc_jp(+I7a#9DszGVp(l09W zfH6wxKv#&5ZrN&lktS-uB;|-0wQJS@Ou@*S2{k2Nx&}1F z63Pn+IFavm>)Uya0HNTdy87A@tPrr7H^!)`iHjlR8dvI@7UC;x+Ac@m%Hr)bS^E@> zxbRptEuJ5Y(p?WcFn%jjn=XAJvh|Whv1+V1E*HhBU7UO8^3JKmQ$uR1US|2EoB1w6zfYu{C3WBE2cFU8sj#)E1lwN+Kv7}1+1qb61~%j-L;{nQ!b zNmhR(BGUObbF~MChK}7B=%j|KN>FGRY(T7dx+Tg_a+Z5#W{etYcVNxuq06DaDtWRp z4r6(Pt${dsZ=CAR;jhxnMizSs_|pUC($<0S9aVNK&~bEF+d$!2@|!sIVs)Q^>}~OC zLG*>ZfU>uusb+42j$(R3pIi#*Eq{+!6MNq<0oY>qXIQXrqA|^|!Y)nY#41_>N_a{= zbpRU(=bADIJNN|th~6(+#BX`F_%zrg#1~1@vq8ysSiq@sAWmx~7U_iTfqzpz#rq`C z=O;geo3&k%8Z4&E_#`zYoj+5mtM?XR9pL3pn}d^>S!@&=E9cU6811O4+Bulz@Zs{3 zBsEFJ7yokB!`N%imphWw4DqA?Gpbajxnsfwd(Akn4XdT$x=cZdxYRdfLA-BVJ6ZRm3OdS^AlkqM@$t!seO z>$}csc&fb$4o2oUb_smnVu130`9RNsE|c}$*p;$piW;fn4uD*@Rf$n?J%(5MBpG}{ z=@d)5FuKMHZ58K70KJ!B%)F_}r3rF>ikj%#0>+w9UrPMxp?`Sq#&%4Alm+P~vY<{5p6{2nG7=zjMT9^8__?=EcRNKnFFD z?PBoiguI~zMTJAhkI5^_n=p0!6bglOT)amqfys_}SrWt#RqEz`F5N3H?XE@-d7u>~ z>|_>~83<;==>0qPHTr(Jv8fIw#>vbUb*;}VE_KPwSCzH02K&0GT)G>W*6K))z-T66 zIip>1B=31kNgl?wB!Bsep|ny|Vj%OduwnwR+&9oQpztJifsd&vsRI9Cv*m)+lvLM| zCreK3rB3OWOU;k~?7E??V14ovT^JKZqQg=+aqBB}1P(y%3Scrrc*oS#z;CfpUeQw> zD2~bJda4P~M<4Z6dy3y=`wVrSI|l6NpBT^{FS^T~%}P=_AJ7;pCo8;f(U+$5^_{o2 zz!qU|x!2g`2GKVlF}C|974>wW#fvmXb1w$-4FW?r2}jOy{b%sH?%`thsrUv@u6tJr zS5imwIA?ag0;JGR1L!U**sWS$u=vF@CVo)`n39gk*L$g#h*L7Jx0+mZMNe=ZR_P?@ zD!^4;{Mfg)H}9j7KcjOl@!NTz zxYi~GO+tQcJshOO*Aevnb)Agqqs~m8(n?kK8MM0Gv9si}ZMLnYuY)?fH($MBnc+P* zeFDMMckK-tPw@9i=7o}N+wi_JElZ7tSjo`r;)Saq+q4ylhj>@)ZRui;CkXKi!5! z(&x~IP{PxcflwkfDDNrhRjm38y?C+8LrQXvS3rui+NDr-TylS)fI@zD`dX4H+H}`K zf9Aw=s6&H8oldC(O%dOxjUnenZ8rFDf3Fts<-hc5_VD#%j;N-%PSE(VaYfR4-&MN# zsA&xr$(R9X1t0Y;yU9E<*lqPvx1OX*Bu#Wc(%a?MZlze7eMM zfL_D%PAT|cjTy9jacwN`OI}1q`xGtGM*J3h0y_TPf_##TQrD5}OWg`O$++-_3jC!x zndbrg_>-(ZKrlaI%7b9ZG5fblpP5W;IP^guGt4#|eyxvbT!?_34ybLQ3FhZ>17J2E z%zcq$ig_7?O5A)+0dx530$-AQ3wC*XQvw)thVFfoEdx8l89T%F5yTBE^QhCmTG>DZ z?F;M2JI%&BJ*x0+g%@F_lwXvLE@peA47bI@fja`OWX~g!<{!^5!NFjb=P|Jna|t^T~C*zMf=o!*|s8BP|PHBIAvK zG@X*F_$lc8HeTh8D$3s&?wcs%j&NgZ4rey z$RX6A{Z==K=0~kZK+o&id`NC@D+9eW&~_DF^oGuZbS{%9emf}|J^yO$Id8q6f;P!w zQKM5BhUEYnA;X&=js%=(KW&$*-ZGi-T^&n#Plvtqbw{s~O1!Z#lZInpqm_oEcvI{D z7!K*?rbF_*e!Z&;3MK7Jj$ST*I}7Tt%Bu3!)g`q}t1OSrQrxe%xcb<*LV7*zRS=g=UTuKW(3Cy<-2Op{{1^;Pm$uSY_Z)O&prK5yPfH+O>bHL4xr z&X_4jF-5(Rzj-Ur;6tWvGUZ%WsoazV4im_~9;UvR#@JcX^9mFy^$h}7W$+JBsV@ z1Ebvf$uYr=P5Y7CG}0%yMaW>BTe=KJxqUnE+}xxKcQ(@>g_M1_N1>A&)vP5-m5M12 zdCMkFkeR9!i4MfxWpEY}*QCKf`BgD!p{e;|&U1qm~*n%i?PB|a8jQnpd(QFHg&c)hp`kHd<;7E0rHf}zzpt_Ugn<;`NJN5cPJJ= z&*4E2%&z4>EpiAw9QR2(AM6JRks=}jMZ@c`g{>I63&qQnmp|fMLvZsIV35s>_8;zA~ zN02_c?gm2nvWM1{J?$bCD7bqpSf-NMGfd4ky%+pbe zc*SgM^}=VMdxr{TRCTa?nk{QvC3B2rtf{KKt)#K6t)hk{iCsRp8Qg5HGth$2W80#` z(@IFSEegba0OjIp5E`B22`MS5u4=1oWBbJ1U_1(OVjvjj;<+4~E(HGxa9r4jVg(e5 zx={3jX|}DQ*o%0}EYkGI6?A!p@Au#j8^y353*msRFboA?|8nsl9OGcU&_|#NujYtN z5tspcZJ$P9Ap(k!Xq*enY(>#{2R!B3Ypp7)WT%1|E32$bS{)rVpRE@qF}Mz@#N`uVdE8v-KZ=5An0;T(&tbZu-~VsR;IqG&B^hqa`_(%5KeV%6fC zc>DriyyaoqEtpWXDjctbk9|HVd zbfscEyev+nVzK*W+Je=xy0xN;br2DYm1WLhBHI~*MD9ZDg$CQyML0zP`^C(3OvD>$ zw$gNLK{zHZEx{mY5kD-!RiG558JG$?ZNJID6{wy~E0tT?t18*MHit}H3-Gb6GYjdj z(JbCFq1H#gaJ*kpksDY~);Nlc-oPaBR};qI*G8ev#;;sEY5z;h)^*TaEpBS6YGrk{ zN7)z%Q{>Njo2A6Ewu$NtnZ=yt_zLV1w&i#lc8GO3SObmXdJdg%gb2^YS#VOU$i@Bg aI6uwB1jrIka&e|8Frx>JVv8A{+W!YE`vx)q delta 2907 zcma)83s96t5}xi|V3!45L7oe;3x|M!JQPqwVFj|Q_yApYQQl7hfkjv!XjDp5(d0^l znbYx1JT;}Ksk+3+udZ6g^HFsg6FoJt%1bU$Poq(yCn(~nr`JD-xvHzH@~_&S{GFl%;Eaf*BA zDlC#Nfdiq0o0Q>DA~h7iSib{3dg;BE%E;ylfv{on?m*>2lUhH5jBL z)6N6v`FpvCU^t(cXM^F=ro0_W2;&)69hr)(VK9QXTc?sdZ=C^AlEHReVTks0m8+sO z-duZ}86%DRKcHbN8oymf<8IVO^RU8>ARQ3~qu#i2({ZMifs65!i*a=y;(sl)QK2J> z8eHWPD>VG=6H{}P*9U)K8kuGg4&WmI-W=< z8JUs#F0U^0_}IRZ&#&JFCO&MgKV`VyyqWZ?4L)=LdKxCeYe!d$gRf{cX#2O*&xcNH zB0t@_A7c31j$o3T9OV!rJ$GEgAu-1GC)#C+8BOs;nx&$WKkf=%ZpCBSTrl# z8QjUl5h5h!(uxBRr`f=X|UT#1CmqA?sE~@Q( zU8lda_=gYS4TA$D^VMNWAD4l=ZubP$j{-nR=6)%GUUS=8e^E!O@jd)4xI)9Jwl@xt{t3odqs5y}6R4E3l zKA17k05Vi7QdPvfvnvXTr*C(-JdE$QgN=XIZH0QSe3<1OWBYYR(Opz~gxGlD!#i-3 zXFrNgQp#Q(V?|})sp+*crYBJL303s5g|Q4790tAS26@V4UUR;d6bOm z{11DqUYp20@D%MvUyn;tZO=A>~Fh#V>7K)Wu*!4QLlF)@$v9jmE`VADTHT1{I zYmXtlKkrgPWwM9z%AR&XUvE>y)>pwYtuwEK1)&G8f@P1^*#eM?8Og7jGl)@bn2hpB zlb?Res{gTEAB{DtvJ#ED`R0Sb|X| zXT2XD1&lB|BLgrH(Z}Y@(%}mT{gJwtQ{Bco=nl0ys+dPIV@=hI9Hourj>=l*urlGV z#e8u%7)L;k^JXyG5w18bdOWFwB~HI^+yt;m>>G|IPx;i8md>em)H&EC;TnMlz%GtP z;Am9lJG&$BCxD0IN)%SYdXY8)b&%^U7=gzSLu|r77P*2hvgrCJgu+2_F&2y9hI8ad z6o7h*I30&6Ac>c8n2Y}TA}=0u;GA=FJQg9Oil>P<8s2xtjlxdwlMiG|b$Jy#5y4n_ zRaM&jQHc|nBodNvF>DrpN}<+dTBfC?yp6Rx4~@kbP{|i- zVV$E^blrj!(U^~+B6}PL!ZT;tIDA(zSVx6wE^n%9_<3o5jU+ zTm^+&8)BRh4e+_M*oeIV_eFjtj)5=4+Dt6*@R~{N?DJbHtJz-hBopt!cIT}u z9ISx9iN_{P^^{+e4twc>>Pq@j2*Z8QISb5~gU~IuPsZW!srYI#)O}ARnWkS48LIHcYmO^Z7UySM`lyBF2iobSyKi_}ukB D)-w%H diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index fc74b6074200ae747426c10483f062b82ab3fc62..44a87a74972b6bb06592b0a648b5cc5c73ae41bb 100755 GIT binary patch delta 291 zcmezKllR|G-VMHtJPeFL03<;4(~)!Q&#_%PYc-hA^*yM?SwJs&ssxaj9I zu9>`NvmvW1FAERr7?~#vY!O%DW#m?1Qe@>};#Oc(U=rY0Vg-pXC@`8au>d6) zc^Ef)Y_Z^!;$?7py5iT$E!`*XpJL$VVQ`vy<@)S%@2(wo+`MdWH!C+cg93vF6HuQ3 z&t!wcxszQ_2TfK#*|k~yh#dQ5rlang@11^BCCI_Rz`z2;ra&wV#2nKvx-n`Cf`mZW V8Y;}v&g;$y#7x_H-I?E;0sxOERc8PI delta 310 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 Xn@RJW93w*U%WIeaa_w^X^pE~8p{0UH diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index ca214b6b2a0a2aac5ba59ac40b49bdf7286fbbda..8b3edc3de407b0a3aba8e31a4b0bf13ff573b80d 100755 GIT binary patch delta 15866 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 DY;bp~ diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index ccfcd1370fb546fda503dcb463096edc0ec15dc2..caadc022a595e1243cbc052a730b98ae984d66dd 100755 GIT binary patch delta 16841 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 ZGwHc@{jZNMJ@(aa{ot$L3P1Mhe*@J^UHbq4 delta 17589 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 MfBCb`@R=X~e^np)X#fBK diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index 673e3796ff897249e06b19b6054312a6cf6aae87..81f3ff61cfc51352378107c4921e0dba424c4898 100755 GIT binary patch delta 17311 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 B1q}cI delta 17829 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

~- zqWtEDN}UH{7(gor)Pild^o}H0sN_u{1QLX#nXTL>wi)UuJ8Z;@%w8N-Ac3CKk|*u{Y%&X7=Mf5@HZYC e{9%pC;4d)vAE!Gn-WWc8;pDe3oD3fLU;ht2Mq?2G diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index 1de76495b0dc29c1a312990a2846f1b6d69e32f2..a762266713e4b46b7e7429362550b3957817bfe5 100755 GIT binary patch delta 4416 zcma)9YjjlA6~24lnJ|-?Np8q{G9-5rkUnPr5(bQcAV^-5#{_0#2*^Vu zm}-MO3>@G@5eNtftTte;cFf+x3i50AvD?5YU1^h$PcLvZ84i z4!1_ivdILj0M^WkOI4MOk@*Ev$4}4Bo3POCh{{QHkGcDvv6*uhEuJ_j*E?}qVR1=m z*?qHSmseCy%B@-QVDr3OUsHWU<3o$T zwK5m>u(@&;yXlW3x45<%y?gi>fFxED_bW(dO8f$nt?_F}UX6belJ&NP=OWE6+a;O*_0rLNV(CvKyivB83WcZ{(_Y#sTS3wq_D!QZWy7T&YA-; zZihsXEq?8Z9}-zq+`2l!4d7-)Im=1@H0LU~_1zO61LEk+tw-ABy8H%`SMvV@F8y*r zvJ46A>q0k4*VM`KMpa?%_7r{F)UpVetiLxS8(=sKlq9hCOA=Xr$s|L2;Fc}#)+m8Z zEA`Nn=F$Qg;@SS0g>-x$%yc76-d>(ehvO_?NODE_A7dz)+856g-wf^GA(32GSCI`a z#wxznopW9Vn9QPElF0dUZ-Syoly=F3k`NZf#df#QGHm+Xc#2;=_xEyi&}maqdsp(S zCRJ9l^eGSIo6JUf*~c|bw!P+ls)xVTv=1d0hmso}=t~#Q7z%QZf`Ad^)ZzmsTA;4} z6Nm~gkUs=1YnMYuQcA-*2g>`kdPO3gsu$`vTkR7b&3qu*#fb zc6M1J+t@UgebJUc*0-Db$jjG!^gCYDTFS`F7wHuhrLO5W%ar zbUG2z^zN1Oz?wpZXHyj=js0TVBo@0WlW=Vn-4$LoWAzl0>sPnaHmrA*E+vP)StniYRrvnxKf41=weR z`WtJh8lC#T9=(7GUe)Tsljye7A}44mM|iV;T<`J(9K)!c#NuSP8+PG*F6?~zwsTsS zi!xf+l^KrqZrI_1;b0fM*LS%}0uD1#rfAoUXyPbDv}%27l2k=S-MlD6j1u+y(?2EQ7m1X=12_PRxHY~Db<;n7~A zkGOcWvqB%OeQN0XpMTINQ~R&%StwKPf3xX0%wZcg=a77P^E{H4EpI^@`}LM*qll>1 zLj)tkMe@kj4BGDQtku$x3J!eiop-yvLdh1{bIc zEt=9s^DB0Uq7UvEX#kgR1N#So1oqyJLIb>RfOat}V`q3)JX^SPg-6`J-z%}0|0%I# zIt!8iuXffNk-CVyJ)AS&u2Ho9rd{Fndv61;8Ni3T%8eQQ24Ey}Di<>v_?mDVh#O_7 z4L;+QN#AdS+r`c9?|YOE?}8yKo~fidf9{!HnQA6;kEofNJ=ctyaj4No&0M7w72;gA z_YP`?#qPTpW0bAxjv_iqel?cT^wmChTEHQT2gj;q`PJd!(#|q`JYk=_ev!(W{=35S zr@JlWOIKkM&F}3`hJb#o|6UV4>mMJyK&Se%L+^-t;i(t8A&*5J*-YczN3KIq-+c6h zsM@JN9tm`*nbl$8er+2_qwvd)C$a+r@tFjOOD&A4W)CI_p$OqhH;GSNdIa6_5i?Vi z@T_kJO6a;XYpR%K^lZiPaKy2Oopv=We3FNSU4QF%i=Z)H--`4(_40!m2_uKz z7@|eDqvEo~8V?SMu!Y|Jact2mFM)@}ygGv9_*d(|qi=ilGRAncu9MWE!^pRX28xG0 z{@UB*GUt?&r2o|GP_JkHBF-$2v;B@u8<)j8&${Tn-F9|4Jxt~|JBA*n|9SQGEpLA4 zQI7ozY~vbz^(&f}v<8}jnR?gfKa7X9AqzcEo{TTcq+7qp{V;skhu<++7l7HaFZAqK zI0kp***p&XCT`7vZ19Dqn-hPLi$a zeXBJ#kCY3fEckGoZ6uAa(Rlts*=kD^oD)d-3s`MtQj&!74OnAul=7}TcF^@|vS5&yCFwiFR8oeB<?FUh7}DWQ-c}5`VB!agVSemR@)-Ip zI*tkU^JO7>3H*b)JoKMZcpagZpP31nj#a6Wgy*dmtDUySznKZU;7DluEQmEZP7@U2 zEwha$=ouO0gc0v*OAsG z5Q;32etttZpB{7;%&y&X=AA4i9_@+5GWV zd>vNu5ZZsD3G?ASj{70vLnpdvx}*eXFHS)RN0P8zpuhL|g8Xul|m&fm|g z*5FIv=hn425pPz7?p=$c5I-;FP3tfndiln6m;$pyht}aK1SfCo!W|ZIC3FUttmJcN zqYI@MdFlpSBvbA3mqZ25B?RA0H2I9~xX}oAVomns6za7^?Bp<=B2depDhF9=|GQM#K{fFUb=-3YY z3h<*+{`@Y?lTU_ImCC=_h12Yg`-x|8MMr~A`VlYT_!;aA<@8~+jK8hs#k;Wp5<`#e z#ty{Ks`)?n;3zoAllEd0P3iThL z1B8wI=g*@vwYGRDVH2{~Nf+k~Y1ujJq`%GMUp!AI5zWUO#6Jz!DFfwoYgSXi%?t#5 z9nzjq;vvieMZ6(&`oTv6v=ZHWT;p^nwjIV5bX8qBOl5qEAAA8@pr1RA;B08%HAira zOqU*yF2Q>K_aitJv*+{Yj$#I%c@z`iab9~AC*wuKRzT(d`=h8w;Q7X|&%g<+{V%^; Bitqpc delta 4353 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 diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 58f87a9694..b82f3b3d59 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1353,7 +1353,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.8.0" +version = "0.8.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.8.0" +version = "0.8.1" dependencies = [ "quote", "syn", @@ -1410,7 +1410,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "derivative", @@ -1420,7 +1420,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.8.0" +version = "0.8.1" dependencies = [ "chrono", "concat-idents", @@ -1439,7 +1439,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "namada", @@ -1451,7 +1451,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "namada", @@ -1459,7 +1459,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "namada", @@ -1471,7 +1471,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.8.0" +version = "0.8.1" dependencies = [ "borsh", "getrandom", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 94b01a5e06..8d9f4f4dc9 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.8.0" +version = "0.8.1" [lib] crate-type = ["cdylib"]

yeP)4r(4GK^D_Vv5)B{Z@O)rj3NLX==v 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 TnRsSB@YKD}K6Nku-p~FQXdH6Q delta 6781 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 BP6z-1 diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 57b061685cc46e16f94d95afaac8e36b3f141778..4e08217e64b38a7dc9d901b42a87ce7b283236c7 100755 GIT binary patch delta 17259 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 delta 2772 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 delta 5761 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 XowPSl#hq8~dE&h*FTZyMnqK}7^>)UO delta 3870 zcmai13v`sl6`q;@|2Mn&cQ=0$Ht&$xO+wxYK@5RxLMD(0d6UQ^A|||Ha}a|l56J^6 z6c32NEj=8SA}=`hq}0|_k4mvUt`sY$#};U9r9D1+=iiX1)niU_XYRc- zckX=m-noA+er`MRnQhmUNnLGfH~!G>HcgIlLM35cU8IeWHl@pySxlu=gqXA49oh4A zW!~L(rf5`Q#6l=#lq!x8rt)JX~cF=}BUl4p!El~QXcGi-{Y;uL@7 zkE+p7jqbj+yeTX$);TsQKWl1LsBX_Lo>6_rMB%+7A|WMpTv~cg-kk-L3M*?H8fVs2 z*Vi@QHEZ@95&Eyo?Cz;^!Tz(v znC?*=gfq^B)yD~^#%%bnGM)}XmYQn`;0p0Pw5qFzr~Y@<4=q|FLzLk0sx zagmOJ?(n%tFNJSF+8nW!j`3fPXkf~9rw%>&QBdX@qjQqZ438pcWd|&B-OETloO5;K z;GZ6~+Jg5xo#3U>{!bE`8FfKb(ixfvl+Quh!r!0)e}?-Z3{I8*pl3cyj3>Hy;y7M0 zOPt?{Si}&WYw|h>Ln6edCQ~;|P335{Ej1hGkEJ$I?hmAXptwB_i=F9Q6}KM8{pn(G z7Ymwp4Tsvov09CpVjYE^@LaB<<8H+a zJ;9uTqlG>?0gTB7^bb%uc{3dk7bkDUTyuZR+AUMo;@X2v2@qSLpKRs3rujG{{>^O`Oko9L+-EzW%aUQ9%al;T` zR277R6Q6n9H%jgb{Hf|u#A4ZuZQqGS5=^VEjQx)DjyqwEHy@69r%?^APp?FpGd0nU z1tG>vAsBoyu%{#yHWjv*!ZNyd08x;*QLe}ri#}Jzx0Wf7NDQe2zI|B9L*yGH)27N{!C}~{ ztlJQ-U~pva51JS$MkZ(JtO_Fp&rUrM-h0Rd8kL5%+(GSjm{(fl{HoNU!;#W-yx4C` z-8J%J0o?LcuY^lNfky2f1A%z0pu0%He9s zGF&+VDEv*tVpjGPM@dYR$_l9+=9Z;6?4xTC)4z*2I9xU*Yzk)?tQO77OF69Yaal#F zCJ*L{Jo^#1DyUnNNjZ#w@wFWY!#5n$bOQHJE1QU6`kKemK!9KxhUwxK#zjmDS`=py z4)NI$e9dg9rngZ#01L{iX(sfQzs3TGVNJyhIlNpkm!aFSm9=pry9<3{$I+3^1P^|x zx*^mn-&&#i3@=30lsk|W24;6qvb<)cDdAFr)!~IJH4ETibs{`oUy7&=)vv-@E^W9E zX@7$&RKAzNDh5&eZ(0I{b*V`2tviO)QeT0zuD%S08h(lAYCI%pb6;aEo}$Tfhb1=~ zH<}VWH$>Rwxf^B%*Nm4E)7!UZwx}u*1hSeQG#{ABYR?v$3W2677}uOZC;JBeM%aTkjZ#?Onnrav*qV@L7qs1N|Nb4HwC3WDtZ9|^^~ELv2A^uVId5K@b7lUhh#oR3>( zEndWs`T62(WIkHF0vF9(vMftB&oFf4sb_{eX&iSNvIjc3%aBJ%WuK{pKQ95?yLRb9 zq?ebLQI|h)*#M35a$7n(%iLkyCeAP)j||5Dqvg3l)lg71II7B8Q5{qTf~rfV%FKxE zD~5O72&%4`Dw!JoH&%S2xUmI`9h)%U)5QP|^3y_`mwcM|*yR0JSEf?@%nDsq9Q?Kl zK;5dA+ta>PO?1%z<*H7KA8VZ*mnbI0><6C5ObA&sgpgcX(@%?F+k<&X&pgu+a_(=T#s3HaPw^K`vVU#+ujN{b1~iRM0oL)iN(u&wkK zpvnHGp9ENVvIt0h2rj%9R8Bj1KE;Fm`-EKTRQ^}ka%p})G)u!;Wxc;@OkDeY%&*mS4VyO2RbwyU*{xMXqDEiUhN>V zzp2lzg?EmZ-!5tQiLTqzqbJ(n;b(s>-?@sudvNd{>9gU#CH2t%9J2M`IJsXc`%aq5 z(EeX2_^-=_Q@RsQoVA1VR19r|Rj)+Dgjc-u2+sVDyan@yLcY!vY{vW34+W-In diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index fa5e6bcb3ad1e0d030d9333b252ca90debdb2237..dd51d7be2b6bb2582dc20bf7e9317df2e19e33c9 100755 GIT binary patch delta 20437 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 I%s=e@zhjuQ5&!@I delta 20848 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 HkB|O8nV}VJ From 860e1752a94e14f53102e3a713ddb02c066a94e9 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 22 Sep 2022 17:40:29 +0200 Subject: [PATCH 110/197] fix display proposal result in cli --- shared/src/types/governance.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/shared/src/types/governance.rs b/shared/src/types/governance.rs index 8a8577abdc..5f82335cb2 100644 --- a/shared/src/types/governance.rs +++ b/shared/src/types/governance.rs @@ -5,6 +5,7 @@ use std::fmt::{self, Display}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; +use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -101,13 +102,19 @@ pub struct ProposalResult { impl Display for ProposalResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let percentage = Decimal::checked_div( + self.total_yay_power.into(), + self.total_voting_power.into(), + ) + .unwrap_or_default(); + write!( f, "{} with {} yay votes over {} ({:.2}%)", self.result, self.total_yay_power / SCALE as u128, self.total_voting_power / SCALE as u128, - (self.total_yay_power / self.total_voting_power) * 100 + percentage.checked_mul(100.into()).unwrap_or_default() ) } } From 8e8af7a9cf11a91221356d19a19d050e5cee1d15 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Sep 2022 14:37:46 +0000 Subject: [PATCH 111/197] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 898f6e9763..3d1538fc9f 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.16097490afa7378c79e6216751b20796cde3a9026c34255c3f1e5ec5a4c9482e.wasm", - "tx_from_intent.wasm": "tx_from_intent.f8d1937b17a3abaf7ea595526c870b3d57ddef8e0c1bc96f8e0a448864b186c7.wasm", - "tx_ibc.wasm": "tx_ibc.378b10551c0b22c2c892d24e2676ee5160d654e2e53a50e7925e0f2c6321497b.wasm", - "tx_init_account.wasm": "tx_init_account.adab66c2b4d635e9c42133936aafb143363f91dddff2a60f94df504ffec951a6.wasm", - "tx_init_nft.wasm": "tx_init_nft.d1065ebd80ba6ea97f29bc2268becf9ba3ba2952641992464f3e9e868df17447.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.184131576a579f9ece96460d1eb20e5970fcd149b0527c8e56b711e5c535aa5f.wasm", - "tx_init_validator.wasm": "tx_init_validator.2990747d24d467b56e19724c5d13df826a3aab83f7e1bf26558dbdf44e260f8a.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.33db14dea4a03ff7508ca44f3ae956d83c0abceb3dae5be844668e54ac22b273.wasm", - "tx_transfer.wasm": "tx_transfer.a601d62296f56f6b4dabb0a2ad082478d195e667c7469f363bdfd5fe41349bd8.wasm", - "tx_unbond.wasm": "tx_unbond.014cbf5b0aa3ac592c0a6940dd502ec8569a3af4d12782e3a5931c15dc13042f.wasm", - "tx_update_vp.wasm": "tx_update_vp.83d4caeb5a9ca3009cd899810493a6b87b4c07fa9ed36f297db99dc881fb9a1c.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.bcb5280be9dfeed0a7650ba5e4a3cebc2c19b76780fd74dcb345be3da766b64a.wasm", - "tx_withdraw.wasm": "tx_withdraw.8fc0a3439ee9ae66047c520519877bc1f540e0cb02abfa31afa8cce8cd069b6f.wasm", - "vp_nft.wasm": "vp_nft.2c820c728d241b82bf0ed3c552ee9e7c046bceaa4f7b6f12d3236a1a3d7c1589.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.6e762f3fda8aa7a252e2b29a4a312db91ded062d6c18b8b489883733c89dc227.wasm", - "vp_token.wasm": "vp_token.c45cc3848f12fc47713702dc206d1312ad740a6bbee7f141556714f6f89d4985.wasm", - "vp_user.wasm": "vp_user.d6cd2f4b5bc26f96df6aa300fddf4d25e1656920d59896209bd54ae8d407ecde.wasm" + "tx_bond.wasm": "tx_bond.69b997687f93801f7822ad39032ab8fbc29ca989317e94c07c7bf3bc4a2a2ad3.wasm", + "tx_from_intent.wasm": "tx_from_intent.1f8b246e7d0c4e87188018215eb2860082ef99ace78a12c0481a39523a0b7851.wasm", + "tx_ibc.wasm": "tx_ibc.90505336c947d962bb02f4643dbd294263dd3bb88601d766f3cf0f8d17ec92fb.wasm", + "tx_init_account.wasm": "tx_init_account.a1bdaca982e027f068cbd2c17ed9de64396ecc5ba3a14ac7578fded20a829005.wasm", + "tx_init_nft.wasm": "tx_init_nft.18ad3e76dfe4c3d3bdba1069f668bd0d349aa8f99cfa2c7a51e5b6bc782e3c25.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.794302c125d17068b40d25a67f943ff520f0f83bde3233ce24bfc227c61407dc.wasm", + "tx_init_validator.wasm": "tx_init_validator.276634f0ff3c6c123a110459876e8c37be25cbb2d0f28b320a91ae7592aab86f.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.ac9a7edb87c605457a39521c7a4ab0786077043e41de7975a996e872f6519f5d.wasm", + "tx_transfer.wasm": "tx_transfer.e769c543c2f9c0bf76fbe73af8cca79b3d6b92c80e957daa278873f85d5ef1b9.wasm", + "tx_unbond.wasm": "tx_unbond.8a4aa3106a1f594da45fce6be76597735eed366a4aa5c462474dd6536d845761.wasm", + "tx_update_vp.wasm": "tx_update_vp.f75fc727bc0f9e13ce78e8cdcd5ca978f4becf1e7417eada81a42bdc57ad3f03.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.1c50c26be4ecf0f8b8893a1436768a77eff1050dbd51ab1c22b4cf458783f6ce.wasm", + "tx_withdraw.wasm": "tx_withdraw.59f41e331871b6f763e511d120f15da384a9c8d1f7e56845bb67d7a01fc6a863.wasm", + "vp_nft.wasm": "vp_nft.ccf80799aa2f9839e1af7b8be1e99d31d793c5ad100fab89af22f34aa6fd9e4b.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.181e1882bb09c5c2be54fec6454c3490a35c305706c11bec95ca739652555c26.wasm", + "vp_token.wasm": "vp_token.72c51248c57b47039d43550dca719b969ee83013fb9c440470e704316a6075b5.wasm", + "vp_user.wasm": "vp_user.6a05a513c383ac3fe39a993fea923d8bd2caa9e259f0ebbb95dc56a21ce813df.wasm" } \ No newline at end of file From 7119de488ece0e05ad43d9ed67c20342645976d8 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 22 Sep 2022 17:17:20 +0200 Subject: [PATCH 112/197] Fixes specs --- .../src/explore/design/ledger/governance.md | 6 ++-- .../specs/src/base-ledger/execution.md | 2 +- .../specs/src/base-ledger/governance.md | 29 ++++++++----------- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/governance.md b/documentation/dev/src/explore/design/ledger/governance.md index 63b466b23e..da26c8e989 100644 --- a/documentation/dev/src/explore/design/ledger/governance.md +++ b/documentation/dev/src/explore/design/ledger/governance.md @@ -51,9 +51,9 @@ and follow these rules: - be grater than `currentEpoch`, where current epoch is the epoch in which the transaction is executed and included in a block - be a multiple of `min_proposal_period`. - `endEpoch` must: - - be at least `min_proposal_period` epochs greater than `startEpoch` - - be at most `max_proposal_period` epochs greater than `startEpoch` - - be a multiple of `min_proposal_period` + - be at least `min_proposal_period` epochs greater than `startEpoch` + - be at most `max_proposal_period` epochs greater than `startEpoch` + - be a multiple of `min_proposal_period` - `graceEpoch` must: - be at least `min_grace_epoch` epochs greater than `endEpoch` - `proposalCode` can be empty and must be a valid transaction with size less than `max_proposal_code_size` kibibytes. diff --git a/documentation/specs/src/base-ledger/execution.md b/documentation/specs/src/base-ledger/execution.md index 26cfe65f22..3395ffb858 100644 --- a/documentation/specs/src/base-ledger/execution.md +++ b/documentation/specs/src/base-ledger/execution.md @@ -21,7 +21,7 @@ Supported validity predicates for Namada: - Proof-of-stake (see [spec](../economics/proof-of-stake.md)) - IBC & IbcToken (see [spec](../interoperability/ibc.md)) - Governance (see [spec](./governance.md)) - - Treasury (see [spec](./governance.md#TreasuryAddress)) + - SlashFund (see [spec](./governance.md#SlashFundAddress)) - Protocol parameters - WASM - Fungible token (see [spec](./fungible-token.md)) diff --git a/documentation/specs/src/base-ledger/governance.md b/documentation/specs/src/base-ledger/governance.md index 45b0aa0cb1..591558fcc0 100644 --- a/documentation/specs/src/base-ledger/governance.md +++ b/documentation/specs/src/base-ledger/governance.md @@ -7,7 +7,7 @@ Namada introduces a governance mechanism to propose and apply protocol changes w ### Governance Address Governance adds 2 internal addresses: - `GovernanceAddress` -- `TreasuryAddress` +- `SlashFundAddress` The first internal address contains all the proposals under its address space. The second internal address holds the funds of rejected proposals. @@ -72,7 +72,7 @@ The governance machinery also relies on a subkey stored under the `NAM` token ad ``` This is to leverage the `NAM` VP to check that the funds were correctly locked. -The governance subkey, `/\$GovernanceAddress/proposal/\$id/funds` will be used after the tally step to know the exact amount of tokens to refund or move to Treasury. +The governance subkey, `/\$GovernanceAddress/proposal/\$id/funds` will be used after the tally step to know the exact amount of tokens to refund or move to SlashFund. ### GovernanceAddress VP Just like Pos, also governance has his own storage space. The `GovernanceAddress` validity predicate task is to check the integrity and correctness of new proposals. A proposal, to be correct, must satisfy the following: @@ -167,7 +167,7 @@ All the computation above must be made at the epoch specified in the `start_epoc It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/master/shared/src/ledger/governance/utils.rs#L68). ### Refund and Proposal Execution mechanism -Together with tallying, in the first block at the beginning of each epoch, in the `FinalizeBlock` event, the protocol will manage the execution of accepted proposals and refunding. For each ended proposal with a positive outcome, it will refund the locked funds from `GovernanceAddress` to the proposal author address (specified in the proposal `author` field). For each proposal that has been rejected, instead, the locked funds will be moved to the `TreasuryAddress`. Moreover, if the proposal had a positive outcome and `proposal_code` is defined, these changes will be executed right away. +Together with tallying, in the first block at the beginning of each epoch, in the `FinalizeBlock` event, the protocol will manage the execution of accepted proposals and refunding. For each ended proposal with a positive outcome, it will refund the locked funds from `GovernanceAddress` to the proposal author address (specified in the proposal `author` field). For each proposal that has been rejected, instead, the locked funds will be moved to the `SlashFundAddress`. Moreover, if the proposal had a positive outcome and `proposal_code` is defined, these changes will be executed right away. To summarize the execution of governance in the `FinalizeBlock` event: If the proposal outcome is positive and current epoch is equal to the proposal `grace_epoch`, in the `FinalizeBlock` event: @@ -175,33 +175,28 @@ If the proposal outcome is positive and current epoch is equal to the proposal ` - execute any changes specified by `proposal_code` In case the proposal was rejected or if any error, in the `FinalizeBlock` event: -- transfer the locked funds to `TreasuryAddress` +- transfer the locked funds to `SlashFundAddress` The result is then signaled by creating and inserting a [`Tendermint Event`](https://github.com/tendermint/tendermint/blob/ab0835463f1f89dcadf83f9492e98d85583b0e71/docs/spec/abci/abci.md#events. -## TreasuryAddress -Funds locked in `TreasuryAddress` address should be spendable only by proposals. +## SlashFundAddress +Funds locked in `SlashFundAddress` address should be spendable only by proposals. -### TreasuryAddress storage +### SlashFundAddress storage ``` -/\$TreasuryAddress/max_transferable_fund: u64 -/\$TreasuryAddress/?: Vec +/\$SlashFundAddress/?: Vec ``` The funds will be stored under: ``` -/\$NAMAddress/balance/\$TreasuryAddress: u64 +/\$NAMAddress/balance/\$SlashFundAddress: u64 ``` -### TreasuryAddress VP -The treasury validity predicate will approve a transfer only if: -- the transfer has been made by the protocol (by checking the existence of `/$GovernanceAddress/pending/$proposal_id` storage key) -- the transfered amount is <= `MAX_SPENDABLE_SUM` +### SlashFundAddress VP +The slash_fund validity predicate will approve a transfer only if the transfer has been made by the protocol (by checking the existence of `/$GovernanceAddress/pending/$proposal_id` storage key) -`MAX_SPENDABLE_SUM` is a parameter of the treasury native vp. - -It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/master/shared/src/ledger/treasury/mod.rs#L55). +It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/main/shared/src/ledger/slash_fund/mod.rs#L70). ## Off-chain protocol From e7c868f0d4a5534251adfb44ae4b970829030b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Sep 2022 18:02:42 +0200 Subject: [PATCH 113/197] ledger/storage/lazy: update lazy_set for updated trait LazyCollection --- .../storage_api/collections/lazy_set.rs | 103 +++++++++++++++++- .../src/ledger/storage_api/collections/mod.rs | 3 +- .../collections/nested_lazy_map.rs | 2 +- 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 9635724543..3a001e6048 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -1,11 +1,14 @@ //! Lazy set. +use std::fmt::Debug; use std::marker::PhantomData; +use thiserror::Error; + use super::super::Result; use super::{LazyCollection, ReadError}; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; -use crate::types::storage::{self, KeySeg}; +use crate::types::storage::{self, DbKeySeg, KeySeg}; /// Subkey corresponding to the data elements of the LazySet pub const DATA_SUBKEY: &str = "data"; @@ -29,7 +32,42 @@ pub struct LazySet { phantom: PhantomData, } -impl LazyCollection for LazySet { +/// Possible sub-keys of a [`LazySet`] +#[derive(Clone, Debug)] +pub enum SubKey { + /// Data sub-key with its literal set value + Data(T), +} + +/// Possible actions that can modify a [`LazySet`]. This +/// roughly corresponds to the methods that have `StorageWrite` access. +#[derive(Clone, Debug)] +pub enum Action { + /// Insert or update a value `T` in a [`LazySet`]. + Insert(T), + /// Remove a value `T` from a [`LazySet`]. + Remove(T), +} + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum ValidationError { + #[error("Invalid storage key {0}")] + InvalidSubKey(storage::Key), +} + +impl LazyCollection for LazySet +where + T: storage::KeySeg + Debug, +{ + type Action = Action; + type SubKey = SubKey; + // In a set, the `SubKey` already contains the data, but we have to + // distinguish `Insert` from `Remove` + type SubKeyWithData = Action; + // There is no "value" for LazySet, `T` is written into the key + type Value = (); + /// Create or use an existing set with the given storage `key`. fn open(key: storage::Key) -> Self { Self { @@ -37,6 +75,67 @@ impl LazyCollection for LazySet { phantom: PhantomData, } } + + fn is_valid_sub_key( + &self, + key: &storage::Key, + ) -> storage_api::Result> { + let suffix = match key.split_prefix(&self.key) { + None => { + // not matching prefix, irrelevant + return Ok(None); + } + Some(None) => { + // no suffix, invalid + return Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(); + } + Some(Some(suffix)) => suffix, + }; + + // Match the suffix against expected sub-keys + match &suffix.segments[..] { + [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { + Ok(Some(SubKey::Data(key_in_kv))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + } + _ => Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(), + } + } + + fn read_sub_key_data( + env: &ENV, + storage_key: &storage::Key, + sub_key: Self::SubKey, + ) -> storage_api::Result> + where + ENV: for<'a> crate::ledger::vp_env::VpEnv<'a>, + { + // There is no "value" for LazySet, `T` is written into the key + let SubKey::Data(sub_key) = sub_key; + let has_pre = env.has_key_pre(storage_key)?; + let has_post = env.has_key_post(storage_key)?; + if has_pre && !has_post { + Ok(Some(Action::Remove(sub_key))) + } else if !has_pre && has_post { + Ok(Some(Action::Insert(sub_key))) + } else { + Ok(None) + } + } + + fn validate_changed_sub_keys( + keys: Vec, + ) -> storage_api::Result> { + Ok(keys) + } } impl LazySet diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index b77b207c7f..3a9236fc71 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -105,8 +105,7 @@ pub trait LazyCollection { /// call `fn build()` on it to get the validation result. /// This function will return `Ok(true)` if the storage key is a valid /// sub-key of this collection, `Ok(false)` if the storage key doesn't match - /// the prefix of this collection, or fail with - /// [`ValidationError::InvalidSubKey`] if the prefix matches this + /// the prefix of this collection, or error if the prefix matches this /// collection, but the key itself is not recognized. fn accumulate( &self, diff --git a/tests/src/storage_api/collections/nested_lazy_map.rs b/tests/src/storage_api/collections/nested_lazy_map.rs index 80b066c18f..037decce46 100644 --- a/tests/src/storage_api/collections/nested_lazy_map.rs +++ b/tests/src/storage_api/collections/nested_lazy_map.rs @@ -639,7 +639,7 @@ mod tests { } Transition::Remove((key_outer, key_middle, key_inner)) => { let middle = - map.entry(*key_outer).or_insert(Default::default()); + map.entry(*key_outer).or_insert_with(Default::default); let inner = middle.entry(*key_middle).or_insert_with(Default::default); let _popped = inner.remove(key_inner); From a5d4a67d41ea17394256c7ea09b5f8c216fd0ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Sep 2022 18:06:09 +0200 Subject: [PATCH 114/197] remove unfinished lazy_set, lazy_hashset and lazy_hashmap for now --- .../ledger/storage_api/collections/hasher.rs | 9 - .../storage_api/collections/lazy_hashmap.rs | 269 ----------------- .../storage_api/collections/lazy_hashset.rs | 184 ------------ .../storage_api/collections/lazy_map.rs | 3 - .../storage_api/collections/lazy_set.rs | 284 ------------------ .../src/ledger/storage_api/collections/mod.rs | 7 - 6 files changed, 756 deletions(-) delete mode 100644 shared/src/ledger/storage_api/collections/hasher.rs delete mode 100644 shared/src/ledger/storage_api/collections/lazy_hashmap.rs delete mode 100644 shared/src/ledger/storage_api/collections/lazy_hashset.rs delete mode 100644 shared/src/ledger/storage_api/collections/lazy_set.rs diff --git a/shared/src/ledger/storage_api/collections/hasher.rs b/shared/src/ledger/storage_api/collections/hasher.rs deleted file mode 100644 index 0f864259f5..0000000000 --- a/shared/src/ledger/storage_api/collections/hasher.rs +++ /dev/null @@ -1,9 +0,0 @@ -use borsh::BorshSerialize; - -/// Hash borsh encoded data into a storage sub-key. -/// This is a sha256 as an uppercase hexadecimal string. -pub fn hash_for_storage_key(data: impl BorshSerialize) -> String { - let bytes = data.try_to_vec().unwrap(); - let hash = crate::types::hash::Hash::sha256(bytes); - hash.to_string() -} diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs deleted file mode 100644 index 9a60fd18f0..0000000000 --- a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs +++ /dev/null @@ -1,269 +0,0 @@ -//! Lazy hash map. - -use std::marker::PhantomData; - -use borsh::{BorshDeserialize, BorshSerialize}; - -use super::super::Result; -use super::hasher::hash_for_storage_key; -use super::LazyCollection; -use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; -use crate::types::storage; - -/// Subkey corresponding to the data elements of the LazyMap -pub const DATA_SUBKEY: &str = "data"; - -/// Lazy hash map. -/// -/// This can be used as an alternative to `std::collections::HashMap` and -/// `BTreeMap`. In the lazy map, the elements do not reside in memory but are -/// instead read and written to storage sub-keys of the storage `key` given to -/// construct the map. -/// -/// In the [`LazyHashMap`], the type of key `K` can be anything that -/// [`BorshSerialize`] and [`BorshDeserialize`] and a hex string of sha256 hash -/// over the borsh encoded keys are used as storage key segments. -/// -/// This is different from [`super::LazyMap`], which uses [`storage::KeySeg`] -/// trait. -/// -/// Additionally, [`LazyHashMap`] also writes the unhashed values into the -/// storage together with the values (using an internal `KeyVal` type). -#[derive(Debug)] -pub struct LazyHashMap { - key: storage::Key, - phantom_k: PhantomData, - phantom_v: PhantomData, -} - -/// Struct to hold a key-value pair -#[derive(Debug, BorshSerialize, BorshDeserialize)] -struct KeyVal { - key: K, - val: V, -} - -impl LazyCollection for LazyHashMap { - /// Create or use an existing map with the given storage `key`. - fn open(key: storage::Key) -> Self { - Self { - key, - phantom_k: PhantomData, - phantom_v: PhantomData, - } - } -} - -impl LazyHashMap -where - K: BorshDeserialize + BorshSerialize + 'static, - V: BorshDeserialize + BorshSerialize + 'static, -{ - /// Inserts a key-value pair into the map. - /// - /// If the map did not have this key present, `None` is returned. - /// If the map did have this key present, the value is updated, and the old - /// value is returned. Unlike in `std::collection::HashMap`, the key is also - /// updated; this matters for types that can be `==` without being - /// identical. - pub fn insert( - &self, - storage: &mut S, - key: K, - val: V, - ) -> Result> - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - let previous = self.get(storage, &key)?; - - let data_key = self.get_data_key(&key); - Self::write_key_val(storage, &data_key, key, val)?; - - Ok(previous) - } - - /// Removes a key from the map, returning the value at the key if the key - /// was previously in the map. - pub fn remove(&self, storage: &mut S, key: &K) -> Result> - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - let value = self.get(storage, key)?; - - let data_key = self.get_data_key(key); - storage.delete(&data_key)?; - - Ok(value) - } - - /// Returns the value corresponding to the key, if any. - pub fn get(&self, storage: &S, key: &K) -> Result> - where - S: for<'iter> StorageRead<'iter>, - { - let res = self.get_key_val(storage, key)?; - Ok(res.map(|(_key, val)| val)) - } - - /// Returns the key-value corresponding to the key, if any. - pub fn get_key_val(&self, storage: &S, key: &K) -> Result> - where - S: for<'iter> StorageRead<'iter>, - { - let data_key = self.get_data_key(key); - Self::read_key_val(storage, &data_key) - } - - /// Returns the key-value corresponding to the given hash of a key, if any. - pub fn get_key_val_by_hash( - &self, - storage: &S, - key_hash: &str, - ) -> Result> - where - S: for<'iter> StorageRead<'iter>, - { - let data_key = - self.get_data_prefix().push(&key_hash.to_string()).unwrap(); - Self::read_key_val(storage, &data_key) - } - - /// Returns whether the set contains a value. - pub fn contains(&self, storage: &S, key: &K) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - storage.has_key(&self.get_data_key(key)) - } - - /// Returns whether the map contains no elements. - pub fn is_empty(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let mut iter = - storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - Ok(iter.next().is_none()) - } - - /// Reads the number of elements in the map. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded maps to avoid gas usage increasing with the length of the - /// set. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let iter = - storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - iter.count().try_into().into_storage_result() - } - - /// An iterator visiting all key-value elements. The iterator element type - /// is `Result<(K, V)>`, because iterator's call to `next` may fail with - /// e.g. out of gas or data decoding error. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded maps to avoid gas usage increasing with the length of the - /// map. - pub fn iter<'iter>( - &self, - storage: &'iter impl StorageRead<'iter>, - ) -> Result> + 'iter> { - let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; - Ok(iter.map(|key_val_res| { - let (_key, val) = key_val_res?; - let KeyVal { key, val } = val; - Ok((key, val)) - })) - } - - /// Reads a key-value from storage - fn read_key_val( - storage: &S, - storage_key: &storage::Key, - ) -> Result> - where - S: for<'iter> StorageRead<'iter>, - { - let res = storage.read(storage_key)?; - Ok(res.map(|KeyVal { key, val }| (key, val))) - } - - /// Write a key-value into storage - fn write_key_val( - storage: &mut impl StorageWrite, - storage_key: &storage::Key, - key: K, - val: V, - ) -> Result<()> { - storage.write(storage_key, KeyVal { key, val }) - } - - /// Get the prefix of set's elements storage - fn get_data_prefix(&self) -> storage::Key { - self.key.push(&DATA_SUBKEY.to_owned()).unwrap() - } - - /// Get the sub-key of a given element - fn get_data_key(&self, key: &K) -> storage::Key { - let hash_str = hash_for_storage_key(key); - self.get_data_prefix().push(&hash_str).unwrap() - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::ledger::storage::testing::TestStorage; - - #[test] - fn test_lazy_hash_map_basics() -> storage_api::Result<()> { - let mut storage = TestStorage::default(); - - let key = storage::Key::parse("test").unwrap(); - let lazy_map = LazyHashMap::::open(key); - - // The map should be empty at first - assert!(lazy_map.is_empty(&storage)?); - assert!(lazy_map.len(&storage)? == 0); - assert!(!lazy_map.contains(&storage, &0)?); - assert!(!lazy_map.contains(&storage, &1)?); - assert!(lazy_map.iter(&storage)?.next().is_none()); - assert!(lazy_map.get(&storage, &0)?.is_none()); - assert!(lazy_map.get(&storage, &1)?.is_none()); - assert!(lazy_map.remove(&mut storage, &0)?.is_none()); - assert!(lazy_map.remove(&mut storage, &1)?.is_none()); - - // Insert a new value and check that it's added - let (key, val) = (123, "Test".to_string()); - lazy_map.insert(&mut storage, key, val.clone())?; - assert!(!lazy_map.contains(&storage, &0)?); - assert!(lazy_map.contains(&storage, &key)?); - assert!(!lazy_map.is_empty(&storage)?); - assert!(lazy_map.len(&storage)? == 1); - assert_eq!( - lazy_map.iter(&storage)?.next().unwrap()?, - (key, val.clone()) - ); - assert!(lazy_map.get(&storage, &0)?.is_none()); - assert_eq!(lazy_map.get(&storage, &key)?.unwrap(), val); - - // Remove the last value and check that the map is empty again - let removed = lazy_map.remove(&mut storage, &key)?.unwrap(); - assert_eq!(removed, val); - assert!(lazy_map.is_empty(&storage)?); - assert!(lazy_map.len(&storage)? == 0); - assert!(!lazy_map.contains(&storage, &0)?); - assert!(!lazy_map.contains(&storage, &1)?); - assert!(lazy_map.get(&storage, &0)?.is_none()); - assert!(lazy_map.get(&storage, &key)?.is_none()); - assert!(lazy_map.iter(&storage)?.next().is_none()); - assert!(lazy_map.remove(&mut storage, &key)?.is_none()); - - Ok(()) - } -} diff --git a/shared/src/ledger/storage_api/collections/lazy_hashset.rs b/shared/src/ledger/storage_api/collections/lazy_hashset.rs deleted file mode 100644 index 63ac5c845c..0000000000 --- a/shared/src/ledger/storage_api/collections/lazy_hashset.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! Lazy hash set. - -use std::marker::PhantomData; - -use borsh::{BorshDeserialize, BorshSerialize}; - -use super::super::Result; -use super::hasher::hash_for_storage_key; -use super::LazyCollection; -use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; -use crate::types::storage; - -/// Subkey corresponding to the data elements of the LazySet -pub const DATA_SUBKEY: &str = "data"; - -/// Lazy hash set. -/// -/// This can be used as an alternative to `std::collections::HashSet` and -/// `BTreeSet`. In the lazy set, the elements do not reside in memory but are -/// instead read and written to storage sub-keys of the storage `key` given to -/// construct the set. -/// -/// In the [`LazyHashSet`], the type of value `T` can be anything that -/// [`BorshSerialize`] and [`BorshDeserialize`] and a hex string of sha256 hash -/// over the borsh encoded values are used as storage key segments. -/// -/// This is different from [`super::LazySet`], which uses [`storage::KeySeg`] -/// trait. -/// -/// Additionally, [`LazyHashSet`] also writes the unhashed values into the -/// storage. -#[derive(Debug)] -pub struct LazyHashSet { - key: storage::Key, - phantom: PhantomData, -} - -impl LazyCollection for LazyHashSet { - /// Create or use an existing set with the given storage `key`. - fn open(key: storage::Key) -> Self { - Self { - key, - phantom: PhantomData, - } - } -} - -impl LazyHashSet -where - T: BorshSerialize + BorshDeserialize + 'static, -{ - /// Adds a value to the set. If the set did not have this value present, - /// `Ok(true)` is returned, `Ok(false)` otherwise. - pub fn insert(&self, storage: &mut S, val: T) -> Result - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - if self.contains(storage, &val)? { - Ok(false) - } else { - let data_key = self.get_data_key(&val); - storage.write(&data_key, &val)?; - Ok(true) - } - } - - /// Removes a value from the set. Returns whether the value was present in - /// the set. - pub fn remove(&self, storage: &mut S, val: &T) -> Result - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - let data_key = self.get_data_key(val); - let value: Option = storage.read(&data_key)?; - storage.delete(&data_key)?; - Ok(value.is_some()) - } - - /// Returns whether the set contains a value. - pub fn contains(&self, storage: &S, val: &T) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let value: Option = storage.read(&self.get_data_key(val))?; - Ok(value.is_some()) - } - - /// Reads the number of elements in the set. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the - /// set. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let iter = - storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - iter.count().try_into().into_storage_result() - } - - /// Returns whether the set contains no elements. - pub fn is_empty(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let mut iter = storage.iter_prefix(&self.get_data_prefix())?; - Ok(storage.iter_next(&mut iter)?.is_none()) - } - - /// An iterator visiting all elements. The iterator element type is - /// `Result`, because iterator's call to `next` may fail with e.g. out of - /// gas or data decoding error. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the - /// set. - pub fn iter<'iter>( - &self, - storage: &'iter impl StorageRead<'iter>, - ) -> Result> + 'iter> { - let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; - Ok(iter.map(|key_val_res| { - let (_key, val) = key_val_res?; - Ok(val) - })) - } - - /// Get the prefix of set's elements storage - fn get_data_prefix(&self) -> storage::Key { - self.key.push(&DATA_SUBKEY.to_owned()).unwrap() - } - - /// Get the sub-key of a given element - fn get_data_key(&self, val: &T) -> storage::Key { - let hash_str = hash_for_storage_key(val); - self.get_data_prefix().push(&hash_str).unwrap() - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::ledger::storage::testing::TestStorage; - - #[test] - fn test_lazy_set_basics() -> storage_api::Result<()> { - let mut storage = TestStorage::default(); - - let key = storage::Key::parse("test").unwrap(); - let lazy_set = LazyHashSet::::open(key); - - // The set should be empty at first - assert!(lazy_set.is_empty(&storage)?); - assert!(lazy_set.len(&storage)? == 0); - assert!(!lazy_set.contains(&storage, &0)?); - assert!(lazy_set.is_empty(&storage)?); - assert!(lazy_set.iter(&storage)?.next().is_none()); - assert!(!lazy_set.remove(&mut storage, &0)?); - assert!(!lazy_set.remove(&mut storage, &1)?); - - // Insert a new value and check that it's added - let val = 1337; - lazy_set.insert(&mut storage, val)?; - assert!(!lazy_set.is_empty(&storage)?); - assert!(lazy_set.len(&storage)? == 1); - assert_eq!(lazy_set.iter(&storage)?.next().unwrap()?, val.clone()); - assert!(!lazy_set.contains(&storage, &0)?); - assert!(lazy_set.contains(&storage, &val)?); - - // Remove the last value and check that the set is empty again - let is_removed = lazy_set.remove(&mut storage, &val)?; - assert!(is_removed); - assert!(lazy_set.is_empty(&storage)?); - assert!(lazy_set.len(&storage)? == 0); - assert!(!lazy_set.contains(&storage, &0)?); - assert!(lazy_set.is_empty(&storage)?); - assert!(!lazy_set.remove(&mut storage, &0)?); - assert!(!lazy_set.remove(&mut storage, &1)?); - - Ok(()) - } -} diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 6e32b5399b..34a0f7d891 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -28,9 +28,6 @@ pub const DATA_SUBKEY: &str = "data"; /// In the [`LazyMap`], the type of key `K` can be anything that implements /// [`storage::KeySeg`] and this trait is used to turn the keys into key /// segments. -/// -/// This is different from [`super::LazyHashMap`], which hashes borsh encoded -/// key. #[derive(Debug)] pub struct LazyMap { key: storage::Key, diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs deleted file mode 100644 index 3a001e6048..0000000000 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ /dev/null @@ -1,284 +0,0 @@ -//! Lazy set. - -use std::fmt::Debug; -use std::marker::PhantomData; - -use thiserror::Error; - -use super::super::Result; -use super::{LazyCollection, ReadError}; -use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; -use crate::types::storage::{self, DbKeySeg, KeySeg}; - -/// Subkey corresponding to the data elements of the LazySet -pub const DATA_SUBKEY: &str = "data"; - -/// Lazy set. -/// -/// This can be used as an alternative to `std::collections::HashSet` and -/// `BTreeSet`. In the lazy set, the elements do not reside in memory but are -/// instead read and written to storage sub-keys of the storage `key` used to -/// construct the set. -/// -/// In the [`LazySet`], the type of value `T` can be anything that implements -/// [`storage::KeySeg`] and this trait is used to turn the values into key -/// segments. -/// -/// This is different from [`super::LazyHashSet`], which hashes borsh encoded -/// values. -#[derive(Debug)] -pub struct LazySet { - key: storage::Key, - phantom: PhantomData, -} - -/// Possible sub-keys of a [`LazySet`] -#[derive(Clone, Debug)] -pub enum SubKey { - /// Data sub-key with its literal set value - Data(T), -} - -/// Possible actions that can modify a [`LazySet`]. This -/// roughly corresponds to the methods that have `StorageWrite` access. -#[derive(Clone, Debug)] -pub enum Action { - /// Insert or update a value `T` in a [`LazySet`]. - Insert(T), - /// Remove a value `T` from a [`LazySet`]. - Remove(T), -} - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum ValidationError { - #[error("Invalid storage key {0}")] - InvalidSubKey(storage::Key), -} - -impl LazyCollection for LazySet -where - T: storage::KeySeg + Debug, -{ - type Action = Action; - type SubKey = SubKey; - // In a set, the `SubKey` already contains the data, but we have to - // distinguish `Insert` from `Remove` - type SubKeyWithData = Action; - // There is no "value" for LazySet, `T` is written into the key - type Value = (); - - /// Create or use an existing set with the given storage `key`. - fn open(key: storage::Key) -> Self { - Self { - key, - phantom: PhantomData, - } - } - - fn is_valid_sub_key( - &self, - key: &storage::Key, - ) -> storage_api::Result> { - let suffix = match key.split_prefix(&self.key) { - None => { - // not matching prefix, irrelevant - return Ok(None); - } - Some(None) => { - // no suffix, invalid - return Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result(); - } - Some(Some(suffix)) => suffix, - }; - - // Match the suffix against expected sub-keys - match &suffix.segments[..] { - [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] - if sub_a == DATA_SUBKEY => - { - if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { - Ok(Some(SubKey::Data(key_in_kv))) - } else { - Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result() - } - } - _ => Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result(), - } - } - - fn read_sub_key_data( - env: &ENV, - storage_key: &storage::Key, - sub_key: Self::SubKey, - ) -> storage_api::Result> - where - ENV: for<'a> crate::ledger::vp_env::VpEnv<'a>, - { - // There is no "value" for LazySet, `T` is written into the key - let SubKey::Data(sub_key) = sub_key; - let has_pre = env.has_key_pre(storage_key)?; - let has_post = env.has_key_post(storage_key)?; - if has_pre && !has_post { - Ok(Some(Action::Remove(sub_key))) - } else if !has_pre && has_post { - Ok(Some(Action::Insert(sub_key))) - } else { - Ok(None) - } - } - - fn validate_changed_sub_keys( - keys: Vec, - ) -> storage_api::Result> { - Ok(keys) - } -} - -impl LazySet -where - T: storage::KeySeg, -{ - /// Adds a value to the set. If the set did not have this value present, - /// `Ok(true)` is returned, `Ok(false)` otherwise. - pub fn insert(&self, storage: &mut S, val: T) -> Result - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - if self.contains(storage, &val)? { - Ok(false) - } else { - let data_key = self.get_data_key(&val); - // The actual value is written into the key, so the value written to - // the storage is empty (unit) - storage.write(&data_key, ())?; - Ok(true) - } - } - - /// Removes a value from the set. Returns whether the value was present in - /// the set. - pub fn remove(&self, storage: &mut S, val: &T) -> Result - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - let data_key = self.get_data_key(val); - let value: Option<()> = storage.read(&data_key)?; - storage.delete(&data_key)?; - Ok(value.is_some()) - } - - /// Returns whether the set contains a value. - pub fn contains(&self, storage: &S, val: &T) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - storage.has_key(&self.get_data_key(val)) - } - - /// Returns whether the set contains no elements. - pub fn is_empty(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let mut iter = - storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - Ok(iter.next().is_none()) - } - - /// Reads the number of elements in the set. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the - /// set. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let iter = - storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - iter.count().try_into().into_storage_result() - } - - /// An iterator visiting all elements. The iterator element type is - /// `Result`, because iterator's call to `next` may fail with e.g. out of - /// gas or data decoding error. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the - /// set. - pub fn iter<'iter>( - &self, - storage: &'iter impl StorageRead<'iter>, - ) -> Result> + 'iter> { - let iter = - storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - Ok(iter.map(|key_val_res| { - let (key, _val) = key_val_res?; - let last_key_seg = key - .last() - .ok_or(ReadError::UnexpectedlyEmptyStorageKey) - .into_storage_result()?; - T::parse(last_key_seg.raw()).into_storage_result() - })) - } - - /// Get the prefix of set's elements storage - fn get_data_prefix(&self) -> storage::Key { - self.key.push(&DATA_SUBKEY.to_owned()).unwrap() - } - - /// Get the sub-key of a given element - fn get_data_key(&self, val: &T) -> storage::Key { - let key_str = val.to_db_key(); - self.get_data_prefix().push(&key_str).unwrap() - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::ledger::storage::testing::TestStorage; - - #[test] - fn test_lazy_set_basics() -> storage_api::Result<()> { - let mut storage = TestStorage::default(); - - let key = storage::Key::parse("test").unwrap(); - let lazy_set = LazySet::::open(key); - - // The set should be empty at first - assert!(lazy_set.is_empty(&storage)?); - assert!(lazy_set.len(&storage)? == 0); - assert!(!lazy_set.contains(&storage, &0)?); - assert!(lazy_set.is_empty(&storage)?); - assert!(lazy_set.iter(&storage)?.next().is_none()); - assert!(!lazy_set.remove(&mut storage, &0)?); - assert!(!lazy_set.remove(&mut storage, &1)?); - - // Insert a new value and check that it's added - let val = 1337; - lazy_set.insert(&mut storage, val)?; - assert!(!lazy_set.is_empty(&storage)?); - assert!(lazy_set.len(&storage)? == 1); - assert_eq!(lazy_set.iter(&storage)?.next().unwrap()?, val.clone()); - assert!(!lazy_set.contains(&storage, &0)?); - assert!(lazy_set.contains(&storage, &val)?); - - // Remove the last value and check that the set is empty again - let is_removed = lazy_set.remove(&mut storage, &val)?; - assert!(is_removed); - assert!(lazy_set.is_empty(&storage)?); - assert!(lazy_set.len(&storage)? == 0); - assert!(!lazy_set.contains(&storage, &0)?); - assert!(lazy_set.is_empty(&storage)?); - assert!(!lazy_set.remove(&mut storage, &0)?); - assert!(!lazy_set.remove(&mut storage, &1)?); - - Ok(()) - } -} diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index 3a9236fc71..688b76bd49 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -13,17 +13,10 @@ use borsh::BorshDeserialize; use derivative::Derivative; use thiserror::Error; -mod hasher; -// pub mod lazy_hashmap; -// pub mod lazy_hashset; pub mod lazy_map; -// pub mod lazy_set; pub mod lazy_vec; -// pub use lazy_hashmap::LazyHashMap; -// pub use lazy_hashset::LazyHashSet; pub use lazy_map::LazyMap; -// pub use lazy_set::LazySet; pub use lazy_vec::LazyVec; use crate::ledger::storage_api; From 5cedd450be759ee2e766f2374332c204a7955608 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Sep 2022 16:53:24 +0000 Subject: [PATCH 115/197] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index d8c5d1223f..2247dd1683 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.76d289c2ba2a1d35d24cbe160e878512ea982cee51f6f3e592f96384683714f1.wasm", - "tx_from_intent.wasm": "tx_from_intent.f8d8ecc1fd565a692b9ca208eebfdab8b4f1a097d58a8604f1c1f92dea795ed4.wasm", - "tx_ibc.wasm": "tx_ibc.417bea698b37f8bf4c55d99df79ea51c10c5b110c25729ac6def4f9bc65c4842.wasm", - "tx_init_account.wasm": "tx_init_account.16404b828089ee1c5a3df94d8ba1d1569974b7d10c00f839ed0894d2ee0ad802.wasm", - "tx_init_nft.wasm": "tx_init_nft.409379e7d008d770dadc3a7c002b25a92e32a34eb195ef01eb0b76ae96db84bf.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f5b7da549aba6432c2686057f66f550e0881cd114f6192ca0f11fef886bad2cd.wasm", - "tx_init_validator.wasm": "tx_init_validator.7052307657586039fe19cc68f91fb7a3ecfaf34e6a5612ca928f2dfbeb1c2feb.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.4ec445c5108994f2f357c68af5468157f57239a27f04b51c04b5952c09a32f06.wasm", - "tx_transfer.wasm": "tx_transfer.f40c4eac4ed8189e73aa1b19a74b7ec1f18ace52b548b83a28766203d87bf16e.wasm", - "tx_unbond.wasm": "tx_unbond.cb56274fac522963f8c0f70c58a722151d9628ba9e4a7977664b2d6c297ca2e8.wasm", - "tx_update_vp.wasm": "tx_update_vp.685c9eddfee14aeeaf8fadf5cc100be638d0b15488ec20f822ae4b07256bce5c.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.67520bf791c1598f687991622c20f7c0f3142c07382037e225c6d4cdf160165e.wasm", - "tx_withdraw.wasm": "tx_withdraw.1728a2d0fe66a0242816ad0fd8005e09c27a585cb9c9a6f6a1c1c63738aeae8f.wasm", - "vp_nft.wasm": "vp_nft.1d4d3898ae605927af793d50beef268641e1ff8f0e3d91a0b463c5da550fa8aa.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.9440526f730a029d82bd5c5f8eb00fa8f6f03a70aca121d045a1b5f13ad5cf9d.wasm", - "vp_token.wasm": "vp_token.1b5e24692cea7998d3ec8b06ee0c299934c19aeec7543737d94bc803853a477d.wasm", - "vp_user.wasm": "vp_user.09225e083d4f28f93350ce7ff0e67dbae47ea096780a388d0338a0945c3b7222.wasm" + "tx_bond.wasm": "tx_bond.7bfc18f1969d0ba119e7ca2fe00e7ca156ff2b60277a380e97b4cdefeb20ea51.wasm", + "tx_from_intent.wasm": "tx_from_intent.229e4c974480899212c1c30374c1344aa95c0366109ff56b316fcfcc0120c732.wasm", + "tx_ibc.wasm": "tx_ibc.7341c8abc400599cbe4787f7f9fbd84f2ca18959f70d61c2a389f3a4a3ef34d3.wasm", + "tx_init_account.wasm": "tx_init_account.ced4788ea1837d0cacd6ba691f09181555c846f420cb46d32e76cccae9cad0e5.wasm", + "tx_init_nft.wasm": "tx_init_nft.411a1fb5c2f5ef8a9484443fa3f2affddb6b11779c961abc274288e3cd4aba28.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.c3e13023ae8444a10c0fcc50fa27344d6c35367966e41cd1f95a609add0aa26a.wasm", + "tx_init_validator.wasm": "tx_init_validator.707d0798265ba501a813321b5d9a1222bda8448650460e78518e2796f0b42c30.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.0910d261e037e3c0633a3898fc00a08a953d87c8a4b9db2b4041877b91f8317e.wasm", + "tx_transfer.wasm": "tx_transfer.486ffcee9265df25b01751d6007b7f07a083288b985396a8b6fd2aeaacd3e7a8.wasm", + "tx_unbond.wasm": "tx_unbond.dbc10595136c99949a86567561857bb7c465a7a1ea6e21a2b9d261510867ec63.wasm", + "tx_update_vp.wasm": "tx_update_vp.a04692ad8b2715c6262b4e3342591ab7bbb3e6577458979c33d196e8d80146fc.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.25dbae81da4255cae3ffeab6857646af46ef76d70984acfc7c89a3aeb8249679.wasm", + "tx_withdraw.wasm": "tx_withdraw.599ecc125b197b22b27127ce61bc17138a4dd05eb1598a64862496f301c0bc28.wasm", + "vp_nft.wasm": "vp_nft.a7f25944fba3d9a2b00e98482535ed4591282bbf794d64cad18d3c7d15a6318c.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.e28867d79578ce4a32d027b6e50580e8e5e0a19b44c60dc10cacb41dfe07e28c.wasm", + "vp_token.wasm": "vp_token.d24443f5683d0d7d0259dab878f811a56c3d19f3158aecfaae6ce7627cb40884.wasm", + "vp_user.wasm": "vp_user.1aa3756e386a883f523534ac76fb4b75e01c06fcde647c2b6fcca807ba683497.wasm" } \ No newline at end of file From 707db38db5f21674cad24766c4df40a011dc1fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Sep 2022 18:53:49 +0200 Subject: [PATCH 116/197] changelog: #503 --- .changelog/unreleased/features/503-lazy-vec-and-map.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/503-lazy-vec-and-map.md diff --git a/.changelog/unreleased/features/503-lazy-vec-and-map.md b/.changelog/unreleased/features/503-lazy-vec-and-map.md new file mode 100644 index 0000000000..d29ee5fd9c --- /dev/null +++ b/.changelog/unreleased/features/503-lazy-vec-and-map.md @@ -0,0 +1,2 @@ +- Added lazy vector and map data structures for ledger storage + ([#503](https://github.com/anoma/namada/pull/503)) \ No newline at end of file From 467fcc87d4ea1969d0bb668e985e3fdd4f52916f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 23 Sep 2022 10:11:31 +0000 Subject: [PATCH 117/197] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 3d1538fc9f..b9af125eaf 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.69b997687f93801f7822ad39032ab8fbc29ca989317e94c07c7bf3bc4a2a2ad3.wasm", - "tx_from_intent.wasm": "tx_from_intent.1f8b246e7d0c4e87188018215eb2860082ef99ace78a12c0481a39523a0b7851.wasm", - "tx_ibc.wasm": "tx_ibc.90505336c947d962bb02f4643dbd294263dd3bb88601d766f3cf0f8d17ec92fb.wasm", - "tx_init_account.wasm": "tx_init_account.a1bdaca982e027f068cbd2c17ed9de64396ecc5ba3a14ac7578fded20a829005.wasm", - "tx_init_nft.wasm": "tx_init_nft.18ad3e76dfe4c3d3bdba1069f668bd0d349aa8f99cfa2c7a51e5b6bc782e3c25.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.794302c125d17068b40d25a67f943ff520f0f83bde3233ce24bfc227c61407dc.wasm", - "tx_init_validator.wasm": "tx_init_validator.276634f0ff3c6c123a110459876e8c37be25cbb2d0f28b320a91ae7592aab86f.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.ac9a7edb87c605457a39521c7a4ab0786077043e41de7975a996e872f6519f5d.wasm", - "tx_transfer.wasm": "tx_transfer.e769c543c2f9c0bf76fbe73af8cca79b3d6b92c80e957daa278873f85d5ef1b9.wasm", - "tx_unbond.wasm": "tx_unbond.8a4aa3106a1f594da45fce6be76597735eed366a4aa5c462474dd6536d845761.wasm", - "tx_update_vp.wasm": "tx_update_vp.f75fc727bc0f9e13ce78e8cdcd5ca978f4becf1e7417eada81a42bdc57ad3f03.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.1c50c26be4ecf0f8b8893a1436768a77eff1050dbd51ab1c22b4cf458783f6ce.wasm", - "tx_withdraw.wasm": "tx_withdraw.59f41e331871b6f763e511d120f15da384a9c8d1f7e56845bb67d7a01fc6a863.wasm", - "vp_nft.wasm": "vp_nft.ccf80799aa2f9839e1af7b8be1e99d31d793c5ad100fab89af22f34aa6fd9e4b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.181e1882bb09c5c2be54fec6454c3490a35c305706c11bec95ca739652555c26.wasm", - "vp_token.wasm": "vp_token.72c51248c57b47039d43550dca719b969ee83013fb9c440470e704316a6075b5.wasm", - "vp_user.wasm": "vp_user.6a05a513c383ac3fe39a993fea923d8bd2caa9e259f0ebbb95dc56a21ce813df.wasm" + "tx_bond.wasm": "tx_bond.72b81bb51f67b574cf7da27204e1b22a9221c575d6cb25346521c01e763caaf9.wasm", + "tx_from_intent.wasm": "tx_from_intent.01872d339c58bd25149874eb7498c30e9d654c904cb3ff8d1708cb848d9b1c2d.wasm", + "tx_ibc.wasm": "tx_ibc.abc0775b0afa0966503ade2b11e510219c34e55e65ffc0ddaee5111c47d69fd6.wasm", + "tx_init_account.wasm": "tx_init_account.f6a9ee5a298c4ba7ccefeea083c5df42af5c924927245ce3d4f1b8c07665fa63.wasm", + "tx_init_nft.wasm": "tx_init_nft.5d4524436b5f6fcb29912d486dca279c3a8677a47ab7af9f535e4a2955f15469.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.b295c83a4bffb87b8c5bc987021c804caa839c3b224cc5bc5225505257ab9fad.wasm", + "tx_init_validator.wasm": "tx_init_validator.de89fa2521bfb06a064577ef0c31a5316d991a6b1413115958083c3328cf4abd.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.65835905408cb8b57ab679aef7816bff51fe38383313a855207081ed9d635e56.wasm", + "tx_transfer.wasm": "tx_transfer.399f825e9d40075fd981dd1be9b48332933223ac5dcc47c1375aa337da881fcb.wasm", + "tx_unbond.wasm": "tx_unbond.3854b888082ac44a971900a4c761aa4917a03fbaa0cdf403028222e97879614f.wasm", + "tx_update_vp.wasm": "tx_update_vp.f462d73148a9ed2e48ffd33d77713981e69d56b391a055b75d367915b2e768d2.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.6486326c8772c7a20d5e8640235fb54abaa5dc2b1c624e75d5a1f49e2df9cdab.wasm", + "tx_withdraw.wasm": "tx_withdraw.a04fd246f0871995ebc7291e376cff83bc33228403607e21ab490c3063e905b1.wasm", + "vp_nft.wasm": "vp_nft.f82082aa459fbb211271b03561f0d0fe9222320366dabab75bfedd84e596b477.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.7cd43d8cd003fea8614d35cdc3916e8ec05ff974d25b3e6d0dadf4fa74ed3ca6.wasm", + "vp_token.wasm": "vp_token.188e37e0deff4f3e2c5acb2037e43d0dd77080ecadd0353cb28f3e2f787bb0fc.wasm", + "vp_user.wasm": "vp_user.be9fab0f727449620989abe3219188138730cedca36076b6cc40a60f34e19af0.wasm" } \ No newline at end of file From 88513f697956b240456fd7b27a2d73f3e286a5fd Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 23 Sep 2022 12:52:58 +0200 Subject: [PATCH 118/197] Refactors governance e2e tests --- tests/src/e2e/ledger_tests.rs | 73 ++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 777090d4d4..68b8b85cc4 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -997,7 +997,10 @@ fn proposal_submission() -> Result<()> { min_num_of_blocks: 1, min_duration: 1, max_expected_time_per_block: 1, - ..genesis.parameters + vp_whitelist: genesis.parameters.vp_whitelist, + // Enable tx whitelist to test the execution of a + // non-whitelisted tx by governance + tx_whitelist: Some(vec![String::from("test")]), }; GenesisConfig { @@ -1046,7 +1049,6 @@ fn proposal_submission() -> Result<()> { client.assert_success(); // 2. Submit valid proposal - let test_dir = tempfile::tempdir_in(test.test_dir.path()).unwrap(); let proposal_code = wasm_abs_path(TX_PROPOSAL_CODE); let albert = find_address(&test, ALBERT)?; @@ -1070,18 +1072,19 @@ fn proposal_submission() -> Result<()> { "proposal_code_path": proposal_code.to_str().unwrap() } ); - let proposal_file = - tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); - serde_json::to_writer(&proposal_file, &valid_proposal_json).unwrap(); - let proposal_path = proposal_file.path(); - let proposal_ref = proposal_path.to_string_lossy(); + let valid_proposal_json_path = + test.test_dir.path().join("valid_proposal.json"); + generate_proposal_json_file( + valid_proposal_json_path.as_path(), + &valid_proposal_json, + ); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let submit_proposal_args = vec![ "init-proposal", "--data-path", - &proposal_ref, + valid_proposal_json_path.to_str().unwrap(), "--ledger-address", &validator_one_rpc, ]; @@ -1169,17 +1172,17 @@ fn proposal_submission() -> Result<()> { "grace_epoch": 10009_u64, } ); - let invalid_proposal_file = - tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); - serde_json::to_writer(&invalid_proposal_file, &invalid_proposal_json) - .unwrap(); - let invalid_proposal_path = invalid_proposal_file.path(); - let invalid_proposal_ref = invalid_proposal_path.to_string_lossy(); + let invalid_proposal_json_path = + test.test_dir.path().join("invalid_proposal.json"); + generate_proposal_json_file( + invalid_proposal_json_path.as_path(), + &invalid_proposal_json, + ); let submit_proposal_args = vec![ "init-proposal", "--data-path", - &invalid_proposal_ref, + invalid_proposal_json_path.to_str().unwrap(), "--ledger-address", &validator_one_rpc, ]; @@ -1395,8 +1398,6 @@ fn proposal_offline() -> Result<()> { client.assert_success(); // 2. Create an offline - let test_dir = tempfile::tempdir_in(test.test_dir.path()).unwrap(); - let albert = find_address(&test, ALBERT)?; let valid_proposal_json = json!( { @@ -1417,18 +1418,19 @@ fn proposal_offline() -> Result<()> { "grace_epoch": 18_u64 } ); - let proposal_file = - tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); - serde_json::to_writer(&proposal_file, &valid_proposal_json).unwrap(); - let proposal_path = proposal_file.path(); - let proposal_ref = proposal_path.to_string_lossy(); + let valid_proposal_json_path = + test.test_dir.path().join("valid_proposal.json"); + generate_proposal_json_file( + valid_proposal_json_path.as_path(), + &valid_proposal_json, + ); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let offline_proposal_args = vec![ "init-proposal", "--data-path", - &proposal_ref, + valid_proposal_json_path.to_str().unwrap(), "--offline", "--ledger-address", &validator_one_rpc, @@ -1445,12 +1447,10 @@ fn proposal_offline() -> Result<()> { epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } - let proposal_path = test_dir.path().join("proposal"); - let proposal_ref = proposal_path.to_string_lossy(); let submit_proposal_vote = vec![ "vote-proposal", "--data-path", - &proposal_ref, + test.test_dir.path().to_str().unwrap(), "--vote", "yay", "--signer", @@ -1465,17 +1465,14 @@ fn proposal_offline() -> Result<()> { client.assert_success(); let expected_file_name = format!("proposal-vote-{}", albert); - let expected_path_vote = test_dir.path().join(&expected_file_name); + let expected_path_vote = test.test_dir.path().join(&expected_file_name); assert!(expected_path_vote.exists()); - let expected_path_proposal = test_dir.path().join("proposal"); - assert!(expected_path_proposal.exists()); - // 4. Compute offline tally let tally_offline = vec![ "query-proposal-result", "--data-path", - test_dir.path().to_str().unwrap(), + test.test_dir.path().to_str().unwrap(), "--offline", "--ledger-address", &validator_one_rpc, @@ -1488,6 +1485,20 @@ fn proposal_offline() -> Result<()> { Ok(()) } +fn generate_proposal_json_file( + proposal_path: &std::path::Path, + proposal_content: &serde_json::Value, +) { + let intent_writer = std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(proposal_path) + .unwrap(); + + serde_json::to_writer(intent_writer, proposal_content).unwrap(); +} + /// In this test we: /// 1. Setup 2 genesis validators /// 2. Initialize a new network with the 2 validators From 218f45d199c1c6e2f90ff5b431cd9b80d84e1728 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 23 Sep 2022 14:20:30 +0200 Subject: [PATCH 119/197] Uses `end_epoch` in `query_proposal_result` --- apps/src/lib/client/rpc.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d19ded990c..19ed8aaaa6 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -303,20 +303,17 @@ pub async fn query_proposal_result( match args.proposal_id { Some(id) => { - let start_epoch_key = gov_storage::get_voting_start_epoch_key(id); let end_epoch_key = gov_storage::get_voting_end_epoch_key(id); - let start_epoch = - query_storage_value::(&client, &start_epoch_key).await; let end_epoch = query_storage_value::(&client, &end_epoch_key).await; - match (start_epoch, end_epoch) { - (Some(start_epoch), Some(end_epoch)) => { + match end_epoch { + Some(end_epoch) => { if current_epoch > end_epoch { let votes = - get_proposal_votes(&client, start_epoch, id).await; + get_proposal_votes(&client, end_epoch, id).await; let proposal_result = - compute_tally(&client, start_epoch, votes).await; + compute_tally(&client, end_epoch, votes).await; println!("Proposal: {}", id); println!("{:4}Result: {}", "", proposal_result); } else { @@ -324,7 +321,7 @@ pub async fn query_proposal_result( cli::safe_exit(1) } } - _ => { + None => { eprintln!("Error while retriving proposal."); cli::safe_exit(1) } From 5b34bff794bb206e3697f61c2dd331ac7ba3a7af Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 23 Sep 2022 15:31:15 +0200 Subject: [PATCH 120/197] Misc refactoring --- apps/src/lib/client/rpc.rs | 109 ++++-------------- .../lib/node/ledger/shell/finalize_block.rs | 6 +- apps/src/lib/node/ledger/shell/governance.rs | 5 - shared/src/ledger/governance/utils.rs | 70 +++-------- 4 files changed, 41 insertions(+), 149 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 19ed8aaaa6..38da9b056d 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1550,45 +1550,15 @@ pub async fn get_proposal_votes( .await; if let Some(amount) = delegator_token_amount { if vote.is_yay() { - match yay_delegators.get_mut(&voter_address) { - Some(map) => { - map.insert( - validator_address, - VotePower::from(amount), - ); - } - None => { - let delegations_map: HashMap< - Address, - VotePower, - > = HashMap::from([( - validator_address, - VotePower::from(amount), - )]); - yay_delegators - .insert(voter_address, delegations_map); - } - } + let entry = + yay_delegators.entry(voter_address).or_default(); + entry + .insert(validator_address, VotePower::from(amount)); } else { - match nay_delegators.get_mut(&voter_address) { - Some(map) => { - map.insert( - validator_address, - VotePower::from(amount), - ); - } - None => { - let delegations_map: HashMap< - Address, - VotePower, - > = HashMap::from([( - validator_address, - VotePower::from(amount), - )]); - nay_delegators - .insert(voter_address, delegations_map); - } - } + let entry = + nay_delegators.entry(voter_address).or_default(); + entry + .insert(validator_address, VotePower::from(amount)); } } } @@ -1670,49 +1640,17 @@ pub async fn get_proposal_offline_votes( "Delegation key should contain validator address.", ); if proposal_vote.vote.is_yay() { - match yay_delegators.get_mut(&proposal_vote.address) { - Some(map) => { - map.insert( - validator_address, - VotePower::from(amount), - ); - } - None => { - let delegations_map: HashMap< - Address, - VotePower, - > = HashMap::from([( - validator_address, - VotePower::from(amount), - )]); - yay_delegators.insert( - proposal_vote.address.clone(), - delegations_map, - ); - } - } + let entry = yay_delegators + .entry(proposal_vote.address.clone()) + .or_default(); + entry + .insert(validator_address, VotePower::from(amount)); } else { - match nay_delegators.get_mut(&proposal_vote.address) { - Some(map) => { - map.insert( - validator_address, - VotePower::from(amount), - ); - } - None => { - let delegations_map: HashMap< - Address, - VotePower, - > = HashMap::from([( - validator_address, - VotePower::from(amount), - )]); - nay_delegators.insert( - proposal_vote.address.clone(), - delegations_map, - ); - } - } + let entry = nay_delegators + .entry(proposal_vote.address.clone()) + .or_default(); + entry + .insert(validator_address, VotePower::from(amount)); } } } @@ -1865,14 +1803,9 @@ async fn get_validator_stake( .await .expect("Total deltas should be defined"); let epoched_total_voting_power = total_voting_power.get(epoch); - if let Some(epoched_total_voting_power) = epoched_total_voting_power { - match VotePower::try_from(epoched_total_voting_power) { - Ok(voting_power) => voting_power, - Err(_) => VotePower::from(0_u64), - } - } else { - VotePower::from(0_u64) - } + + VotePower::try_from(epoched_total_voting_power.unwrap_or_default()) + .unwrap_or_default() } pub async fn get_delegators_delegation( diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index a52876492c..c15df6e399 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -44,8 +44,10 @@ where let (height, new_epoch) = self.update_state(req.header, req.hash, req.byzantine_validators); - let _proposals_result = - execute_governance_proposals(self, new_epoch, &mut response)?; + if new_epoch { + let _proposals_result = + execute_governance_proposals(self, &mut response)?; + } for processed_tx in &req.txs { let tx = if let Ok(tx) = Tx::try_from(processed_tx.tx.as_ref()) { diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index a1d17110eb..979c3c0df1 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -22,7 +22,6 @@ pub struct ProposalsResult { pub fn execute_governance_proposals( shell: &mut Shell, - new_epoch: bool, response: &mut shim::response::FinalizeBlock, ) -> Result where @@ -31,10 +30,6 @@ where { let mut proposals_result = ProposalsResult::default(); - if !new_epoch { - return Ok(proposals_result); - } - for id in std::mem::take(&mut shell.proposal_data) { let proposal_funds_key = gov_storage::get_funds_key(id); let proposal_end_epoch_key = gov_storage::get_voting_end_epoch_key(id); diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 3e75f99e93..03f75be722 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -244,57 +244,21 @@ where ); if let Some(amount) = amount { if vote.is_yay() { - match yay_delegators - .get_mut(voter_address) - { - Some(map) => { - map.insert( - validator_address - .clone(), - VotePower::from(amount), - ); - } - None => { - let delegations_map = - HashMap::from([( - validator_address - .clone(), - VotePower::from( - amount, - ), - )]); - yay_delegators.insert( - voter_address.clone(), - delegations_map, - ); - } - } + let entry = yay_delegators + .entry(voter_address.to_owned()) + .or_default(); + entry.insert( + validator_address.to_owned(), + VotePower::from(amount), + ); } else { - match nay_delegators - .get_mut(&voter_address.clone()) - { - Some(map) => { - map.insert( - validator_address - .clone(), - VotePower::from(amount), - ); - } - None => { - let delegations_map = - HashMap::from([( - validator_address - .clone(), - VotePower::from( - amount, - ), - )]); - nay_delegators.insert( - voter_address.clone(), - delegations_map, - ); - } - } + let entry = nay_delegators + .entry(voter_address.to_owned()) + .or_default(); + entry.insert( + validator_address.to_owned(), + VotePower::from(amount), + ); } } } @@ -383,10 +347,8 @@ where if let Some(total_delta) = total_delta { let epoched_total_delta = total_delta.get(epoch); if let Some(epoched_total_delta) = epoched_total_delta { - match VotePower::try_from(epoched_total_delta) { - Ok(voting_power) => return voting_power, - Err(_) => return VotePower::from(0_u64), - } + return VotePower::try_from(epoched_total_delta) + .unwrap_or_default(); } } } From e4ab8c985eecdb14a3e8d823930a8abd5fc31b54 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 26 Sep 2022 13:30:52 +0200 Subject: [PATCH 121/197] fix e2e tests --- tests/src/e2e/ledger_tests.rs | 15 ++++++++++++--- tests/src/e2e/setup.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 68b8b85cc4..f9ec4b4212 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -22,6 +22,7 @@ use namada_apps::config::genesis::genesis_config::{ use serde_json::json; use setup::constants::*; +use super::setup::get_all_wasms_hashes; use crate::e2e::helpers::{ find_address, find_voting_power, get_actor_rpc, get_epoch, }; @@ -991,16 +992,24 @@ fn ledger_many_txs_in_a_block() -> Result<()> { /// 13. Check governance address funds are 0 #[test] fn proposal_submission() -> Result<()> { + let working_dir = setup::working_dir(); + let test = setup::network( |genesis| { let parameters = ParametersConfig { min_num_of_blocks: 1, min_duration: 1, max_expected_time_per_block: 1, - vp_whitelist: genesis.parameters.vp_whitelist, + vp_whitelist: Some(get_all_wasms_hashes( + &working_dir, + Some("vp_"), + )), // Enable tx whitelist to test the execution of a // non-whitelisted tx by governance - tx_whitelist: Some(vec![String::from("test")]), + tx_whitelist: Some(get_all_wasms_hashes( + &working_dir, + Some("tx_"), + )), }; GenesisConfig { @@ -1450,7 +1459,7 @@ fn proposal_offline() -> Result<()> { let submit_proposal_vote = vec![ "vote-proposal", "--data-path", - test.test_dir.path().to_str().unwrap(), + valid_proposal_json_path.to_str().unwrap(),, "--vote", "yay", "--signer", diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 6499bf9806..d261ebcc0a 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::ffi::OsStr; use std::fmt::Display; use std::fs::{File, OpenOptions}; @@ -23,6 +24,7 @@ use namada_apps::client::utils; use namada_apps::config::genesis::genesis_config::{self, GenesisConfig}; use namada_apps::{config, wallet}; use rand::Rng; +use serde_json; use tempfile::{tempdir, TempDir}; use crate::e2e::helpers::generate_bin_command; @@ -860,3 +862,28 @@ pub fn copy_wasm_to_chain_dir<'a>( } } } + +pub fn get_all_wasms_hashes( + working_dir: &Path, + filter: Option<&str>, +) -> Vec { + let checksums_path = working_dir.join("wasm/checksums.json"); + let checksums_content = fs::read_to_string(checksums_path).unwrap(); + let checksums: HashMap = + serde_json::from_str(&checksums_content).unwrap(); + let filter_prefix = filter.unwrap_or_default(); + checksums + .values() + .filter_map(|wasm| { + if wasm.contains(&filter_prefix) { + Some( + wasm.split('.').collect::>()[1] + .to_owned() + .to_uppercase(), + ) + } else { + None + } + }) + .collect() +} From c0e18b933a116a55162006956288ceca541da539 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 26 Sep 2022 13:43:52 +0200 Subject: [PATCH 122/197] fix e2e tests --- tests/src/e2e/ledger_tests.rs | 4 +++- wasm/checksums.json | 34 +++++++++++++++++----------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index f9ec4b4212..5fe0e52921 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1456,10 +1456,12 @@ fn proposal_offline() -> Result<()> { epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } + let proposal_path = test.test_dir.path().join("proposal"); + let submit_proposal_vote = vec![ "vote-proposal", "--data-path", - valid_proposal_json_path.to_str().unwrap(),, + proposal_path.to_str().unwrap(), "--vote", "yay", "--signer", diff --git a/wasm/checksums.json b/wasm/checksums.json index b9af125eaf..dfd8e3e0ce 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.72b81bb51f67b574cf7da27204e1b22a9221c575d6cb25346521c01e763caaf9.wasm", - "tx_from_intent.wasm": "tx_from_intent.01872d339c58bd25149874eb7498c30e9d654c904cb3ff8d1708cb848d9b1c2d.wasm", - "tx_ibc.wasm": "tx_ibc.abc0775b0afa0966503ade2b11e510219c34e55e65ffc0ddaee5111c47d69fd6.wasm", - "tx_init_account.wasm": "tx_init_account.f6a9ee5a298c4ba7ccefeea083c5df42af5c924927245ce3d4f1b8c07665fa63.wasm", - "tx_init_nft.wasm": "tx_init_nft.5d4524436b5f6fcb29912d486dca279c3a8677a47ab7af9f535e4a2955f15469.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.b295c83a4bffb87b8c5bc987021c804caa839c3b224cc5bc5225505257ab9fad.wasm", - "tx_init_validator.wasm": "tx_init_validator.de89fa2521bfb06a064577ef0c31a5316d991a6b1413115958083c3328cf4abd.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.65835905408cb8b57ab679aef7816bff51fe38383313a855207081ed9d635e56.wasm", - "tx_transfer.wasm": "tx_transfer.399f825e9d40075fd981dd1be9b48332933223ac5dcc47c1375aa337da881fcb.wasm", - "tx_unbond.wasm": "tx_unbond.3854b888082ac44a971900a4c761aa4917a03fbaa0cdf403028222e97879614f.wasm", - "tx_update_vp.wasm": "tx_update_vp.f462d73148a9ed2e48ffd33d77713981e69d56b391a055b75d367915b2e768d2.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.6486326c8772c7a20d5e8640235fb54abaa5dc2b1c624e75d5a1f49e2df9cdab.wasm", - "tx_withdraw.wasm": "tx_withdraw.a04fd246f0871995ebc7291e376cff83bc33228403607e21ab490c3063e905b1.wasm", - "vp_nft.wasm": "vp_nft.f82082aa459fbb211271b03561f0d0fe9222320366dabab75bfedd84e596b477.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.7cd43d8cd003fea8614d35cdc3916e8ec05ff974d25b3e6d0dadf4fa74ed3ca6.wasm", - "vp_token.wasm": "vp_token.188e37e0deff4f3e2c5acb2037e43d0dd77080ecadd0353cb28f3e2f787bb0fc.wasm", - "vp_user.wasm": "vp_user.be9fab0f727449620989abe3219188138730cedca36076b6cc40a60f34e19af0.wasm" + "tx_bond.wasm": "tx_bond.ccbc6090956e0f76327ea9622f8f3e0f2ffd704b0f97f4612977c9a44a9db8d1.wasm", + "tx_from_intent.wasm": "tx_from_intent.46190da394e7188b74cba0f5d6e4575040a9a7ad1cf63e7bb116b9b12450f7f4.wasm", + "tx_ibc.wasm": "tx_ibc.94721e7b33b38742697de698d82cde35e7704f862d9c7d8de7d761216326d446.wasm", + "tx_init_account.wasm": "tx_init_account.cbd4d7e68cc3f9c1056b4e5d00d0286b98ddc105159255ff90d225f46962ef2f.wasm", + "tx_init_nft.wasm": "tx_init_nft.f5f37b6358040d4c4c63f817b54b5072f52f7fe1661172303e1ab93f9ab74285.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.bc8e3ccd6a3c2321ea3a199df0796fbb2d20044de4ec5a1f85f1266be20cac1b.wasm", + "tx_init_validator.wasm": "tx_init_validator.52bed77804568b4009720bc80452c1e7394c29d561497b1c09c84312decf2d19.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.771071501355a6d82031a6d01817c562e93abb7cbf29d8eab5882527426663c2.wasm", + "tx_transfer.wasm": "tx_transfer.6b54952c89732ed902bda78895510cf8a65de24c76b514c1cfd6a22d79f056f6.wasm", + "tx_unbond.wasm": "tx_unbond.dfc98d68f800df2e99ec7d5281d0c0199b64363942b50d264af79115d2239605.wasm", + "tx_update_vp.wasm": "tx_update_vp.f8dbd92128e858de1100500d0e2ae734aa51a9fe97ee2fd6a353853024b966d8.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.c168f3615edaa5733edb84169352d7d4979b8aa296718a1be614c0f334f91884.wasm", + "tx_withdraw.wasm": "tx_withdraw.28025d5e28c3f3a456d93042b8d2b865f7b1dba90fa9a8b5a49fc37505350321.wasm", + "vp_nft.wasm": "vp_nft.a27006540f8b687128b6e5019c6f73cfc81d75abab08b92ee70d013f33a9f055.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.952095dc7be7dd0f4db1454f835421d9c2661edf78c8182275363f72b2751837.wasm", + "vp_token.wasm": "vp_token.589171ceb6c3fd2a364a4c68738db737fbd74f8d2a44da9782b1872ab3a2e7f6.wasm", + "vp_user.wasm": "vp_user.ac27cb60a731a7e8526a2f369fa0491ffe9c09e4261090e01095d822c5eba8f2.wasm" } \ No newline at end of file From ccfa965ca87528ec11f91f359c3769aec4095d02 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 26 Sep 2022 14:48:31 +0200 Subject: [PATCH 123/197] changelog: add #501 --- .changelog/unreleased/improvements/467-governance-fixes.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/467-governance-fixes.md diff --git a/.changelog/unreleased/improvements/467-governance-fixes.md b/.changelog/unreleased/improvements/467-governance-fixes.md new file mode 100644 index 0000000000..441277d632 --- /dev/null +++ b/.changelog/unreleased/improvements/467-governance-fixes.md @@ -0,0 +1,2 @@ +- Fixed governance parameters, tally, tx whitelist and renamed treasury + ([#467](https://github.com/anoma/namada/issues/467)) \ No newline at end of file From 7e3cf9007788308387eb69d5770d1cdf4986b7f9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 26 Sep 2022 13:13:02 +0000 Subject: [PATCH 124/197] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index dfd8e3e0ce..b9af125eaf 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.ccbc6090956e0f76327ea9622f8f3e0f2ffd704b0f97f4612977c9a44a9db8d1.wasm", - "tx_from_intent.wasm": "tx_from_intent.46190da394e7188b74cba0f5d6e4575040a9a7ad1cf63e7bb116b9b12450f7f4.wasm", - "tx_ibc.wasm": "tx_ibc.94721e7b33b38742697de698d82cde35e7704f862d9c7d8de7d761216326d446.wasm", - "tx_init_account.wasm": "tx_init_account.cbd4d7e68cc3f9c1056b4e5d00d0286b98ddc105159255ff90d225f46962ef2f.wasm", - "tx_init_nft.wasm": "tx_init_nft.f5f37b6358040d4c4c63f817b54b5072f52f7fe1661172303e1ab93f9ab74285.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.bc8e3ccd6a3c2321ea3a199df0796fbb2d20044de4ec5a1f85f1266be20cac1b.wasm", - "tx_init_validator.wasm": "tx_init_validator.52bed77804568b4009720bc80452c1e7394c29d561497b1c09c84312decf2d19.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.771071501355a6d82031a6d01817c562e93abb7cbf29d8eab5882527426663c2.wasm", - "tx_transfer.wasm": "tx_transfer.6b54952c89732ed902bda78895510cf8a65de24c76b514c1cfd6a22d79f056f6.wasm", - "tx_unbond.wasm": "tx_unbond.dfc98d68f800df2e99ec7d5281d0c0199b64363942b50d264af79115d2239605.wasm", - "tx_update_vp.wasm": "tx_update_vp.f8dbd92128e858de1100500d0e2ae734aa51a9fe97ee2fd6a353853024b966d8.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.c168f3615edaa5733edb84169352d7d4979b8aa296718a1be614c0f334f91884.wasm", - "tx_withdraw.wasm": "tx_withdraw.28025d5e28c3f3a456d93042b8d2b865f7b1dba90fa9a8b5a49fc37505350321.wasm", - "vp_nft.wasm": "vp_nft.a27006540f8b687128b6e5019c6f73cfc81d75abab08b92ee70d013f33a9f055.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.952095dc7be7dd0f4db1454f835421d9c2661edf78c8182275363f72b2751837.wasm", - "vp_token.wasm": "vp_token.589171ceb6c3fd2a364a4c68738db737fbd74f8d2a44da9782b1872ab3a2e7f6.wasm", - "vp_user.wasm": "vp_user.ac27cb60a731a7e8526a2f369fa0491ffe9c09e4261090e01095d822c5eba8f2.wasm" + "tx_bond.wasm": "tx_bond.72b81bb51f67b574cf7da27204e1b22a9221c575d6cb25346521c01e763caaf9.wasm", + "tx_from_intent.wasm": "tx_from_intent.01872d339c58bd25149874eb7498c30e9d654c904cb3ff8d1708cb848d9b1c2d.wasm", + "tx_ibc.wasm": "tx_ibc.abc0775b0afa0966503ade2b11e510219c34e55e65ffc0ddaee5111c47d69fd6.wasm", + "tx_init_account.wasm": "tx_init_account.f6a9ee5a298c4ba7ccefeea083c5df42af5c924927245ce3d4f1b8c07665fa63.wasm", + "tx_init_nft.wasm": "tx_init_nft.5d4524436b5f6fcb29912d486dca279c3a8677a47ab7af9f535e4a2955f15469.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.b295c83a4bffb87b8c5bc987021c804caa839c3b224cc5bc5225505257ab9fad.wasm", + "tx_init_validator.wasm": "tx_init_validator.de89fa2521bfb06a064577ef0c31a5316d991a6b1413115958083c3328cf4abd.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.65835905408cb8b57ab679aef7816bff51fe38383313a855207081ed9d635e56.wasm", + "tx_transfer.wasm": "tx_transfer.399f825e9d40075fd981dd1be9b48332933223ac5dcc47c1375aa337da881fcb.wasm", + "tx_unbond.wasm": "tx_unbond.3854b888082ac44a971900a4c761aa4917a03fbaa0cdf403028222e97879614f.wasm", + "tx_update_vp.wasm": "tx_update_vp.f462d73148a9ed2e48ffd33d77713981e69d56b391a055b75d367915b2e768d2.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.6486326c8772c7a20d5e8640235fb54abaa5dc2b1c624e75d5a1f49e2df9cdab.wasm", + "tx_withdraw.wasm": "tx_withdraw.a04fd246f0871995ebc7291e376cff83bc33228403607e21ab490c3063e905b1.wasm", + "vp_nft.wasm": "vp_nft.f82082aa459fbb211271b03561f0d0fe9222320366dabab75bfedd84e596b477.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.7cd43d8cd003fea8614d35cdc3916e8ec05ff974d25b3e6d0dadf4fa74ed3ca6.wasm", + "vp_token.wasm": "vp_token.188e37e0deff4f3e2c5acb2037e43d0dd77080ecadd0353cb28f3e2f787bb0fc.wasm", + "vp_user.wasm": "vp_user.be9fab0f727449620989abe3219188138730cedca36076b6cc40a60f34e19af0.wasm" } \ No newline at end of file From b3f8289d42a86c7ee0c9c7b1431bc545b32a0753 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 27 Sep 2022 14:36:35 +0100 Subject: [PATCH 125/197] Enable mdbook-admonish for the specs mdbook --- .../specs/assets/mdbook-admonish.css | 352 ++++++++++++++++++ documentation/specs/book.toml | 7 +- 2 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 documentation/specs/assets/mdbook-admonish.css diff --git a/documentation/specs/assets/mdbook-admonish.css b/documentation/specs/assets/mdbook-admonish.css new file mode 100644 index 0000000000..5e360387df --- /dev/null +++ b/documentation/specs/assets/mdbook-admonish.css @@ -0,0 +1,352 @@ +@charset "UTF-8"; +:root { + --md-admonition-icon--note: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--abstract: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--info: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--tip: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--success: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--question: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--warning: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--failure: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--danger: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--bug: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--example: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--quote: + url("data:image/svg+xml;charset=utf-8,"); + --md-details-icon: + url("data:image/svg+xml;charset=utf-8,"); +} + +:is(.admonition) { + display: flow-root; + margin: 1.5625em 0; + padding: 0 1.2rem; + color: var(--fg); + page-break-inside: avoid; + background-color: var(--bg); + border: 0 solid black; + border-inline-start-width: 0.4rem; + border-radius: 0.2rem; + box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1); +} +@media print { + :is(.admonition) { + box-shadow: none; + } +} +:is(.admonition) > * { + box-sizing: border-box; +} +:is(.admonition) :is(.admonition) { + margin-top: 1em; + margin-bottom: 1em; +} +:is(.admonition) > .tabbed-set:only-child { + margin-top: 0; +} +html :is(.admonition) > :last-child { + margin-bottom: 1.2rem; +} + +a.admonition-anchor-link { + display: none; + position: absolute; + left: -1.2rem; + padding-right: 1rem; +} +a.admonition-anchor-link:link, a.admonition-anchor-link:visited { + color: var(--fg); +} +a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover { + text-decoration: none; +} +a.admonition-anchor-link::before { + content: "§"; +} + +:is(.admonition-title, summary) { + position: relative; + margin-block: 0; + margin-inline: -1.6rem -1.2rem; + padding-block: 0.8rem; + padding-inline: 4.4rem 1.2rem; + font-weight: 700; + background-color: rgba(68, 138, 255, 0.1); + display: flex; +} +:is(.admonition-title, summary) p { + margin: 0; +} +html :is(.admonition-title, summary):last-child { + margin-bottom: 0; +} +:is(.admonition-title, summary)::before { + position: absolute; + top: 0.625em; + inset-inline-start: 1.6rem; + width: 2rem; + height: 2rem; + background-color: #448aff; + mask-image: url('data:image/svg+xml;charset=utf-8,'); + -webkit-mask-image: url('data:image/svg+xml;charset=utf-8,'); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-size: contain; + content: ""; +} +:is(.admonition-title, summary):hover a.admonition-anchor-link { + display: initial; +} + +details.admonition > summary.admonition-title::after { + position: absolute; + top: 0.625em; + inset-inline-end: 1.6rem; + height: 2rem; + width: 2rem; + background-color: currentcolor; + mask-image: var(--md-details-icon); + -webkit-mask-image: var(--md-details-icon); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-size: contain; + content: ""; + transform: rotate(0deg); + transition: transform 0.25s; +} +details[open].admonition > summary.admonition-title::after { + transform: rotate(90deg); +} + +:is(.admonition):is(.note) { + border-color: #448aff; +} + +:is(.note) > :is(.admonition-title, summary) { + background-color: rgba(68, 138, 255, 0.1); +} +:is(.note) > :is(.admonition-title, summary)::before { + background-color: #448aff; + mask-image: var(--md-admonition-icon--note); + -webkit-mask-image: var(--md-admonition-icon--note); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.abstract, .summary, .tldr) { + border-color: #00b0ff; +} + +:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) { + background-color: rgba(0, 176, 255, 0.1); +} +:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before { + background-color: #00b0ff; + mask-image: var(--md-admonition-icon--abstract); + -webkit-mask-image: var(--md-admonition-icon--abstract); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.info, .todo) { + border-color: #00b8d4; +} + +:is(.info, .todo) > :is(.admonition-title, summary) { + background-color: rgba(0, 184, 212, 0.1); +} +:is(.info, .todo) > :is(.admonition-title, summary)::before { + background-color: #00b8d4; + mask-image: var(--md-admonition-icon--info); + -webkit-mask-image: var(--md-admonition-icon--info); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.tip, .hint, .important) { + border-color: #00bfa5; +} + +:is(.tip, .hint, .important) > :is(.admonition-title, summary) { + background-color: rgba(0, 191, 165, 0.1); +} +:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before { + background-color: #00bfa5; + mask-image: var(--md-admonition-icon--tip); + -webkit-mask-image: var(--md-admonition-icon--tip); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.success, .check, .done) { + border-color: #00c853; +} + +:is(.success, .check, .done) > :is(.admonition-title, summary) { + background-color: rgba(0, 200, 83, 0.1); +} +:is(.success, .check, .done) > :is(.admonition-title, summary)::before { + background-color: #00c853; + mask-image: var(--md-admonition-icon--success); + -webkit-mask-image: var(--md-admonition-icon--success); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.question, .help, .faq) { + border-color: #64dd17; +} + +:is(.question, .help, .faq) > :is(.admonition-title, summary) { + background-color: rgba(100, 221, 23, 0.1); +} +:is(.question, .help, .faq) > :is(.admonition-title, summary)::before { + background-color: #64dd17; + mask-image: var(--md-admonition-icon--question); + -webkit-mask-image: var(--md-admonition-icon--question); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.warning, .caution, .attention) { + border-color: #ff9100; +} + +:is(.warning, .caution, .attention) > :is(.admonition-title, summary) { + background-color: rgba(255, 145, 0, 0.1); +} +:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before { + background-color: #ff9100; + mask-image: var(--md-admonition-icon--warning); + -webkit-mask-image: var(--md-admonition-icon--warning); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.failure, .fail, .missing) { + border-color: #ff5252; +} + +:is(.failure, .fail, .missing) > :is(.admonition-title, summary) { + background-color: rgba(255, 82, 82, 0.1); +} +:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before { + background-color: #ff5252; + mask-image: var(--md-admonition-icon--failure); + -webkit-mask-image: var(--md-admonition-icon--failure); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.danger, .error) { + border-color: #ff1744; +} + +:is(.danger, .error) > :is(.admonition-title, summary) { + background-color: rgba(255, 23, 68, 0.1); +} +:is(.danger, .error) > :is(.admonition-title, summary)::before { + background-color: #ff1744; + mask-image: var(--md-admonition-icon--danger); + -webkit-mask-image: var(--md-admonition-icon--danger); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.bug) { + border-color: #f50057; +} + +:is(.bug) > :is(.admonition-title, summary) { + background-color: rgba(245, 0, 87, 0.1); +} +:is(.bug) > :is(.admonition-title, summary)::before { + background-color: #f50057; + mask-image: var(--md-admonition-icon--bug); + -webkit-mask-image: var(--md-admonition-icon--bug); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.example) { + border-color: #7c4dff; +} + +:is(.example) > :is(.admonition-title, summary) { + background-color: rgba(124, 77, 255, 0.1); +} +:is(.example) > :is(.admonition-title, summary)::before { + background-color: #7c4dff; + mask-image: var(--md-admonition-icon--example); + -webkit-mask-image: var(--md-admonition-icon--example); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.quote, .cite) { + border-color: #9e9e9e; +} + +:is(.quote, .cite) > :is(.admonition-title, summary) { + background-color: rgba(158, 158, 158, 0.1); +} +:is(.quote, .cite) > :is(.admonition-title, summary)::before { + background-color: #9e9e9e; + mask-image: var(--md-admonition-icon--quote); + -webkit-mask-image: var(--md-admonition-icon--quote); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +.navy :is(.admonition) { + background-color: var(--sidebar-bg); +} + +.ayu :is(.admonition), .coal :is(.admonition) { + background-color: var(--theme-hover); +} + +.rust :is(.admonition) { + background-color: var(--sidebar-bg); + color: var(--sidebar-fg); +} +.rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited { + color: var(--sidebar-fg); +} diff --git a/documentation/specs/book.toml b/documentation/specs/book.toml index 7848d519db..18b3e24ec9 100644 --- a/documentation/specs/book.toml +++ b/documentation/specs/book.toml @@ -9,6 +9,7 @@ title = "Namada Specifications" [output.html] edit-url-template = "https://github.com/anoma/namada/edit/main/documentation/specs/{path}" git-repository-url = "https://github.com/anoma/namada" +additional-css = ["assets/mdbook-admonish.css"] git-branch = "main" [output.html.search] @@ -18,4 +19,8 @@ expand = true [output.linkcheck] -[preprocessor.katex] \ No newline at end of file +[preprocessor.katex] + +[preprocessor.admonish] +command = "mdbook-admonish" +assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` From 9ac8bc402ba3c961bb468be50a391bffeeade1b9 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 7 Sep 2022 15:02:20 +0200 Subject: [PATCH 126/197] feat: update rocksdb version --- Cargo.lock | 1639 ++++++++++--------- Cargo.toml | 4 + apps/Cargo.toml | 2 +- apps/src/lib/node/ledger/storage/rocksdb.rs | 10 +- shared/src/ledger/storage/mockdb.rs | 10 +- shared/src/ledger/storage/types.rs | 5 +- wasm_for_tests/wasm_source/Cargo.lock | 24 +- 7 files changed, 857 insertions(+), 837 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eafd05b2ff..c2fa1b54da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli 0.26.1", + "gimli 0.26.2", ] [[package]] @@ -23,7 +23,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] @@ -34,7 +34,7 @@ checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "opaque-debug 0.3.0", ] @@ -58,20 +58,29 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", "once_cell", "version_check 0.9.4", ] [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -83,9 +92,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "ark-bls12-381" @@ -229,9 +238,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "ascii" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" [[package]] name = "asn1_der" @@ -261,9 +270,9 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-channel" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" dependencies = [ "concurrent-queue", "event-listener", @@ -286,26 +295,25 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.0.4" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43" +checksum = "0da5b41ee986eed3f524c380e6d64965aea573882a8907682ad100f7859305ca" dependencies = [ "async-channel", "async-executor", "async-io", - "async-mutex", + "async-lock", "blocking", "futures-lite", - "num_cpus", "once_cell", ] [[package]] name = "async-io" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" +version = "1.9.0" +source = "git+https://github.com/heliaxdev/async-io.git?rev=9285dad39c9a37ecd0dbd498c5ce5b0e65b02489#9285dad39c9a37ecd0dbd498c5ce5b0e65b02489" dependencies = [ + "autocfg 1.1.0", "concurrent-queue", "futures-lite", "libc", @@ -313,8 +321,9 @@ dependencies = [ "once_cell", "parking", "polling", + "rustversion", "slab", - "socket2 0.4.4", + "socket2 0.4.7", "waker-fn", "winapi 0.3.9", ] @@ -328,44 +337,36 @@ dependencies = [ "event-listener", ] -[[package]] -name = "async-mutex" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" -dependencies = [ - "event-listener", -] - [[package]] name = "async-process" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" +version = "1.5.0" +source = "git+https://github.com/heliaxdev/async-process.git?rev=e42c527e87d937da9e01aaeb563c0b948580dc89#e42c527e87d937da9e01aaeb563c0b948580dc89" dependencies = [ "async-io", + "autocfg 1.1.0", "blocking", "cfg-if 1.0.0", "event-listener", "futures-lite", "libc", "once_cell", + "rustversion", "signal-hook", "winapi 0.3.9", ] [[package]] name = "async-std" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ "async-channel", "async-global-executor", "async-io", "async-lock", "async-process", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.12", "futures-channel", "futures-core", "futures-io", @@ -374,7 +375,6 @@ dependencies = [ "kv-log-macro", "log 0.4.17", "memchr", - "num_cpus", "once_cell", "pin-project-lite 0.2.9", "pin-utils", @@ -419,15 +419,15 @@ dependencies = [ [[package]] name = "async-task" -version = "4.2.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -456,7 +456,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures-sink", "futures-util", "memchr", @@ -480,12 +480,12 @@ checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" [[package]] name = "atty" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ + "hermit-abi", "libc", - "termion", "winapi 0.3.9", ] @@ -506,16 +506,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object", + "object 0.29.0", "rustc-demangle", ] @@ -568,19 +568,19 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "serde 1.0.137", + "serde 1.0.145", ] [[package]] name = "bindgen" -version = "0.59.2" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" +checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" dependencies = [ "bitflags", "cexpr", "clang-sys", - "lazy_static 1.4.0", + "lazy_static", "lazycell", "peeking_take_while", "proc-macro2", @@ -592,9 +592,9 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] @@ -628,7 +628,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -663,7 +663,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -685,16 +685,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding 0.2.1", - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] @@ -779,16 +779,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "memchr", "regex-automata", ] [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "byte-tools" @@ -807,9 +807,9 @@ dependencies = [ [[package]] name = "bytecheck" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a31f923c2db9513e4298b72df143e6e655a759b3d6a0966df18f81223fff54f" +checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -817,9 +817,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb17c862a905d912174daa27ae002326fff56dc8b8ada50a0a5f0976cb174f0" +checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" dependencies = [ "proc-macro2", "quote", @@ -850,9 +850,9 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "bzip2-sys" @@ -871,12 +871,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +[[package]] +name = "camino" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e" + [[package]] name = "cargo-watch" -version = "7.7.0" +version = "7.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a4cf2908216028d1d97f49ed180367f009fdb3cd07550d0ef2db42bd6c739f" +checksum = "45fee6e0b2e10066aee5060c0dc74826f9a6447987fc535ca7719ae8883abd8e" dependencies = [ + "camino", "clap 2.34.0", "log 0.4.17", "shell-escape", @@ -886,9 +893,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" dependencies = [ "jobserver", ] @@ -928,13 +935,13 @@ dependencies = [ [[package]] name = "chacha20" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", ] [[package]] @@ -952,14 +959,16 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits 0.2.15", "time 0.1.44", + "wasm-bindgen", "winapi 0.3.9", ] @@ -975,14 +984,14 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] name = "clang-sys" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" +checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" dependencies = [ "glob", "libc", @@ -999,7 +1008,6 @@ dependencies = [ "atty", "bitflags", "strsim 0.8.0", - "term_size", "textwrap 0.11.0", "unicode-width", "vec_map", @@ -1013,7 +1021,7 @@ dependencies = [ "atty", "bitflags", "indexmap", - "lazy_static 1.4.0", + "lazy_static", "os_str_bytes", "strsim 0.10.0", "termcolor", @@ -1022,6 +1030,19 @@ dependencies = [ "vec_map", ] +[[package]] +name = "clearscreen" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c969a6b6dadff9f3349b1f783f553e2411104763ca4789e1c6ca6a41f46a57b0" +dependencies = [ + "nix 0.24.2", + "terminfo", + "thiserror", + "which", + "winapi 0.3.9", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -1059,10 +1080,20 @@ checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" dependencies = [ "once_cell", "owo-colors", - "tracing-core 0.1.27", + "tracing-core 0.1.29", "tracing-error", ] +[[package]] +name = "command-group" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a8a86f409b4a59df3a3e4bee2de0b83f1755fdd2a25e3a9684c396fc4bed2c" +dependencies = [ + "nix 0.22.3", + "winapi 0.3.9", +] + [[package]] name = "concat-idents" version = "1.1.3" @@ -1075,9 +1106,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.2" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" dependencies = [ "cache-padded", ] @@ -1088,10 +1119,10 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "nom 5.1.2", "rust-ini", - "serde 1.0.137", + "serde 1.0.145", "serde-hjson", "serde_json", "toml", @@ -1146,9 +1177,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -1175,7 +1206,7 @@ dependencies = [ "gimli 0.25.0", "log 0.4.17", "regalloc", - "smallvec 1.8.0", + "smallvec 1.10.0", "target-lexicon", ] @@ -1209,7 +1240,7 @@ checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" dependencies = [ "cranelift-codegen", "log 0.4.17", - "smallvec 1.8.0", + "smallvec 1.10.0", "target-lexicon", ] @@ -1224,35 +1255,34 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.12", ] [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.12", ] [[package]] name = "crossbeam-epoch" -version = "0.9.8" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", - "lazy_static 1.4.0", + "crossbeam-utils 0.8.12", "memoffset", "scopeguard", ] @@ -1265,17 +1295,16 @@ checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ "autocfg 1.1.0", "cfg-if 0.1.10", - "lazy_static 1.4.0", + "lazy_static", ] [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if 1.0.0", - "lazy_static 1.4.0", ] [[package]] @@ -1290,19 +1319,19 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ - "generic-array 0.14.5", - "rand_core 0.6.3", + "generic-array 0.14.6", + "rand_core 0.6.4", "subtle 2.4.1", "zeroize", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", "typenum", ] @@ -1322,7 +1351,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", "subtle 2.4.1", ] @@ -1332,7 +1361,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", "subtle 2.4.1", ] @@ -1353,9 +1382,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" dependencies = [ "quote", "syn", @@ -1389,24 +1418,24 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" dependencies = [ "curl-sys", "libc", "openssl-probe", "openssl-sys", "schannel", - "socket2 0.4.4", + "socket2 0.4.7", "winapi 0.3.9", ] [[package]] name = "curl-sys" -version = "0.4.55+curl-7.83.1" +version = "0.4.56+curl-7.83.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23734ec77368ec583c2e61dd3f0b0e5c98b93abe6d2a004ca06b91dd7e3e2762" +checksum = "6093e169dd4de29e468fa649fbae11cdcd5551c81fe5bf1b0677adad7ef3d26f" dependencies = [ "cc", "libc", @@ -1438,7 +1467,7 @@ checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle-ng", "zeroize", ] @@ -1455,12 +1484,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.4" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" dependencies = [ - "darling_core 0.13.4", - "darling_macro 0.13.4", + "darling_core 0.14.1", + "darling_macro 0.14.1", ] [[package]] @@ -1479,9 +1508,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" dependencies = [ "fnv", "ident_case", @@ -1503,11 +1532,11 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" dependencies = [ - "darling_core 0.13.4", + "darling_core 0.14.1", "quote", "syn", ] @@ -1582,9 +1611,9 @@ dependencies = [ [[package]] name = "diff" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "difflib" @@ -1607,16 +1636,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] name = "digest" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.3", "crypto-common", "subtle 2.4.1", ] @@ -1630,6 +1659,16 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.3.7" @@ -1671,7 +1710,7 @@ checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" dependencies = [ "bitflags", "byteorder", - "lazy_static 1.4.0", + "lazy_static", "proc-macro-error", "proc-macro2", "quote", @@ -1707,7 +1746,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ - "serde 1.0.137", + "serde 1.0.145", "signature", ] @@ -1719,8 +1758,8 @@ checksum = "758e2a0cd8a6cdf483e1d369e7d081647e00b88d8953e34d8f2cbba05ae28368" dependencies = [ "curve25519-dalek-ng", "hex", - "rand_core 0.6.3", - "serde 1.0.137", + "rand_core 0.6.4", + "serde 1.0.145", "sha2 0.9.9", "thiserror", "zeroize", @@ -1736,7 +1775,7 @@ dependencies = [ "ed25519", "merlin", "rand 0.7.3", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", "sha2 0.9.9", "zeroize", @@ -1744,9 +1783,9 @@ dependencies = [ [[package]] name = "either" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" @@ -1758,27 +1797,14 @@ dependencies = [ "crypto-bigint", "der", "ff", - "generic-array 0.14.5", + "generic-array 0.14.6", "group", - "rand_core 0.6.3", + "rand_core 0.6.4", "sec1", "subtle 2.4.1", "zeroize", ] -[[package]] -name = "embed-resource" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc24ff8d764818e9ab17963b0593c535f077a513f565e75e4352d758bc4d8c0" -dependencies = [ - "cc", - "rustc_version 0.4.0", - "toml", - "vswhom", - "winreg 0.10.1", -] - [[package]] name = "encoding_rs" version = "0.8.31" @@ -1822,34 +1848,25 @@ dependencies = [ [[package]] name = "enumset" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" +checksum = "19be8061a06ab6f3a6cf21106c873578bf01bd42ad15e0311a9c76161cb1c753" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" +checksum = "03e7b551eba279bf0fa88b83a46330168c1560a52a94f5126f892f0b364ab3e0" dependencies = [ - "darling 0.13.4", + "darling 0.14.1", "proc-macro2", "quote", "syn", ] -[[package]] -name = "env_logger" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" -dependencies = [ - "log 0.4.17", -] - [[package]] name = "escargot" version = "0.5.7" @@ -1858,15 +1875,15 @@ checksum = "f5584ba17d7ab26a8a7284f13e5bd196294dd2f2d79773cff29b9e9edef601a6" dependencies = [ "log 0.4.17", "once_cell", - "serde 1.0.137", + "serde 1.0.145", "serde_json", ] [[package]] name = "event-listener" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "expectrl" @@ -1904,9 +1921,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -1914,7 +1931,7 @@ dependencies = [ [[package]] name = "ferveo" version = "0.1.1" -source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-bls12-381", @@ -1928,19 +1945,19 @@ dependencies = [ "blake2 0.10.4", "blake2b_simd", "borsh", - "digest 0.10.3", + "digest 0.10.5", "ed25519-dalek", "either", "ferveo-common", "group-threshold-cryptography", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "measure_time", "miracl_core", "num", "rand 0.7.3", "rand 0.8.5", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", "serde_json", "subproductdomain", @@ -1951,13 +1968,13 @@ dependencies = [ [[package]] name = "ferveo-common" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-ec", "ark-serialize", "ark-std", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", ] @@ -1967,27 +1984,27 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle 2.4.1", ] [[package]] name = "file-lock" -version = "2.1.4" +version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d68ace7c2694114c6d6b1047f87f8422cb84ab21e3166728c1af5a77e2e5325" +checksum = "0815fc2a1924e651b71ae6d13df07b356a671a09ecaf857dbd344a2ba937a496" dependencies = [ "cc", "libc", "mktemp", - "nix 0.24.1", + "nix 0.24.2", ] [[package]] name = "file-serve" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a09c8127b1a49f66ac56a7c9efe420e2ab23e00266ea4144cc2b905076a7f1" +checksum = "e43addbb09a5dcb5609cb44a01a79e67716fe40b50c109f50112ef201a8c7c59" dependencies = [ "log 0.4.17", "mime_guess", @@ -1996,14 +2013,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.13", - "winapi 0.3.9", + "redox_syscall 0.2.16", + "windows-sys", ] [[package]] @@ -2014,9 +2031,9 @@ checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" [[package]] name = "fixedbitset" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" @@ -2062,12 +2079,11 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", - "percent-encoding 2.1.0", + "percent-encoding 2.2.0", ] [[package]] @@ -2125,9 +2141,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" dependencies = [ "futures-channel", "futures-core", @@ -2140,9 +2156,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", "futures-sink", @@ -2150,15 +2166,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" dependencies = [ "futures-core", "futures-task", @@ -2168,9 +2184,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" [[package]] name = "futures-lite" @@ -2189,9 +2205,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" dependencies = [ "proc-macro2", "quote", @@ -2211,15 +2227,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-timer" @@ -2229,9 +2245,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ "futures-channel", "futures-core", @@ -2256,9 +2272,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check 0.9.4", @@ -2277,13 +2293,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -2309,9 +2325,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" [[package]] name = "git2" @@ -2325,7 +2341,7 @@ dependencies = [ "log 0.4.17", "openssl-probe", "openssl-sys", - "url 2.2.2", + "url 2.3.1", ] [[package]] @@ -2336,9 +2352,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.8" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a" dependencies = [ "aho-corasick", "bstr", @@ -2366,14 +2382,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "ff", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle 2.4.1", ] [[package]] name = "group-threshold-cryptography" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-bls12-381", @@ -2383,12 +2399,12 @@ dependencies = [ "ark-serialize", "ark-std", "blake2b_simd", - "chacha20 0.8.1", + "chacha20 0.8.2", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "miracl_core", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "rayon", "subproductdomain", "thiserror", @@ -2416,11 +2432,11 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "fnv", "futures-core", "futures-sink", @@ -2429,8 +2445,8 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.3", - "tracing 0.1.35", + "tokio-util 0.7.4", + "tracing 0.1.36", ] [[package]] @@ -2444,41 +2460,37 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] [[package]] name = "hdrhistogram" -version = "7.5.0" +version = "7.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31672b7011be2c4f7456c4ddbcb40e7e9a4a9fad8efe49a6ebaf5f307d0109c0" +checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" dependencies = [ - "base64 0.13.0", "byteorder", - "crossbeam-channel", - "flate2", - "nom 7.1.1", "num-traits 0.2.15", ] [[package]] name = "headers" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64 0.13.0", "bitflags", - "bytes 1.1.0", + "bytes 1.2.1", "headers-core", "http", "httpdate", "mime 0.3.16", - "sha-1 0.10.0", + "sha1", ] [[package]] @@ -2574,7 +2586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array 0.14.5", + "generic-array 0.14.6", "hmac 0.8.1", ] @@ -2595,7 +2607,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "fnv", "itoa", ] @@ -2606,16 +2618,16 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "http", "pin-project-lite 0.2.9", ] [[package]] name = "httparse" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -2644,11 +2656,11 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.19" +version = "0.14.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures-channel", "futures-core", "futures-util", @@ -2659,10 +2671,10 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite 0.2.9", - "socket2 0.4.4", + "socket2 0.4.7", "tokio", "tower-service", - "tracing 0.1.35", + "tracing 0.1.36", "want", ] @@ -2672,11 +2684,11 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "headers", "http", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-rustls", "rustls-native-certs", "tokio", @@ -2693,7 +2705,7 @@ checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" dependencies = [ "ct-logs", "futures-util", - "hyper 0.14.19", + "hyper 0.14.20", "log 0.4.17", "rustls", "rustls-native-certs", @@ -2709,7 +2721,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.19", + "hyper 0.14.20", "pin-project-lite 0.2.9", "tokio", "tokio-io-timeout", @@ -2721,19 +2733,32 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.1.0", - "hyper 0.14.19", + "bytes 1.2.1", + "hyper 0.14.20", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "wasm-bindgen", + "winapi 0.3.9", +] + [[package]] name = "ibc" version = "0.12.0" source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "derive_more", "flex-error", "ibc-proto", @@ -2742,17 +2767,17 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "safe-regex", - "serde 1.0.137", + "serde 1.0.145", "serde_derive", "serde_json", - "sha2 0.10.2", + "sha2 0.10.6", "subtle-encoding", "tendermint", "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.9", - "tracing 0.1.35", + "time 0.3.14", + "tracing 0.1.36", ] [[package]] @@ -2760,10 +2785,10 @@ name = "ibc-proto" version = "0.16.0" source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.137", + "serde 1.0.145", "tendermint-proto", "tonic", ] @@ -2775,7 +2800,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce15e4758c46a0453bdf4b3b1dfcce70c43f79d1943c2ee0635b77eb2e7aa233" dependencies = [ "anyhow", - "bytes 1.1.0", + "bytes 1.2.1", "hex", "prost 0.9.0", "ripemd160", @@ -2812,6 +2837,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "if-addrs" version = "0.6.7" @@ -2840,7 +2875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae8ab7f67bad3240049cb24fb9cb0b4c2c6af4c245840917fbbdededeee91179" dependencies = [ "async-io", - "futures 0.3.21", + "futures 0.3.24", "futures-lite", "if-addrs", "ipnet", @@ -2857,13 +2892,13 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.2" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg 1.1.0", - "hashbrown 0.11.2", - "serde 1.0.137", + "hashbrown 0.12.3", + "serde 1.0.145", ] [[package]] @@ -2892,7 +2927,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", ] [[package]] @@ -2909,9 +2944,9 @@ dependencies = [ [[package]] name = "integer-encoding" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e85a1509a128c855368e135cffcde7eac17d8e1083f41e2b98c58bc1a5074be" +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" [[package]] name = "iovec" @@ -2951,33 +2986,33 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "jobserver" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] @@ -2989,7 +3024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" dependencies = [ "log 0.4.17", - "serde 1.0.137", + "serde 1.0.145", "serde_json", ] @@ -3037,12 +3072,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" -[[package]] -name = "lazy_static" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" - [[package]] name = "lazy_static" version = "1.4.0" @@ -3076,9 +3105,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.121" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "libgit2-sys" @@ -3110,9 +3139,9 @@ version = "0.38.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "atomic", - "bytes 1.1.0", - "futures 0.3.21", - "lazy_static 1.4.0", + "bytes 1.2.1", + "futures 0.3.24", + "lazy_static", "libp2p-core", "libp2p-deflate", "libp2p-dns", @@ -3137,8 +3166,8 @@ dependencies = [ "libp2p-yamux", "parity-multiaddr", "parking_lot 0.11.2", - "pin-project 1.0.10", - "smallvec 1.8.0", + "pin-project 1.0.12", + "smallvec 1.10.0", "wasm-timer", ] @@ -3152,23 +3181,23 @@ dependencies = [ "ed25519-dalek", "either", "fnv", - "futures 0.3.21", + "futures 0.3.24", "futures-timer", - "lazy_static 1.4.0", + "lazy_static", "libsecp256k1 0.3.5", "log 0.4.17", "multihash", "multistream-select", "parity-multiaddr", "parking_lot 0.11.2", - "pin-project 1.0.10", + "pin-project 1.0.12", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", "ring", "rw-stream-sink", "sha2 0.9.9", - "smallvec 1.8.0", + "smallvec 1.10.0", "thiserror", "unsigned-varint 0.7.1", "void", @@ -3181,7 +3210,7 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "flate2", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", ] @@ -3191,10 +3220,10 @@ version = "0.28.1" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-std-resolver", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "log 0.4.17", - "smallvec 1.8.0", + "smallvec 1.10.0", "trust-dns-resolver", ] @@ -3205,14 +3234,14 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "cuckoofilter", "fnv", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.10.0", ] [[package]] @@ -3223,9 +3252,9 @@ dependencies = [ "asynchronous-codec", "base64 0.13.0", "byteorder", - "bytes 1.1.0", + "bytes 1.2.1", "fnv", - "futures 0.3.21", + "futures 0.3.24", "hex_fmt", "libp2p-core", "libp2p-swarm", @@ -3235,7 +3264,7 @@ dependencies = [ "rand 0.7.3", "regex", "sha2 0.9.9", - "smallvec 1.8.0", + "smallvec 1.10.0", "unsigned-varint 0.7.1", "wasm-timer", ] @@ -3245,13 +3274,13 @@ name = "libp2p-identify" version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", "prost 0.7.0", "prost-build 0.7.0", - "smallvec 1.8.0", + "smallvec 1.10.0", "wasm-timer", ] @@ -3262,10 +3291,10 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "arrayvec 0.5.2", "asynchronous-codec", - "bytes 1.1.0", + "bytes 1.2.1", "either", "fnv", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", @@ -3273,7 +3302,7 @@ dependencies = [ "prost-build 0.7.0", "rand 0.7.3", "sha2 0.9.9", - "smallvec 1.8.0", + "smallvec 1.10.0", "uint", "unsigned-varint 0.7.1", "void", @@ -3288,15 +3317,15 @@ dependencies = [ "async-io", "data-encoding", "dns-parser", - "futures 0.3.21", + "futures 0.3.24", "if-watch", - "lazy_static 1.4.0", + "lazy_static", "libp2p-core", "libp2p-swarm", "log 0.4.17", "rand 0.8.5", - "smallvec 1.8.0", - "socket2 0.4.4", + "smallvec 1.10.0", + "socket2 0.4.7", "void", ] @@ -3306,14 +3335,14 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "libp2p-core", "log 0.4.17", "nohash-hasher", "parking_lot 0.11.2", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.10.0", "unsigned-varint 0.7.1", ] @@ -3322,10 +3351,10 @@ name = "libp2p-noise" version = "0.31.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "curve25519-dalek", - "futures 0.3.21", - "lazy_static 1.4.0", + "futures 0.3.24", + "lazy_static", "libp2p-core", "log 0.4.17", "prost 0.7.0", @@ -3343,7 +3372,7 @@ name = "libp2p-ping" version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", @@ -3358,8 +3387,8 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "libp2p-core", "log 0.4.17", "prost 0.7.0", @@ -3373,9 +3402,9 @@ name = "libp2p-pnet" version = "0.21.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "log 0.4.17", - "pin-project 1.0.10", + "pin-project 1.0.12", "rand 0.7.3", "salsa20", "sha3", @@ -3387,17 +3416,17 @@ version = "0.2.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "futures-timer", "libp2p-core", "libp2p-swarm", "log 0.4.17", - "pin-project 1.0.10", + "pin-project 1.0.12", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.10.0", "unsigned-varint 0.7.1", "void", "wasm-timer", @@ -3409,15 +3438,15 @@ version = "0.11.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-trait", - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", "lru", "minicbor", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.10.0", "unsigned-varint 0.7.1", "wasm-timer", ] @@ -3428,11 +3457,11 @@ version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "either", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "log 0.4.17", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.10.0", "void", "wasm-timer", ] @@ -3452,14 +3481,14 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-io", - "futures 0.3.21", + "futures 0.3.24", "futures-timer", "if-watch", "ipnet", "libc", "libp2p-core", "log 0.4.17", - "socket2 0.4.4", + "socket2 0.4.7", ] [[package]] @@ -3468,7 +3497,7 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-std", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "log 0.4.17", ] @@ -3478,7 +3507,7 @@ name = "libp2p-wasm-ext" version = "0.28.2" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "js-sys", "libp2p-core", "parity-send-wrapper", @@ -3492,14 +3521,14 @@ version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "either", - "futures 0.3.21", + "futures 0.3.24", "futures-rustls", "libp2p-core", "log 0.4.17", "quicksink", "rw-stream-sink", "soketto", - "url 2.2.2", + "url 2.3.1", "webpki-roots", ] @@ -3508,7 +3537,7 @@ name = "libp2p-yamux" version = "0.32.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "parking_lot 0.11.2", "thiserror", @@ -3517,9 +3546,9 @@ dependencies = [ [[package]] name = "librocksdb-sys" -version = "0.6.1+6.28.2" +version = "0.8.0+7.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc587013734dadb7cf23468e531aa120788b87243648be42e2d3a072186291" +checksum = "611804e4666a25136fcc5f8cf425ab4d26c7f74ea245ffe92ea23b85b6420b5d" dependencies = [ "bindgen", "bzip2-sys", @@ -3559,7 +3588,7 @@ dependencies = [ "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand 0.8.5", - "serde 1.0.137", + "serde 1.0.145", "sha2 0.9.9", "typenum", ] @@ -3618,12 +3647,11 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" dependencies = [ - "serde 1.0.137", - "serde_test", + "serde 1.0.145", ] [[package]] @@ -3637,9 +3665,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg 1.1.0", "scopeguard", @@ -3721,7 +3749,7 @@ dependencies = [ "indexmap", "linked-hash-map", "regex", - "serde 1.0.137", + "serde 1.0.145", "serde_derive", "serde_yaml", ] @@ -3771,9 +3799,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5172b50c23043ff43dd53e51392f36519d9b35a8f3a410d30ece5d1aedd58ae" +checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" dependencies = [ "libc", ] @@ -3806,15 +3834,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" dependencies = [ "crossbeam-channel", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.12", "integer-encoding", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "mio 0.7.14", - "serde 1.0.137", + "serde 1.0.145", "strum", "tungstenite 0.16.0", - "url 2.2.2", + "url 2.3.1", ] [[package]] @@ -3870,9 +3898,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", ] @@ -3911,9 +3939,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log 0.4.17", @@ -3982,7 +4010,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dac63698b887d2d929306ea48b63760431ff8a24fac40ddb22f9c7f49fb7cab" dependencies = [ "digest 0.9.0", - "generic-array 0.14.5", + "generic-array 0.14.6", "multihash-derive", "sha2 0.9.9", "unsigned-varint 0.5.1", @@ -3994,7 +4022,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate 1.2.1", "proc-macro-error", "proc-macro2", "quote", @@ -4013,11 +4041,11 @@ name = "multistream-select" version = "0.10.3" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "log 0.4.17", - "pin-project 1.0.10", - "smallvec 1.8.0", + "pin-project 1.0.12", + "smallvec 1.10.0", "unsigned-varint 0.7.1", ] @@ -4043,7 +4071,7 @@ dependencies = [ "ibc", "ibc-proto", "ics23", - "itertools 0.10.3", + "itertools 0.10.5", "libsecp256k1 0.7.0", "loupe", "namada_proof_of_stake", @@ -4054,9 +4082,9 @@ dependencies = [ "prost-types 0.9.0", "pwasm-utils", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "rust_decimal", - "serde 1.0.137", + "serde 1.0.145", "serde_json", "sha2 0.9.9", "sparse-merkle-tree", @@ -4066,8 +4094,8 @@ dependencies = [ "test-log", "thiserror", "tonic-build", - "tracing 0.1.35", - "tracing-subscriber 0.3.11", + "tracing 0.1.36", + "tracing-subscriber 0.3.15", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -4106,10 +4134,10 @@ dependencies = [ "ferveo-common", "file-lock", "flate2", - "futures 0.3.21", + "futures 0.3.24", "git2", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "jsonpath_lib", "libc", "libloading", @@ -4126,14 +4154,14 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "rayon", "regex", "reqwest", "rlimit", "rocksdb", "rpassword", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", "serde_json", "serde_regex", @@ -4156,9 +4184,9 @@ dependencies = [ "tonic-build", "tower", "tower-abci", - "tracing 0.1.35", + "tracing 0.1.36", "tracing-log", - "tracing-subscriber 0.3.11", + "tracing-subscriber 0.3.15", "websocket", "winapi 0.3.9", ] @@ -4168,8 +4196,8 @@ name = "namada_encoding_spec" version = "0.7.1" dependencies = [ "borsh", - "itertools 0.10.3", - "lazy_static 1.4.0", + "itertools 0.10.5", + "lazy_static", "madato", "namada", ] @@ -4207,7 +4235,7 @@ dependencies = [ "file-serve", "fs_extra", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "libp2p", "namada", "namada_apps", @@ -4221,8 +4249,8 @@ dependencies = [ "tempfile", "test-log", "toml", - "tracing 0.1.35", - "tracing-subscriber 0.3.11", + "tracing 0.1.36", + "tracing-subscriber 0.3.15", ] [[package]] @@ -4230,7 +4258,7 @@ name = "namada_tx_prelude" version = "0.7.1" dependencies = [ "namada_vm_env", - "sha2 0.10.2", + "sha2 0.10.6", ] [[package]] @@ -4248,7 +4276,7 @@ name = "namada_vp_prelude" version = "0.7.1" dependencies = [ "namada_vm_env", - "sha2 0.10.2", + "sha2 0.10.6", ] [[package]] @@ -4257,7 +4285,7 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "libc", "log 0.4.17", "openssl", @@ -4282,9 +4310,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.20.2" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" +checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" dependencies = [ "bitflags", "cc", @@ -4295,9 +4323,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" dependencies = [ "bitflags", "cc", @@ -4321,9 +4349,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -4411,9 +4439,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" dependencies = [ "num-traits 0.2.15", ] @@ -4452,9 +4480,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg 1.1.0", "num-bigint", @@ -4499,12 +4527,6 @@ dependencies = [ "libc", ] -[[package]] -name = "numtoa" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" - [[package]] name = "object" version = "0.28.4" @@ -4517,11 +4539,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.12.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -4537,9 +4568,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.40" +version = "0.10.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -4569,9 +4600,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.74" +version = "0.9.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" +checksum = "5230151e44c0f05157effb743e8d517472843121cf9243e8b81393edb5acd9ce" dependencies = [ "autocfg 1.1.0", "cc", @@ -4587,7 +4618,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", - "getrandom 0.2.6", + "getrandom 0.2.7", "subtle 2.4.1", "zeroize", ] @@ -4623,11 +4654,11 @@ dependencies = [ "byteorder", "data-encoding", "multihash", - "percent-encoding 2.1.0", - "serde 1.0.137", + "percent-encoding 2.2.0", + "serde 1.0.145", "static_assertions", "unsigned-varint 0.7.1", - "url 2.2.2", + "url 2.3.1", ] [[package]] @@ -4666,7 +4697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", - "lock_api 0.4.7", + "lock_api 0.4.9", "parking_lot_core 0.8.5", ] @@ -4676,7 +4707,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "lock_api 0.4.7", + "lock_api 0.4.9", "parking_lot_core 0.9.3", ] @@ -4704,8 +4735,8 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.2.13", - "smallvec 1.8.0", + "redox_syscall 0.2.16", + "smallvec 1.10.0", "winapi 0.3.9", ] @@ -4717,16 +4748,16 @@ checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.13", - "smallvec 1.8.0", + "redox_syscall 0.2.16", + "smallvec 1.10.0", "windows-sys", ] [[package]] name = "paste" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "pathdiff" @@ -4775,9 +4806,9 @@ checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" @@ -4804,33 +4835,71 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ - "fixedbitset 0.4.1", + "fixedbitset 0.4.2", "indexmap", ] +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand 0.7.3", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9615c18d31137579e9ff063499264ddc1278e7b1982757ebc111028c4d1dc909" +checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a" dependencies = [ - "pin-project-internal 0.4.29", + "pin-project-internal 0.4.30", ] [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ - "pin-project-internal 1.0.10", + "pin-project-internal 1.0.12", ] [[package]] name = "pin-project-internal" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "044964427019eed9d49d9d5bbce6047ef18f37100ea400912a9fa4a3523ab12a" +checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e" dependencies = [ "proc-macro2", "quote", @@ -4839,9 +4908,9 @@ dependencies = [ [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", @@ -4874,13 +4943,14 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "polling" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +version = "2.3.0" +source = "git+https://github.com/heliaxdev/polling.git?rev=02a655775282879459a3460e2646b60c005bca2c#02a655775282879459a3460e2646b60c005bca2c" dependencies = [ + "autocfg 1.1.0", "cfg-if 1.0.0", "libc", "log 0.4.17", + "rustversion", "wepoll-ffi", "winapi 0.3.9", ] @@ -4891,7 +4961,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "opaque-debug 0.3.0", "universal-hash", ] @@ -4903,7 +4973,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "opaque-debug 0.3.0", "universal-hash", ] @@ -4921,7 +4991,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" dependencies = [ "difflib", - "itertools 0.10.3", + "itertools 0.10.5", "predicates-core", ] @@ -4964,10 +5034,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" dependencies = [ + "once_cell", "thiserror", "toml", ] @@ -4998,9 +5069,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ "unicode-ident", ] @@ -5013,7 +5084,7 @@ dependencies = [ "bit-set", "bitflags", "byteorder", - "lazy_static 1.4.0", + "lazy_static", "num-traits 0.2.15", "quick-error 2.0.1", "rand 0.8.5", @@ -5030,7 +5101,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost-derive 0.7.0", ] @@ -5040,7 +5111,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost-derive 0.9.0", ] @@ -5050,7 +5121,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32d3ebd75ac2679c2af3a92246639f9fcc8a442ee420719cc4fe195b98dd5fa3" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "heck 0.3.3", "itertools 0.9.0", "log 0.4.17", @@ -5068,10 +5139,10 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "heck 0.3.3", - "itertools 0.10.3", - "lazy_static 1.4.0", + "itertools 0.10.5", + "lazy_static", "log 0.4.17", "multimap", "petgraph 0.6.2", @@ -5102,7 +5173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools 0.10.3", + "itertools 0.10.5", "proc-macro2", "quote", "syn", @@ -5114,7 +5185,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b518d7cdd93dab1d1122cf07fa9a60771836c668dde9d9e2a139f957f0d9f1bb" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost 0.7.0", ] @@ -5124,7 +5195,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost 0.9.0", ] @@ -5193,9 +5264,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.18" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -5214,7 +5285,7 @@ dependencies = [ "rand_isaac", "rand_jitter", "rand_os", - "rand_pcg", + "rand_pcg 0.1.2", "rand_xorshift 0.1.1", "winapi 0.3.9", ] @@ -5230,6 +5301,7 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", + "rand_pcg 0.2.1", ] [[package]] @@ -5240,7 +5312,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -5270,7 +5342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -5299,11 +5371,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", ] [[package]] @@ -5368,6 +5440,15 @@ dependencies = [ "rand_core 0.4.2", ] +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rand_xorshift" version = "0.1.1" @@ -5383,7 +5464,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -5406,7 +5487,7 @@ checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.12", "num_cpus", ] @@ -5427,30 +5508,21 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] -[[package]] -name = "redox_termios" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" -dependencies = [ - "redox_syscall 0.2.13", -] - [[package]] name = "redox_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.6", - "redox_syscall 0.2.13", + "getrandom 0.2.7", + "redox_syscall 0.2.16", "thiserror", ] @@ -5462,14 +5534,14 @@ checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" dependencies = [ "log 0.4.17", "rustc-hash", - "smallvec 1.8.0", + "smallvec 1.10.0", ] [[package]] name = "regex" -version = "1.5.6" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -5487,9 +5559,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "region" @@ -5523,34 +5595,35 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.10" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" +checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" dependencies = [ "base64 0.13.0", - "bytes 1.1.0", + "bytes 1.2.1", "encoding_rs", "futures-core", "futures-util", "h2", "http", "http-body", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-tls", "ipnet", "js-sys", - "lazy_static 1.4.0", "log 0.4.17", "mime 0.3.16", "native-tls", - "percent-encoding 2.1.0", + "once_cell", + "percent-encoding 2.2.0", "pin-project-lite 0.2.9", - "serde 1.0.137", + "serde 1.0.145", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", - "url 2.2.2", + "tower-service", + "url 2.3.1", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -5606,12 +5679,12 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.38" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517a3034eb2b1499714e9d1e49b2367ad567e07639b69776d35e259d9c27cca6" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" dependencies = [ "bytecheck", - "hashbrown 0.12.1", + "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", @@ -5620,9 +5693,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.38" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505c209ee04111a006431abf39696e640838364d67a107c559ababaf6fd8c9dd" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", @@ -5640,9 +5713,9 @@ dependencies = [ [[package]] name = "rocksdb" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "620f4129485ff1a7128d184bc687470c21c7951b64779ebc9cfdad3dcd920290" +checksum = "7e9562ea1d70c0cc63a34a22d977753b50cca91cc6b6527750463bd5dd8697bc" dependencies = [ "libc", "librocksdb-sys", @@ -5666,13 +5739,13 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rust_decimal" -version = "1.24.0" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ee7337df68898256ad0d4af4aad178210d9e44d2ff900ce44064a97cd86530" +checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec 0.7.2", "num-traits 0.2.15", - "serde 1.0.137", + "serde 1.0.145", ] [[package]] @@ -5705,15 +5778,6 @@ dependencies = [ "semver 0.11.0", ] -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.10", -] - [[package]] name = "rustls" version = "0.19.1" @@ -5741,9 +5805,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" [[package]] name = "rusty-fork" @@ -5763,16 +5827,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" dependencies = [ - "futures 0.3.21", - "pin-project 0.4.29", + "futures 0.3.24", + "pin-project 0.4.30", "static_assertions", ] [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "safe-proc-macro2" @@ -5851,7 +5915,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "windows-sys", ] @@ -5884,7 +5948,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", - "generic-array 0.14.5", + "generic-array 0.14.6", "subtle 2.4.1", "zeroize", ] @@ -5930,12 +5994,6 @@ dependencies = [ "semver-parser 0.10.2", ] -[[package]] -name = "semver" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" - [[package]] name = "semver-parser" version = "0.7.0" @@ -5959,9 +6017,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.137" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] @@ -5972,7 +6030,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "num-traits 0.1.43", "regex", "serde 0.8.23", @@ -5980,18 +6038,18 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" dependencies = [ - "serde 1.0.137", + "serde 1.0.145", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -6000,14 +6058,14 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "indexmap", "itoa", "ryu", - "serde 1.0.137", + "serde 1.0.145", ] [[package]] @@ -6017,29 +6075,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", - "serde 1.0.137", + "serde 1.0.145", ] [[package]] name = "serde_repr" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "serde_test" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe196827aea34242c314d2f0dd49ed00a129225e80dda71b0dbf65d54d25628d" -dependencies = [ - "serde 1.0.137", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -6049,7 +6098,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.137", + "serde 1.0.145", ] [[package]] @@ -6060,7 +6109,7 @@ checksum = "ef8099d3df28273c99a1728190c7a9f19d444c941044f64adf986bee7ec53051" dependencies = [ "dtoa", "linked-hash-map", - "serde 1.0.137", + "serde 1.0.145", "yaml-rust", ] @@ -6084,20 +6133,20 @@ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "digest 0.9.0", "opaque-debug 0.3.0", ] [[package]] -name = "sha-1" -version = "0.10.0" +name = "sha1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", - "digest 0.10.3", + "cpufeatures 0.2.5", + "digest 0.10.5", ] [[package]] @@ -6120,20 +6169,20 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "digest 0.9.0", "opaque-debug 0.3.0", ] [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", - "digest 0.10.3", + "cpufeatures 0.2.5", + "digest 0.10.5", ] [[package]] @@ -6154,7 +6203,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", ] [[package]] @@ -6195,7 +6244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ "digest 0.9.0", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -6204,11 +6253,20 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg 1.1.0", +] [[package]] name = "smallvec" @@ -6221,9 +6279,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "snow" @@ -6235,7 +6293,7 @@ dependencies = [ "blake2 0.9.2", "chacha20poly1305", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "ring", "rustc_version 0.3.3", "sha2 0.9.9", @@ -6256,9 +6314,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi 0.3.9", @@ -6273,7 +6331,7 @@ dependencies = [ "base64 0.12.3", "bytes 0.5.6", "flate2", - "futures 0.3.21", + "futures 0.3.24", "httparse", "log 0.4.17", "rand 0.7.3", @@ -6318,15 +6376,15 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stderrlog" -version = "0.4.3" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e5ee9b90a5452c570a0b0ac1c99ae9498db7e56e33d74366de7f2a7add7f25" +checksum = "af95cb8a5f79db5b2af2a46f44da7594b5adbcbb65cbf87b8da0959bfdd82460" dependencies = [ "atty", "chrono", "log 0.4.17", "termcolor", - "thread_local 0.3.4", + "thread_local", ] [[package]] @@ -6343,18 +6401,18 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.24.0" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck 0.4.0", "proc-macro2", @@ -6366,7 +6424,7 @@ dependencies = [ [[package]] name = "subproductdomain" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-ec", @@ -6405,9 +6463,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.96" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" dependencies = [ "proc-macro2", "quote", @@ -6466,7 +6524,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "libc", - "redox_syscall 0.2.13", + "redox_syscall 0.2.16", "remove_dir_all", "winapi 0.3.9", ] @@ -6477,18 +6535,18 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", - "bytes 1.1.0", + "bytes 1.2.1", "ed25519", "ed25519-dalek", "flex-error", - "futures 0.3.21", + "futures 0.3.24", "k256", "num-traits 0.2.15", "once_cell", "prost 0.9.0", "prost-types 0.9.0", "ripemd160", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", "serde_json", "serde_repr", @@ -6497,7 +6555,7 @@ dependencies = [ "subtle 2.4.1", "subtle-encoding", "tendermint-proto", - "time 0.3.9", + "time 0.3.14", "zeroize", ] @@ -6507,11 +6565,11 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", - "serde 1.0.137", + "serde 1.0.145", "serde_json", "tendermint", "toml", - "url 2.2.2", + "url 2.3.1", ] [[package]] @@ -6521,10 +6579,10 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "derive_more", "flex-error", - "serde 1.0.137", + "serde 1.0.145", "tendermint", "tendermint-rpc", - "time 0.3.9", + "time 0.3.14", ] [[package]] @@ -6532,16 +6590,16 @@ name = "tendermint-proto" version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "flex-error", "num-derive", "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", "subtle-encoding", - "time 0.3.9", + "time 0.3.14", ] [[package]] @@ -6551,17 +6609,17 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "async-trait", "async-tungstenite", - "bytes 1.1.0", + "bytes 1.2.1", "flex-error", - "futures 0.3.21", - "getrandom 0.2.6", + "futures 0.3.24", + "getrandom 0.2.7", "http", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-proxy", "hyper-rustls", "peg", - "pin-project 1.0.10", - "serde 1.0.137", + "pin-project 1.0.12", + "serde 1.0.145", "serde_bytes", "serde_json", "subtle-encoding", @@ -6569,10 +6627,10 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.9", + "time 0.3.14", "tokio", - "tracing 0.1.35", - "url 2.2.2", + "tracing 0.1.36", + "url 2.3.1", "uuid", "walkdir", ] @@ -6584,22 +6642,12 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.137", + "serde 1.0.145", "serde_json", "simple-error", "tempfile", "tendermint", - "time 0.3.9", -] - -[[package]] -name = "term_size" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" -dependencies = [ - "libc", - "winapi 0.3.9", + "time 0.3.14", ] [[package]] @@ -6612,15 +6660,16 @@ dependencies = [ ] [[package]] -name = "termion" -version = "1.5.6" +name = "terminfo" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" +checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e" dependencies = [ - "libc", - "numtoa", - "redox_syscall 0.2.13", - "redox_termios", + "dirs", + "fnv", + "nom 5.1.2", + "phf", + "phf_codegen", ] [[package]] @@ -6631,9 +6680,9 @@ checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "test-log" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4235dbf7ea878b3ef12dea20a59c134b405a66aafc4fc2c7b9935916e289e735" +checksum = "38f0c854faeb68a048f0f2dc410c5ddae3bf83854ef0e4977d58306a5edef50e" dependencies = [ "proc-macro2", "quote", @@ -6646,7 +6695,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "term_size", "unicode-width", ] @@ -6679,16 +6727,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" -dependencies = [ - "lazy_static 0.2.11", - "unreachable", -] - [[package]] name = "thread_local" version = "1.1.4" @@ -6711,9 +6749,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" dependencies = [ "itoa", "libc", @@ -6736,8 +6774,8 @@ dependencies = [ "ascii", "chunked_transfer", "log 0.4.17", - "time 0.3.9", - "url 2.2.2", + "time 0.3.14", + "url 2.3.1", ] [[package]] @@ -6757,20 +6795,20 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.19.2" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ - "bytes 1.1.0", + "autocfg 1.1.0", + "bytes 1.2.1", "libc", "memchr", - "mio 0.8.3", + "mio 0.8.4", "num_cpus", - "once_cell", "parking_lot 0.12.1", "pin-project-lite 0.2.9", "signal-hook-registry", - "socket2 0.4.4", + "socket2 0.4.7", "tokio-macros", "winapi 0.3.9", ] @@ -6846,7 +6884,7 @@ checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" dependencies = [ "crossbeam-utils 0.7.2", "futures 0.1.31", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "mio 0.6.23", "num_cpus", @@ -6870,9 +6908,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" dependencies = [ "futures-core", "pin-project-lite 0.2.9", @@ -6910,7 +6948,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3" dependencies = [ "async-stream", - "bytes 1.1.0", + "bytes 1.2.1", "futures-core", "tokio", "tokio-stream", @@ -6933,7 +6971,7 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures-core", "futures-sink", "log 0.4.17", @@ -6943,16 +6981,16 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures-core", "futures-sink", "pin-project-lite 0.2.9", "tokio", - "tracing 0.1.35", + "tracing 0.1.36", ] [[package]] @@ -6961,7 +6999,7 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ - "serde 1.0.137", + "serde 1.0.145", ] [[package]] @@ -6973,16 +7011,16 @@ dependencies = [ "async-stream", "async-trait", "base64 0.13.0", - "bytes 1.1.0", + "bytes 1.2.1", "futures-core", "futures-util", "h2", "http", "http-body", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-timeout", - "percent-encoding 2.1.0", - "pin-project 1.0.10", + "percent-encoding 2.2.0", + "pin-project 1.0.12", "prost 0.9.0", "prost-derive 0.9.0", "tokio", @@ -6991,7 +7029,7 @@ dependencies = [ "tower", "tower-layer", "tower-service", - "tracing 0.1.35", + "tracing 0.1.36", "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -7009,23 +7047,23 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", "hdrhistogram", "indexmap", - "pin-project 1.0.10", + "pin-project 1.0.12", "pin-project-lite 0.2.9", "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.7.4", "tower-layer", "tower-service", - "tracing 0.1.35", + "tracing 0.1.36", ] [[package]] @@ -7033,9 +7071,9 @@ name = "tower-abci" version = "0.1.0" source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200#f6463388fc319b6e210503b43b3aecf6faf6b200" dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", - "pin-project 1.0.10", + "bytes 1.2.1", + "futures 0.3.24", + "pin-project 1.0.12", "prost 0.9.0", "tendermint-proto", "tokio", @@ -7063,9 +7101,9 @@ dependencies = [ [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" @@ -7080,15 +7118,15 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if 1.0.0", "log 0.4.17", "pin-project-lite 0.2.9", - "tracing-attributes 0.1.21", - "tracing-core 0.1.27", + "tracing-attributes 0.1.22", + "tracing-core 0.1.29", ] [[package]] @@ -7103,9 +7141,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" dependencies = [ "proc-macro2", "quote", @@ -7117,14 +7155,14 @@ name = "tracing-core" version = "0.1.22" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", ] [[package]] name = "tracing-core" -version = "0.1.27" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ "once_cell", "valuable", @@ -7136,7 +7174,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" dependencies = [ - "tracing 0.1.35", + "tracing 0.1.36", "tracing-subscriber 0.2.25", ] @@ -7146,8 +7184,8 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project 1.0.10", - "tracing 0.1.35", + "pin-project 1.0.12", + "tracing 0.1.36", ] [[package]] @@ -7165,9 +7203,9 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", - "tracing-core 0.1.27", + "tracing-core 0.1.29", ] [[package]] @@ -7177,25 +7215,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ "sharded-slab", - "thread_local 1.1.4", - "tracing-core 0.1.27", + "thread_local", + "tracing-core 0.1.29", ] [[package]] name = "tracing-subscriber" -version = "0.3.11" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" dependencies = [ "ansi_term", - "lazy_static 1.4.0", "matchers", + "once_cell", "regex", "sharded-slab", - "smallvec 1.8.0", - "thread_local 1.1.4", - "tracing 0.1.35", - "tracing-core 0.1.27", + "smallvec 1.10.0", + "thread_local", + "tracing 0.1.36", + "tracing-core 0.1.29", "tracing-log", ] @@ -7204,7 +7242,7 @@ name = "tracing-tower" version = "0.1.0" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "pin-project-lite 0.2.9", "tower-layer", "tower-make", @@ -7234,13 +7272,13 @@ dependencies = [ "futures-util", "idna 0.2.3", "ipnet", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "rand 0.8.5", - "smallvec 1.8.0", + "smallvec 1.10.0", "thiserror", "tinyvec", - "url 2.2.2", + "url 2.3.1", ] [[package]] @@ -7252,12 +7290,12 @@ dependencies = [ "cfg-if 1.0.0", "futures-util", "ipconfig", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "lru-cache", "parking_lot 0.11.2", "resolv-conf", - "smallvec 1.8.0", + "smallvec 1.10.0", "thiserror", "trust-dns-proto", ] @@ -7276,14 +7314,14 @@ checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" dependencies = [ "base64 0.13.0", "byteorder", - "bytes 1.1.0", + "bytes 1.2.1", "http", "httparse", "input_buffer", "log 0.4.17", "rand 0.8.5", "sha-1 0.9.8", - "url 2.2.2", + "url 2.3.1", "utf-8", ] @@ -7295,14 +7333,14 @@ checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" dependencies = [ "base64 0.13.0", "byteorder", - "bytes 1.1.0", + "bytes 1.2.1", "http", "httparse", "log 0.4.17", "rand 0.8.5", "sha-1 0.9.8", "thiserror", - "url 2.2.2", + "url 2.3.1", "utf-8", ] @@ -7320,15 +7358,15 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" dependencies = [ "byteorder", "crunchy", @@ -7362,36 +7400,36 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" @@ -7399,19 +7437,10 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", "subtle 2.4.1", ] -[[package]] -name = "unreachable" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -dependencies = [ - "void", -] - [[package]] name = "unsigned-varint" version = "0.5.1" @@ -7425,7 +7454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", + "bytes 1.2.1", "futures-io", "futures-util", ] @@ -7449,14 +7478,13 @@ dependencies = [ [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", - "idna 0.2.3", - "matches", - "percent-encoding 2.1.0", + "idna 0.3.0", + "percent-encoding 2.2.0", ] [[package]] @@ -7477,7 +7505,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", ] [[package]] @@ -7526,26 +7554,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22025f6d8eb903ebf920ea6933b70b1e495be37e2cb4099e62c80454aaf57c39" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "wait-timeout" version = "0.2.0" @@ -7602,9 +7610,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -7612,13 +7620,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static 1.4.0", "log 0.4.17", + "once_cell", "proc-macro2", "quote", "syn", @@ -7627,9 +7635,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.30" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -7639,9 +7647,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7649,9 +7657,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -7662,15 +7670,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wasm-encoder" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f0c17267a5ffd6ae3d897589460e21db1673c84fb7016b909c9691369a75ea" +checksum = "7e7ca71c70a6de5b10968ae4d298e548366d9cd9588176e6ff8866f3c49c96ee" dependencies = [ "leb128", ] @@ -7681,7 +7689,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "js-sys", "parking_lot 0.11.2", "pin-utils", @@ -7737,9 +7745,9 @@ dependencies = [ "enumset", "loupe", "rkyv", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", - "smallvec 1.8.0", + "smallvec 1.10.0", "target-lexicon", "thiserror", "wasmer-types", @@ -7760,9 +7768,9 @@ dependencies = [ "loupe", "more-asserts", "rayon", - "smallvec 1.8.0", + "smallvec 1.10.0", "target-lexicon", - "tracing 0.1.35", + "tracing 0.1.36", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -7777,11 +7785,11 @@ dependencies = [ "byteorder", "dynasm", "dynasmrt", - "lazy_static 1.4.0", + "lazy_static", "loupe", "more-asserts", "rayon", - "smallvec 1.8.0", + "smallvec 1.10.0", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -7807,12 +7815,12 @@ checksum = "41db0ac4df90610cda8320cfd5abf90c6ec90e298b6fe5a09a81dff718b55640" dependencies = [ "backtrace", "enumset", - "lazy_static 1.4.0", + "lazy_static", "loupe", "memmap2", "more-asserts", "rustc-demangle", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", "target-lexicon", "thiserror", @@ -7833,11 +7841,11 @@ dependencies = [ "leb128", "libloading", "loupe", - "object", + "object 0.28.4", "rkyv", - "serde 1.0.137", + "serde 1.0.145", "tempfile", - "tracing 0.1.35", + "tracing 0.1.36", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -7872,7 +7880,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" dependencies = [ - "object", + "object 0.28.4", "thiserror", "wasmer-compiler", "wasmer-types", @@ -7887,7 +7895,7 @@ dependencies = [ "indexmap", "loupe", "rkyv", - "serde 1.0.137", + "serde 1.0.145", "thiserror", ] @@ -7908,7 +7916,7 @@ dependencies = [ "more-asserts", "region", "rkyv", - "serde 1.0.137", + "serde 1.0.145", "thiserror", "wasmer-types", "winapi 0.3.9", @@ -7928,9 +7936,9 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "42.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "badcb03f976f983ff0daf294da9697be659442f61e6b0942bb37a2b6cbfe9dd4" +checksum = "117ccfc4262e62a28a13f0548a147f19ffe71e8a08be802af23ae4ea0bedad73" dependencies = [ "leb128", "memchr", @@ -7940,28 +7948,27 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.44" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b92f20b742ac527066c8414bc0637352661b68cab07ef42586cefaba71c965cf" +checksum = "7aab4e20c60429fbba9670a6cae0fff9520046ba0aa3e6d0b1cd2653bea14898" dependencies = [ "wast", ] [[package]] name = "watchexec" -version = "1.15.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a0fba7f2b9f4240dadf1c2eb4cf15359ffeb19d4f1841cb2d85a7bb4f82755f" +checksum = "c52e0868bc57765fa91593a173323f464855e53b27f779e1110ba0fb4cb6b406" dependencies = [ - "clap 2.34.0", + "clearscreen", + "command-group", "derive_builder", - "embed-resource", - "env_logger", "glob", "globset", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", - "nix 0.20.2", + "nix 0.22.3", "notify", "walkdir", "winapi 0.3.9", @@ -7969,9 +7976,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -7998,9 +8005,9 @@ dependencies = [ [[package]] name = "websocket" -version = "0.26.4" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e2836502b48713d4e391e7e016df529d46e269878fe5d961b15a1fd6417f1a" +checksum = "92aacab060eea423e4036820ddd28f3f9003b2c4d8048cbda985e5a14e18038d" dependencies = [ "bytes 0.4.12", "futures 0.1.31", @@ -8019,9 +8026,9 @@ dependencies = [ [[package]] name = "websocket-base" -version = "0.26.2" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f3fd505ff930da84156389639932955fb09705b3dccd1a3d60c8e7ff62776" +checksum = "49aec794b07318993d1db16156d5a9c750120597a5ee40c6b928d416186cb138" dependencies = [ "base64 0.10.1", "bitflags", @@ -8048,13 +8055,13 @@ dependencies = [ [[package]] name = "which" -version = "4.2.5" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" dependencies = [ "either", - "lazy_static 1.4.0", "libc", + "once_cell", ] [[package]] @@ -8255,7 +8262,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "log 0.4.17", "nohash-hasher", "parking_lot 0.11.2", @@ -8265,9 +8272,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.5.5" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94693807d016b2f2d2e14420eb3bfcca689311ff775dcf113d74ea624b7cdf07" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] @@ -8286,9 +8293,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.6.3+zstd.1.5.2" +version = "2.0.1+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" +checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" dependencies = [ "cc", "libc", diff --git a/Cargo.toml b/Cargo.toml index 2bb0d8ad10..3ce019636e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,10 @@ borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4 borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} +# The following 3 crates patch a work-around for https://github.com/smol-rs/polling/issues/38 breaking namada tooling build with nightly 2022-05-20 +polling = {git = "https://github.com/heliaxdev/polling.git", rev = "02a655775282879459a3460e2646b60c005bca2c"} +async-io = {git = "https://github.com/heliaxdev/async-io.git", rev = "9285dad39c9a37ecd0dbd498c5ce5b0e65b02489"} +async-process = {git = "https://github.com/heliaxdev/async-process.git", rev = "e42c527e87d937da9e01aaeb563c0b948580dc89"} # borsh = {path = "../borsh-rs/borsh"} # borsh-derive = {path = "../borsh-rs/borsh-derive"} # borsh-derive-internal = {path = "../borsh-rs/borsh-derive-internal"} diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 65d7d84eed..4e76e87241 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -92,7 +92,7 @@ rayon = "=1.5.1" regex = "1.4.5" reqwest = "0.11.4" rlimit = "0.5.4" -rocksdb = {version = "0.18.0", features = ['zstd'], default-features = false} +rocksdb = {version = "0.19.0", features = ['zstd'], default-features = false} rpassword = "5.0.1" serde = {version = "1.0.125", features = ["derive"]} serde_bytes = "0.11.5" diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 71ed305ea5..6735aff187 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -323,10 +323,14 @@ impl DB for RocksDB { let mut epoch = None; let mut pred_epochs = None; let mut address_gen = None; - for (key, bytes) in self.0.iterator_opt( + for value in self.0.iterator_opt( IteratorMode::From(prefix.as_bytes(), Direction::Forward), read_opts, ) { + let (key, bytes) = match value { + Ok(data) => data, + Err(e) => return Err(Error::DBError(e.into_string())), + }; let path = &String::from_utf8((*key).to_vec()).map_err(|e| { Error::Temporary { error: format!( @@ -837,7 +841,9 @@ impl<'a> Iterator for PersistentPrefixIterator<'a> { /// Returns the next pair and the gas cost fn next(&mut self) -> Option<(String, Vec, u64)> { match self.0.iter.next() { - Some((key, val)) => { + Some(result) => { + let (key, val) = + result.expect("Prefix iterator shouldn't fail"); let key = String::from_utf8(key.to_vec()) .expect("Cannot convert from bytes to key string"); match key.strip_prefix(&self.0.db_prefix) { diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 3cc7e8863c..63bc03a525 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -447,15 +447,15 @@ pub struct MockIterator { pub type MockPrefixIterator = PrefixIterator; impl Iterator for MockIterator { - type Item = KVBytes; + type Item = Result; fn next(&mut self) -> Option { for (key, val) in &mut self.iter { if key.starts_with(&self.prefix) { - return Some(( + return Some(Ok(( Box::from(key.as_bytes()), Box::from(val.as_slice()), - )); + ))); } } None @@ -468,7 +468,9 @@ impl Iterator for PrefixIterator { /// Returns the next pair and the gas cost fn next(&mut self) -> Option<(String, Vec, u64)> { match self.iter.next() { - Some((key, val)) => { + Some(result) => { + let (key, val) = + result.expect("Prefix iterator shouldn't fail"); let key = String::from_utf8(key.to_vec()) .expect("Cannot convert from bytes to key string"); match key.strip_prefix(&self.db_prefix) { diff --git a/shared/src/ledger/storage/types.rs b/shared/src/ledger/storage/types.rs index c30c3873fc..0f270d4413 100644 --- a/shared/src/ledger/storage/types.rs +++ b/shared/src/ledger/storage/types.rs @@ -45,9 +45,10 @@ pub struct PrefixIterator { impl PrefixIterator { /// Initialize a new prefix iterator - pub fn new(iter: I, db_prefix: String) -> Self + pub fn new(iter: I, db_prefix: String) -> Self where - I: Iterator, + E: std::error::Error, + I: Iterator>, { PrefixIterator { iter, db_prefix } } diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 9df5195b2f..876455fcaa 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1357,7 +1357,7 @@ dependencies = [ [[package]] name = "libsecp256k1" version = "0.7.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", @@ -1373,7 +1373,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1383,7 +1383,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1527,7 +1527,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1584,7 +1584,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -1593,7 +1593,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1611,7 +1611,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1619,7 +1619,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -1629,7 +1629,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1637,7 +1637,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "getrandom", From e476d046583aeffea5dabe3907d052e80cfe69fe Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 7 Sep 2022 15:07:08 +0200 Subject: [PATCH 127/197] feat: rocksdb use jemalloc --- Cargo.lock | 12 ++++++++++++ apps/Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c2fa1b54da..d631ecb754 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3556,6 +3556,7 @@ dependencies = [ "glob", "libc", "libz-sys", + "tikv-jemalloc-sys", "zstd-sys", ] @@ -6736,6 +6737,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.1+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "931e876f91fed0827f863a2d153897790da0b24d882c721a79cb3beb0b903261" +dependencies = [ + "cc", + "fs_extra", + "libc", +] + [[package]] name = "time" version = "0.1.44" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 4e76e87241..ee8249e340 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -92,7 +92,7 @@ rayon = "=1.5.1" regex = "1.4.5" reqwest = "0.11.4" rlimit = "0.5.4" -rocksdb = {version = "0.19.0", features = ['zstd'], default-features = false} +rocksdb = {version = "0.19.0", features = ['zstd', 'jemalloc'], default-features = false} rpassword = "5.0.1" serde = {version = "1.0.125", features = ["derive"]} serde_bytes = "0.11.5" From 7b82cb85e3a66c26642c90bee185adbb2c3a8184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 3 Oct 2022 15:59:20 +0200 Subject: [PATCH 128/197] changelog: add #452 --- .changelog/unreleased/miscellaneous/452-update-rocksdb.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/miscellaneous/452-update-rocksdb.md diff --git a/.changelog/unreleased/miscellaneous/452-update-rocksdb.md b/.changelog/unreleased/miscellaneous/452-update-rocksdb.md new file mode 100644 index 0000000000..83ad52ce87 --- /dev/null +++ b/.changelog/unreleased/miscellaneous/452-update-rocksdb.md @@ -0,0 +1,2 @@ +- Updated rockDB dependency to 0.19.0 and enabled its jemalloc feature. + ([#452](https://github.com/anoma/namada/pull/452)) \ No newline at end of file From bd894ff52043712ae33328ea9ce77b4ca94f051b Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Tue, 4 Oct 2022 12:55:56 +0200 Subject: [PATCH 129/197] ci: added slack secret --- .github/workflows/automation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml index 2bcd22fa6a..58ea874ee0 100644 --- a/.github/workflows/automation.yml +++ b/.github/workflows/automation.yml @@ -77,6 +77,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_READ_ORG_TOKEN: ${{ secrets.GT_READ_ORG }} GITHUB_DISPATCH_TOKEN: ${{ secrets.GT_DISPATCH }} + SLACK_DEVNET_SECRET: ${{ secrets.SLACK_DEVNET_SECRET }} BINARIES_COMMIT_SHA: ${{ steps.comment-branch.outputs.head_sha }} - name: Upload load tester logs if: ${{ matrix.make.logs == 'true' && steps.check.outputs.triggered == 'true' }} From 27ffd3d67df858eb079363da13979749ae03728e Mon Sep 17 00:00:00 2001 From: Awa Sun Yin Date: Tue, 4 Oct 2022 12:34:00 -0300 Subject: [PATCH 130/197] added the right favicon --- .gitignore | 3 +++ documentation/docs/theme/favicon.png | Bin 5037 -> 222 bytes documentation/docs/theme/favicon.svg | 7 +------ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 5f835e6405..381262dbdf 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ wasm/*.wasm # app version string file /apps/version.rs + +# for annoying contributors using mac +*.DS_Store \ No newline at end of file diff --git a/documentation/docs/theme/favicon.png b/documentation/docs/theme/favicon.png index f34029b97164e824eaf0dd6d37db05e11c5e7a8c..11dfb07620650b235461cc83a25af0665d9329fe 100644 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Gdx`!Ln7SY zPIKgHP!Mq4-*Q`FmxKF@f7?H>JiK>QM!_&<%LUg{0U{q`yB42sH(u%lwd^h>tx2;Q5_k0EUT1vXPbP0l+XkK6n9e? literal 5037 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 DRwPG| diff --git a/documentation/docs/theme/favicon.svg b/documentation/docs/theme/favicon.svg index d3343977ed..c32ec9595a 100644 --- a/documentation/docs/theme/favicon.svg +++ b/documentation/docs/theme/favicon.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file From effebdf13b02fce15f9cbc50deb6fff96cf3a670 Mon Sep 17 00:00:00 2001 From: Awa Sun Yin Date: Tue, 4 Oct 2022 13:02:17 -0300 Subject: [PATCH 131/197] updated the front page of docs --- documentation/docs/src/README.md | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/documentation/docs/src/README.md b/documentation/docs/src/README.md index 29f18f2b50..4fc4893357 100644 --- a/documentation/docs/src/README.md +++ b/documentation/docs/src/README.md @@ -4,12 +4,34 @@ Welcome to Namada's docs! ## About Namada -[Namada](https://namada.net/) is a sovereign proof-of-stake blockchain, using Tendermint BFT consensus, that enables multi-asset private transfers for any native or non-native asset using a multi-asset shielded pool derived from the Sapling circuit. +[Namada](https://namada.net/) is a Proof-of-Stake layer 1 protocol for asset-agnostic, interchain privacy. Namada is also Anoma's first fractal intance. -To learn more about the protocol, we recommend the following resources: +Key innovations include: +- ZCash-like transfers for any assets (fungible and non-fungible) +- Rewarded usage of privacy as a public good +- Interoperability with Ethereum via a custom bridge with trust-minimisation +- Vertically integrated user interfaces -- [Introducing Namada: Shielded Transfers with Any Assets](https://medium.com/anomanetwork/introducing-namada-shielded-transfers-with-any-assets-dce2e579384c) +To learn more, we recommend: +- Article: [Introducing Namada: Shielded Transfers with Any Assets](https://medium.com/anomanetwork/introducing-namada-shielded-transfers-with-any-assets-dce2e579384c) + +## Overview of features +- PoS with governance to secure and evolve Namada +- Fast-finality BFT with 4 second blocks +- Near-zero fees +- Trustless 2-way Ethereum bridge via IBC implementation on ETH +- IBC bridges to chains that already speak IBC (all Cosmos chains) +- MASP +- Convert Circuit (shielded set rewards) +- A reference interface +- Ledger application + +To learn more about the protocol, we recommend the following in-depth resources: +- Talk at ZK8 [Namada: asset-agnostic interchain privacy](https://youtu.be/5K6YxmZPFkE) - [Namada's specifications](https://specs.namada.net) +- [Codebase](https://github.com/anoma/namada) + +## About this documentation This book is written using [mdBook](https://rust-lang.github.io/mdBook/), the source can be found in the [Namada repository](https://github.com/anoma/namada/tree/main/documentation/docs). From 84d424a2a1240ec7ddf2fc7dd42dd6135ba441c5 Mon Sep 17 00:00:00 2001 From: Awa Sun Yin Date: Tue, 4 Oct 2022 13:16:59 -0300 Subject: [PATCH 132/197] minor edits, ensuring consistency of terms --- documentation/docs/src/README.md | 6 +++--- documentation/docs/src/SUMMARY.md | 12 ++++++------ documentation/docs/src/chapter_1.md | 1 - documentation/docs/src/quick-start.md | 19 +++++++++++-------- documentation/docs/src/user-guide/README.md | 4 ++-- .../docs/src/user-guide/ledger/masp.md | 4 ++-- 6 files changed, 24 insertions(+), 22 deletions(-) delete mode 100644 documentation/docs/src/chapter_1.md diff --git a/documentation/docs/src/README.md b/documentation/docs/src/README.md index 4fc4893357..8085540251 100644 --- a/documentation/docs/src/README.md +++ b/documentation/docs/src/README.md @@ -4,7 +4,7 @@ Welcome to Namada's docs! ## About Namada -[Namada](https://namada.net/) is a Proof-of-Stake layer 1 protocol for asset-agnostic, interchain privacy. Namada is also Anoma's first fractal intance. +[Namada](https://namada.net/) is a Proof-of-Stake layer 1 protocol for asset-agnostic, interchain privacy. Namada is Anoma's first fractal instance and is currently being developed by [Heliax](https://heliax.dev), a public goods lab. Key innovations include: - ZCash-like transfers for any assets (fungible and non-fungible) @@ -16,8 +16,8 @@ To learn more, we recommend: - Article: [Introducing Namada: Shielded Transfers with Any Assets](https://medium.com/anomanetwork/introducing-namada-shielded-transfers-with-any-assets-dce2e579384c) ## Overview of features -- PoS with governance to secure and evolve Namada -- Fast-finality BFT with 4 second blocks +- Proof-of-Stake with governance to secure and evolve Namada +- Fast-finality BFT with 4-second blocks - Near-zero fees - Trustless 2-way Ethereum bridge via IBC implementation on ETH - IBC bridges to chains that already speak IBC (all Cosmos chains) diff --git a/documentation/docs/src/SUMMARY.md b/documentation/docs/src/SUMMARY.md index 2a3acf5cf9..956eba7921 100644 --- a/documentation/docs/src/SUMMARY.md +++ b/documentation/docs/src/SUMMARY.md @@ -1,15 +1,15 @@ # Summary - [Introduction](./README.md) -- [Quick Start](./quick-start.md) -- [User Guide](./user-guide/README.md) - - [Install Namada](./user-guide/install.md) +- [Quickstart](./quick-start.md) +- [User guide](./user-guide/README.md) + - [Installing Namada](./user-guide/install.md) - [Getting started](./user-guide/getting-started.md) - - [Manage a Wallet](./user-guide/wallet.md) + - [Managing a wallet](./user-guide/wallet.md) - [The Ledger](./user-guide/ledger.md) - - [Interact with PoS](./user-guide/ledger/pos.md) + - [Interacting with PoS](./user-guide/ledger/pos.md) - [Governance](./user-guide/ledger/governance.md) - - [Private Transfers](./user-guide/ledger/masp.md) + - [Shielded transfers](./user-guide/ledger/masp.md) - [Genesis validator setup](./user-guide/genesis-validator-setup.md) - [Applying to be a genesis validator](./user-guide/genesis-validator-apply.md) - [Testnets](./testnets/README.md) 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/docs/src/quick-start.md b/documentation/docs/src/quick-start.md index 41a45c7158..ac333c6c07 100644 --- a/documentation/docs/src/quick-start.md +++ b/documentation/docs/src/quick-start.md @@ -1,17 +1,20 @@ -# Quick Start - How to run a validator node +# Quickstart - How to run a validator on Namada? ## About this guide -This guide is aimed at people interested in running a validator node and assumes basic knowledge of the terminal and how commands are used. +This guide is for the ones interested in operating a Namada validator node and assumes basic knowledge of the terminal and how commands are used. -* Comments start with `#`: - `# this is a comment make sure you read them!` -* Sample outputs start with an arrow: - `➜ this is an example command line output useful for comparing` +* Comments start with `#`: -## Install Namada +```# this is a comment make sure you read them!``` -See [the install guide](user-guide/install.md) for details on installing the Namada binaries. Commands in this guide will assume you have the Namada binaries (`namada`, `namadan`, `namadaw`, `namadac`) on your path. +* Sample outputs start with an arrow: + +```➜ this is an example command line output useful for comparing``` + +## Installing Namada + +See [the installation guide](user-guide/install.md) for details on installing the Namada binaries. Commands in this guide will assume you have the Namada binaries (`namada`, `namadan`, `namadaw`, `namadac`) on your path. ## Joining a network diff --git a/documentation/docs/src/user-guide/README.md b/documentation/docs/src/user-guide/README.md index 422df55146..70bf35ef59 100644 --- a/documentation/docs/src/user-guide/README.md +++ b/documentation/docs/src/user-guide/README.md @@ -1,5 +1,5 @@ # User Guide -Welcome to Namada user guide! +Welcome to Namada's user guide! -This guide is intended to help you find how to install, operate and interact with the Namada ledger node, the client and the wallet. +This guide will help you find how to install, operate and interact with the Namada ledger node, the client, and the wallet. diff --git a/documentation/docs/src/user-guide/ledger/masp.md b/documentation/docs/src/user-guide/ledger/masp.md index 0c50995e81..09dc0b7192 100644 --- a/documentation/docs/src/user-guide/ledger/masp.md +++ b/documentation/docs/src/user-guide/ledger/masp.md @@ -1,6 +1,6 @@ -# Private transfers +# Shielded transfers -In Namada, private transfers are enabled by the Multi-Asset Shielded Pool (MASP). The MASP is a zero-knowledge circuit (zk-SNARK) that extends the Zcash Sapling circuit to add support for sending arbitrary assets. All assets in the pool share the same anonymity set, this means that the more transactions are issued to MASP, the stronger are the privacity guarantees. +In Namada, shielded transfers are enabled by the Multi-Asset Shielded Pool (MASP). The MASP is a zero-knowledge circuit (zk-SNARK) that extends the Zcash Sapling circuit to add support for sending arbitrary assets. All assets in the pool share the same anonymity set, this means that the more transactions are issued to MASP, the stronger are the privacity guarantees. ## Using MASP From f3c477c67cbee846b7b89a5ed618634544745ab1 Mon Sep 17 00:00:00 2001 From: Awa Sun Yin <11296013+awasunyin@users.noreply.github.com> Date: Tue, 4 Oct 2022 13:27:16 -0300 Subject: [PATCH 133/197] Update README.md Minor edits on the readme --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 323bc5e9b8..bda7e103c3 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,8 @@ ## Overview [Namada](http://namada.net) is a sovereign proof-of-stake blockchain, using Tendermint BFT -consensus, that enables multi-asset private transfers for any native -or non-native asset using a multi-asset shielded pool derived from -the Sapling circuit. Namada features full IBC protocol support, +consensus, that enables multi-asset shielded transfers for any native +or non-native asset. Namada features full IBC protocol support, a natively integrated Ethereum bridge, a modern proof-of-stake system with automatic reward compounding and cubic slashing, and a stake-weighted governance signalling mechanism. Users of shielded From 842fcd79d42eec51e7da2358bb39b672d9501cdf Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 6 Oct 2022 11:30:05 +0200 Subject: [PATCH 134/197] [feat]: Moved to ics23 v0.7.0. This came with some ibc updates as well --- Cargo.lock | 54 +++- apps/Cargo.toml | 2 +- apps/src/lib/client/tx.rs | 3 +- shared/Cargo.toml | 8 +- shared/src/ledger/ibc/handler.rs | 205 +++++++----- shared/src/ledger/ibc/storage.rs | 14 +- shared/src/ledger/ibc/vp/channel.rs | 269 +++++++++------- shared/src/ledger/ibc/vp/mod.rs | 65 ++-- shared/src/ledger/ibc/vp/packet.rs | 79 +++-- shared/src/ledger/ibc/vp/port.rs | 22 +- shared/src/ledger/ibc/vp/sequence.rs | 10 +- shared/src/ledger/storage/write_log.rs | 1 + shared/src/types/ibc/data.rs | 8 +- tests/Cargo.toml | 2 +- tests/src/vm_host_env/ibc.rs | 37 ++- tests/src/vm_host_env/mod.rs | 6 +- wasm/tx_template/Cargo.lock | 394 +++-------------------- wasm/vp_template/Cargo.lock | 394 +++-------------------- wasm/wasm_source/Cargo.lock | 394 +++-------------------- wasm_for_tests/wasm_source/Cargo.lock | 429 ++++--------------------- 20 files changed, 674 insertions(+), 1722 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eafd05b2ff..85f274524a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,6 +72,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" @@ -952,14 +961,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", ] @@ -1973,9 +1984,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", @@ -2728,10 +2739,23 @@ 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.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 +2781,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", @@ -3076,9 +3100,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" @@ -6289,7 +6313,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?rev=121c79424646cc3e917f8616e549fbddee775a0a#121c79424646cc3e917f8616e549fbddee775a0a" dependencies = [ "blake2b-rs", "borsh", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 65d7d84eed..2b12b5f902 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", rev = "121c79424646cc3e917f8616e549fbddee775a0a", 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/client/tx.rs b/apps/src/lib/client/tx.rs index 1d41ebbc77..e752ee1632 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -4,7 +4,8 @@ use std::env; use std::fs::File; use std::time::Duration; -use async_std::io::{self, WriteExt}; +use async_std::io::prelude::WriteExt; +use async_std::io::{self}; use borsh::BorshSerialize; use itertools::Either::*; use namada::ledger::governance::storage as gov_storage; diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 53ab95f15c..48a5cc1ba7 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 = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false} +ibc-proto = {package = "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", rev = "121c79424646cc3e917f8616e549fbddee775a0a", 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..9e66630cce 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 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..3a43834da5 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; @@ -10,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, @@ -28,6 +28,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; @@ -37,11 +40,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; @@ -51,7 +57,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; @@ -134,7 +140,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,99 +242,135 @@ where tx_data: &[u8], ) -> Result<()> { let prev_channel = self.channel_end_pre(port_channel_id)?; - 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); - 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); - 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); - 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); - 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( @@ -399,7 +441,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 +460,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 +479,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(), @@ -592,15 +634,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 @@ -668,7 +705,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 +788,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 +806,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 +823,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 +840,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(|_| { @@ -816,11 +854,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()), } @@ -838,22 +875,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 { @@ -929,6 +964,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..7e2baf4666 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, @@ -454,7 +453,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 +493,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)) } @@ -533,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(); @@ -696,7 +685,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 +734,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 +1143,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"), @@ -1167,7 +1155,8 @@ 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_ack_channel_event(&msg); + 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![]; @@ -1240,7 +1229,9 @@ 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 event = make_open_confirm_channel_event(&msg, &channel) + .expect("no connection"); write_log.set_ibc_event(event.try_into().unwrap()); let tx_code = vec![]; @@ -1373,14 +1364,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![]; @@ -1436,7 +1423,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 +1489,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, @@ -1519,11 +1506,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"); @@ -1536,7 +1523,7 @@ mod tests { .unwrap(); let msg = MsgAcknowledgement { packet, - acknowledgement: ack, + acknowledgement: ack.into(), proofs, signer: Signer::new("account0"), }; @@ -1610,18 +1597,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()); @@ -1675,7 +1658,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..207727e91c 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::{ @@ -20,7 +21,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}; @@ -59,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 @@ -92,10 +97,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 +127,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 +205,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 +272,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 +302,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 +332,7 @@ where } PortChannelId { port_id: packet.source_port.clone(), - channel_id: packet.source_channel.clone(), + channel_id: packet.source_channel, } } Phase::Recv => { @@ -347,7 +346,7 @@ where } PortChannelId { port_id: packet.destination_port.clone(), - channel_id: packet.destination_channel.clone(), + channel_id: packet.destination_channel, } } }; @@ -364,7 +363,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 +391,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) { @@ -438,13 +437,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( @@ -463,7 +458,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 +479,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 +547,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 +562,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 +584,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 = @@ -608,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)?; } @@ -700,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/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/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 diff --git a/shared/src/types/ibc/data.rs b/shared/src/types/ibc/data.rs index 4a9cdd41c6..4c5d5f2469 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; @@ -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/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/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 13e7bd3882..e1c372a85f 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) } @@ -597,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 { @@ -612,7 +611,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 +636,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(), @@ -659,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 ce547520f7..93db4f6e40 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); @@ -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() diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 4e85d68f2c..1af96f8642 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" @@ -152,27 +161,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" @@ -383,14 +371,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time 0.1.44", + "wasm-bindgen", "winapi", ] @@ -421,6 +411,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" @@ -1042,25 +1038,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" @@ -1105,79 +1082,22 @@ 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" +name = "iana-time-zone" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" 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" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper", - "pin-project-lite", - "tokio", - "tokio-io-timeout", + "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?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "bytes", "derive_more", @@ -1203,22 +1123,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", "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", @@ -1330,9 +1250,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" @@ -1388,9 +1308,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", ] @@ -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" @@ -1616,15 +1514,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" @@ -1708,9 +1597,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -1804,9 +1693,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" @@ -2445,28 +2334,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 +2349,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?rev=121c79424646cc3e917f8616e549fbddee775a0a#121c79424646cc3e917f8616e549fbddee775a0a" dependencies = [ "borsh", "cfg-if 1.0.0", @@ -2755,82 +2628,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 +2637,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 +2649,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.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if 1.0.0", "log", @@ -2930,9 +2664,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", @@ -2941,24 +2675,14 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ - "lazy_static", + "once_cell", "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 +2698,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 +2804,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..220ae684a7 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" @@ -152,27 +161,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" @@ -383,14 +371,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time 0.1.44", + "wasm-bindgen", "winapi", ] @@ -421,6 +411,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" @@ -1042,25 +1038,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" @@ -1105,79 +1082,22 @@ 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" +name = "iana-time-zone" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" 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" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper", - "pin-project-lite", - "tokio", - "tokio-io-timeout", + "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?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "bytes", "derive_more", @@ -1203,22 +1123,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", "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", @@ -1330,9 +1250,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" @@ -1388,9 +1308,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", ] @@ -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" @@ -1616,15 +1514,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" @@ -1708,9 +1597,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -1804,9 +1693,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" @@ -2445,28 +2334,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 +2349,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?rev=121c79424646cc3e917f8616e549fbddee775a0a#121c79424646cc3e917f8616e549fbddee775a0a" dependencies = [ "borsh", "cfg-if 1.0.0", @@ -2755,82 +2628,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 +2637,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 +2649,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.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if 1.0.0", "log", @@ -2930,9 +2664,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", @@ -2941,24 +2675,14 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ - "lazy_static", + "once_cell", "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 +2698,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 +2804,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..ce28732746 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/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.55" @@ -152,27 +161,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" @@ -383,14 +371,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time 0.1.44", + "wasm-bindgen", "winapi", ] @@ -421,6 +411,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" @@ -1042,25 +1038,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" @@ -1105,79 +1082,22 @@ 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" +name = "iana-time-zone" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" 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" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper", - "pin-project-lite", - "tokio", - "tokio-io-timeout", + "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?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "bytes", "derive_more", @@ -1203,22 +1123,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", "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", @@ -1330,9 +1250,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" @@ -1388,9 +1308,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", ] @@ -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" @@ -1642,15 +1540,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" @@ -1734,9 +1623,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -1830,9 +1719,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" @@ -2471,28 +2360,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 +2375,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?rev=121c79424646cc3e917f8616e549fbddee775a0a#121c79424646cc3e917f8616e549fbddee775a0a" dependencies = [ "borsh", "cfg-if 1.0.0", @@ -2781,82 +2654,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 +2663,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 +2675,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.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if 1.0.0", "log", @@ -2956,9 +2690,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", @@ -2967,24 +2701,14 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ - "lazy_static", + "once_cell", "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 +2724,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 +2819,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..294766e714 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" @@ -152,27 +161,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" @@ -383,14 +371,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time 0.1.44", + "wasm-bindgen", "winapi", ] @@ -421,6 +411,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" @@ -992,7 +988,7 @@ checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1043,25 +1039,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" @@ -1115,79 +1092,22 @@ 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" +name = "iana-time-zone" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" 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" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper", - "pin-project-lite", - "tokio", - "tokio-io-timeout", + "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?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "bytes", "derive_more", @@ -1213,22 +1133,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", "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", @@ -1340,9 +1260,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" @@ -1357,7 +1277,7 @@ dependencies = [ [[package]] name = "libsecp256k1" version = "0.7.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", @@ -1373,7 +1293,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1383,7 +1303,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1391,16 +1311,16 @@ 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", ] [[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", ] @@ -1490,29 +1410,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" @@ -1527,7 +1424,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1576,7 +1473,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1584,7 +1481,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -1593,7 +1490,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1611,7 +1508,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1619,7 +1516,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -1629,7 +1526,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1637,7 +1534,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "getrandom", @@ -1648,15 +1545,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" @@ -1740,9 +1628,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.10.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -1836,9 +1724,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" @@ -2477,28 +2365,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 +2380,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?rev=121c79424646cc3e917f8616e549fbddee775a0a#121c79424646cc3e917f8616e549fbddee775a0a" dependencies = [ "borsh", "cfg-if 1.0.0", @@ -2751,7 +2623,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 +2659,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 +2668,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 +2680,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.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if 1.0.0", "log", @@ -2962,9 +2695,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", @@ -2973,24 +2706,14 @@ 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", + "once_cell", "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 +2729,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 +2824,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 206da9f1cc6e4af26ca8537873f9507d3e2d53a4 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 6 Oct 2022 13:18:17 +0200 Subject: [PATCH 135/197] 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 d6c4da2b78b37b7babd2f4c6c2dcd23e34e3e7a2 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 6 Oct 2022 17:12:15 +0200 Subject: [PATCH 136/197] ci: update wasm version --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index e2e72820c8..f640e75cc6 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -27,7 +27,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 dd81734ecd79872c7c87b61efd7184051883ad85 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 6 Oct 2022 19:56:41 +0200 Subject: [PATCH 137/197] 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 b6b07346be..0d0112924e 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 8c45f3296eecdeb64b77509ee00145152285c45b Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Fri, 7 Oct 2022 11:21:08 +0200 Subject: [PATCH 138/197] Update shared/src/ledger/ibc/handler.rs Co-authored-by: Tomas Zemanovic --- shared/src/ledger/ibc/handler.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index 9e66630cce..88263d3e3c 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -1030,9 +1030,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, ) } From 00956dfc592462b59d2ff62b6fab18cc1269c62d Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 7 Oct 2022 12:03:58 +0200 Subject: [PATCH 139/197] [fix]: Fixed some array concatenations --- shared/src/ledger/ibc/handler.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index 88263d3e3c..cbfed073b4 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, }; @@ -1110,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() } From 8605864726d51a5f260be33d4f6bd2ed4f64eb6f Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Fri, 7 Oct 2022 17:50:39 +0200 Subject: [PATCH 140/197] 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 95a09b755b18389c6af0bc9f72e3a2ebea40b6fb Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 11 Oct 2022 15:03:01 +0200 Subject: [PATCH 141/197] Update shared/src/ledger/ibc/handler.rs Co-authored-by: Tomas Zemanovic --- shared/src/ledger/ibc/handler.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index cbfed073b4..5200c34541 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -1109,17 +1109,17 @@ pub fn packet_from_message( /// Returns a commitment from the given packet pub fn commitment(packet: &Packet) -> PacketCommitment { - let input = packet - .timeout_timestamp - .nanoseconds() - .to_be_bytes() - .to_vec(); + let timeout = packet.timeout_timestamp.nanoseconds().to_be_bytes(); let revision_number = packet.timeout_height.revision_number.to_be_bytes(); - let input = [input.as_slice(), revision_number.as_slice()].concat(); let revision_height = packet.timeout_height.revision_height.to_be_bytes(); - let input = [input.as_slice(), revision_height.as_slice()].concat(); let data = sha2::Sha256::digest(&packet.data); - let input = [input.as_slice(), data.as_slice()].concat(); + let input = [ + &timeout, + &revision_number, + &revision_height, + data.as_slice(), + ] + .concat(); sha2::Sha256::digest(&input).to_vec().into() } From 5b0ade262de5b8061939dd0b614e79c3e04d2cfa Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Wed, 12 Oct 2022 11:34:17 +0200 Subject: [PATCH 142/197] Add install-release target `make install-release` installs the Namada binaries without the tendermint binaries. --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 31e0785b0d..e0b9c3e23f 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 +install-release: + ANOMA_DEV=false $(cargo) install --path ./apps --locked + check-release: ANOMA_DEV=false $(cargo) check --release --package namada_apps From b3e8800597e71c0b53a902648a98fb992f7677d7 Mon Sep 17 00:00:00 2001 From: Awa Sun Yin <11296013+awasunyin@users.noreply.github.com> Date: Wed, 12 Oct 2022 17:15:55 +0200 Subject: [PATCH 143/197] Update .gitignore Co-authored-by: Tomas Zemanovic --- .gitignore | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 381262dbdf..4718c258b2 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,4 @@ target/ wasm/*.wasm # app version string file -/apps/version.rs - -# for annoying contributors using mac -*.DS_Store \ No newline at end of file +/apps/version.rs \ No newline at end of file From d2aee5d80ef21f67bfe095aa123bdca2ac3affa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 11:47:17 +0200 Subject: [PATCH 144/197] move unreleased changelogs accidentally added to v0.7.1 --- .../improvements/1093-unify-native-and-wasm-vp.md | 0 .../improvements/1138-change-wallet-bihashmap.md | 0 .../improvements/1148-allow-absolute-wasm-dir.md | 0 .../improvements/1159-anomac-download-wasms.md | 0 .../improvements/1161-anomaw-address-find.md | 0 .../{v0.7.1 => unreleased}/improvements/1168-pbkdf-iterations.md | 0 .../improvements/1176-genesis-config-error.md | 0 .../improvements/1231-refactor-ledger-run-with-cleanup.md | 0 .../improvements/1248-remove-evidence-params.md | 0 .../improvements/240-host-env-vp-write-check.md | 0 .../{v0.7.1 => unreleased}/improvements/318-refactor-pos-vp.md | 0 .../improvements/324-common-read-storage-trait.md | 0 .../improvements/331-common-write-storage-trait.md | 0 .../improvements/334-refactor-storage-read-write.md | 0 .../improvements/335-refactor-storage-prefix-iter.md | 0 .../improvements/337-wasm-cargo-target-dir.md | 0 .../improvements/380-vp-env-pre-post-via-storage-api.md | 0 .../{v0.7.1 => unreleased}/improvements/409-sorted-prefix-iter.md | 0 .../improvements/465-vp-tx-env-conrete-error.md | 0 .../{v0.7.1 => unreleased}/testing/1221-e2e-keep-temp-fix.md | 0 .changelog/{v0.7.1 => unreleased}/testing/462-pos-tx-tests.md | 0 21 files changed, 0 insertions(+), 0 deletions(-) rename .changelog/{v0.7.1 => unreleased}/improvements/1093-unify-native-and-wasm-vp.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/1138-change-wallet-bihashmap.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/1148-allow-absolute-wasm-dir.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/1159-anomac-download-wasms.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/1161-anomaw-address-find.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/1168-pbkdf-iterations.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/1176-genesis-config-error.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/1231-refactor-ledger-run-with-cleanup.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/1248-remove-evidence-params.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/240-host-env-vp-write-check.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/318-refactor-pos-vp.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/324-common-read-storage-trait.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/331-common-write-storage-trait.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/334-refactor-storage-read-write.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/335-refactor-storage-prefix-iter.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/337-wasm-cargo-target-dir.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/380-vp-env-pre-post-via-storage-api.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/409-sorted-prefix-iter.md (100%) rename .changelog/{v0.7.1 => unreleased}/improvements/465-vp-tx-env-conrete-error.md (100%) rename .changelog/{v0.7.1 => unreleased}/testing/1221-e2e-keep-temp-fix.md (100%) rename .changelog/{v0.7.1 => unreleased}/testing/462-pos-tx-tests.md (100%) diff --git a/.changelog/v0.7.1/improvements/1093-unify-native-and-wasm-vp.md b/.changelog/unreleased/improvements/1093-unify-native-and-wasm-vp.md similarity index 100% rename from .changelog/v0.7.1/improvements/1093-unify-native-and-wasm-vp.md rename to .changelog/unreleased/improvements/1093-unify-native-and-wasm-vp.md diff --git a/.changelog/v0.7.1/improvements/1138-change-wallet-bihashmap.md b/.changelog/unreleased/improvements/1138-change-wallet-bihashmap.md similarity index 100% rename from .changelog/v0.7.1/improvements/1138-change-wallet-bihashmap.md rename to .changelog/unreleased/improvements/1138-change-wallet-bihashmap.md diff --git a/.changelog/v0.7.1/improvements/1148-allow-absolute-wasm-dir.md b/.changelog/unreleased/improvements/1148-allow-absolute-wasm-dir.md similarity index 100% rename from .changelog/v0.7.1/improvements/1148-allow-absolute-wasm-dir.md rename to .changelog/unreleased/improvements/1148-allow-absolute-wasm-dir.md diff --git a/.changelog/v0.7.1/improvements/1159-anomac-download-wasms.md b/.changelog/unreleased/improvements/1159-anomac-download-wasms.md similarity index 100% rename from .changelog/v0.7.1/improvements/1159-anomac-download-wasms.md rename to .changelog/unreleased/improvements/1159-anomac-download-wasms.md diff --git a/.changelog/v0.7.1/improvements/1161-anomaw-address-find.md b/.changelog/unreleased/improvements/1161-anomaw-address-find.md similarity index 100% rename from .changelog/v0.7.1/improvements/1161-anomaw-address-find.md rename to .changelog/unreleased/improvements/1161-anomaw-address-find.md diff --git a/.changelog/v0.7.1/improvements/1168-pbkdf-iterations.md b/.changelog/unreleased/improvements/1168-pbkdf-iterations.md similarity index 100% rename from .changelog/v0.7.1/improvements/1168-pbkdf-iterations.md rename to .changelog/unreleased/improvements/1168-pbkdf-iterations.md diff --git a/.changelog/v0.7.1/improvements/1176-genesis-config-error.md b/.changelog/unreleased/improvements/1176-genesis-config-error.md similarity index 100% rename from .changelog/v0.7.1/improvements/1176-genesis-config-error.md rename to .changelog/unreleased/improvements/1176-genesis-config-error.md diff --git a/.changelog/v0.7.1/improvements/1231-refactor-ledger-run-with-cleanup.md b/.changelog/unreleased/improvements/1231-refactor-ledger-run-with-cleanup.md similarity index 100% rename from .changelog/v0.7.1/improvements/1231-refactor-ledger-run-with-cleanup.md rename to .changelog/unreleased/improvements/1231-refactor-ledger-run-with-cleanup.md diff --git a/.changelog/v0.7.1/improvements/1248-remove-evidence-params.md b/.changelog/unreleased/improvements/1248-remove-evidence-params.md similarity index 100% rename from .changelog/v0.7.1/improvements/1248-remove-evidence-params.md rename to .changelog/unreleased/improvements/1248-remove-evidence-params.md diff --git a/.changelog/v0.7.1/improvements/240-host-env-vp-write-check.md b/.changelog/unreleased/improvements/240-host-env-vp-write-check.md similarity index 100% rename from .changelog/v0.7.1/improvements/240-host-env-vp-write-check.md rename to .changelog/unreleased/improvements/240-host-env-vp-write-check.md diff --git a/.changelog/v0.7.1/improvements/318-refactor-pos-vp.md b/.changelog/unreleased/improvements/318-refactor-pos-vp.md similarity index 100% rename from .changelog/v0.7.1/improvements/318-refactor-pos-vp.md rename to .changelog/unreleased/improvements/318-refactor-pos-vp.md diff --git a/.changelog/v0.7.1/improvements/324-common-read-storage-trait.md b/.changelog/unreleased/improvements/324-common-read-storage-trait.md similarity index 100% rename from .changelog/v0.7.1/improvements/324-common-read-storage-trait.md rename to .changelog/unreleased/improvements/324-common-read-storage-trait.md diff --git a/.changelog/v0.7.1/improvements/331-common-write-storage-trait.md b/.changelog/unreleased/improvements/331-common-write-storage-trait.md similarity index 100% rename from .changelog/v0.7.1/improvements/331-common-write-storage-trait.md rename to .changelog/unreleased/improvements/331-common-write-storage-trait.md diff --git a/.changelog/v0.7.1/improvements/334-refactor-storage-read-write.md b/.changelog/unreleased/improvements/334-refactor-storage-read-write.md similarity index 100% rename from .changelog/v0.7.1/improvements/334-refactor-storage-read-write.md rename to .changelog/unreleased/improvements/334-refactor-storage-read-write.md diff --git a/.changelog/v0.7.1/improvements/335-refactor-storage-prefix-iter.md b/.changelog/unreleased/improvements/335-refactor-storage-prefix-iter.md similarity index 100% rename from .changelog/v0.7.1/improvements/335-refactor-storage-prefix-iter.md rename to .changelog/unreleased/improvements/335-refactor-storage-prefix-iter.md diff --git a/.changelog/v0.7.1/improvements/337-wasm-cargo-target-dir.md b/.changelog/unreleased/improvements/337-wasm-cargo-target-dir.md similarity index 100% rename from .changelog/v0.7.1/improvements/337-wasm-cargo-target-dir.md rename to .changelog/unreleased/improvements/337-wasm-cargo-target-dir.md diff --git a/.changelog/v0.7.1/improvements/380-vp-env-pre-post-via-storage-api.md b/.changelog/unreleased/improvements/380-vp-env-pre-post-via-storage-api.md similarity index 100% rename from .changelog/v0.7.1/improvements/380-vp-env-pre-post-via-storage-api.md rename to .changelog/unreleased/improvements/380-vp-env-pre-post-via-storage-api.md diff --git a/.changelog/v0.7.1/improvements/409-sorted-prefix-iter.md b/.changelog/unreleased/improvements/409-sorted-prefix-iter.md similarity index 100% rename from .changelog/v0.7.1/improvements/409-sorted-prefix-iter.md rename to .changelog/unreleased/improvements/409-sorted-prefix-iter.md diff --git a/.changelog/v0.7.1/improvements/465-vp-tx-env-conrete-error.md b/.changelog/unreleased/improvements/465-vp-tx-env-conrete-error.md similarity index 100% rename from .changelog/v0.7.1/improvements/465-vp-tx-env-conrete-error.md rename to .changelog/unreleased/improvements/465-vp-tx-env-conrete-error.md diff --git a/.changelog/v0.7.1/testing/1221-e2e-keep-temp-fix.md b/.changelog/unreleased/testing/1221-e2e-keep-temp-fix.md similarity index 100% rename from .changelog/v0.7.1/testing/1221-e2e-keep-temp-fix.md rename to .changelog/unreleased/testing/1221-e2e-keep-temp-fix.md diff --git a/.changelog/v0.7.1/testing/462-pos-tx-tests.md b/.changelog/unreleased/testing/462-pos-tx-tests.md similarity index 100% rename from .changelog/v0.7.1/testing/462-pos-tx-tests.md rename to .changelog/unreleased/testing/462-pos-tx-tests.md From 31e5df0b5581bf0dcd3d40ab8a2ce64f58bb020c Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 13 Oct 2022 12:02:10 +0200 Subject: [PATCH 145/197] ci: upload docs after build --- .github/workflows/docs.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3f5e5e5159..0ba9f39b98 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -117,6 +117,13 @@ jobs: cd ${{ matrix.make.folder }} && mdbook-admonish install - name: ${{ matrix.make.name }} run: ${{ matrix.make.command }} + - name: Zip doc folder + run: tar -cvf ${{ matrix.make.bucket }}.tar ${{ matrix.make.folder }}/book + - name: Upload rendered docs + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.make.bucket }}-${{ github.sha }}.tar + path: ${{ matrix.make.bucket }}.tar - name: Publish docs if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} run: aws s3 sync ${{ matrix.make.folder }}/book/html/ s3://${{ matrix.make.bucket }} --region eu-west-1 --delete From f989e881e75b7c2e06217263a00963b86e72cd79 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Fri, 9 Sep 2022 09:57:17 +0200 Subject: [PATCH 146/197] fix: governance docs latex --- .../specs/src/base-ledger/governance.md | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/documentation/specs/src/base-ledger/governance.md b/documentation/specs/src/base-ledger/governance.md index 591558fcc0..8ccff4c474 100644 --- a/documentation/specs/src/base-ledger/governance.md +++ b/documentation/specs/src/base-ledger/governance.md @@ -16,18 +16,18 @@ The second internal address holds the funds of rejected proposals. 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/epoch/$id: 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. -- `/$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. +- `/\$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 @@ -54,16 +54,15 @@ Each proposal will be stored in a sub-key under the internal proposal address. T /\$GovernanceAddress/max_proposal_content_size: u64 /\$GovernanceAddress/min_proposal_grace_epochs: u64 /\$GovernanceAddress/pending/\$proposal_id: u64 - ``` -`counter` is used to assign a unique, incremental ID to each proposal.\ -`min_proposal_fund` represents the minimum amount of locked tokens to submit a proposal.\ -`max_proposal_code_size` is the maximum allowed size (in bytes) of the proposal wasm code.\ -`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 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. +- `counter` is used to assign a unique, incremental ID to each proposal.\ +- `min_proposal_fund` represents the minimum amount of locked tokens to submit a proposal.\ +- `max_proposal_code_size` is the maximum allowed size (in bytes) of the proposal wasm code.\ +- `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 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: @@ -72,7 +71,7 @@ The governance machinery also relies on a subkey stored under the `NAM` token ad ``` This is to leverage the `NAM` VP to check that the funds were correctly locked. -The governance subkey, `/\$GovernanceAddress/proposal/\$id/funds` will be used after the tally step to know the exact amount of tokens to refund or move to SlashFund. +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 following: @@ -236,4 +235,4 @@ Same mechanism as [on chain](#tally) tally but instead of reading the data from ## Interfaces - Ledger CLI -- Wallet \ No newline at end of file +- Wallet From d75a5a75e64c35820e4dbb07f906a9b4ff7dee7c Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 13 Oct 2022 14:17:29 +0200 Subject: [PATCH 147/197] Adds missing excape chars --- .../specs/src/base-ledger/governance.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/documentation/specs/src/base-ledger/governance.md b/documentation/specs/src/base-ledger/governance.md index 8ccff4c474..40c7281af9 100644 --- a/documentation/specs/src/base-ledger/governance.md +++ b/documentation/specs/src/base-ledger/governance.md @@ -16,18 +16,18 @@ The second internal address holds the funds of rejected proposals. 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/epoch/$id: 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. -- `/\$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. +- `/\$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 @@ -71,7 +71,7 @@ The governance machinery also relies on a subkey stored under the `NAM` token ad ``` This is to leverage the `NAM` VP to check that the funds were correctly locked. -The governance subkey, `/$GovernanceAddress/proposal/$id/funds` will be used after the tally step to know the exact amount of tokens to refund or move to Treasury. +The governance subkey, `/\$GovernanceAddress/proposal/\$id/funds` will be used after the tally step to know the exact amount of tokens to refund or move to 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 following: @@ -193,7 +193,7 @@ The funds will be stored under: ``` ### SlashFundAddress VP -The slash_fund validity predicate will approve a transfer only if the transfer has been made by the protocol (by checking the existence of `/$GovernanceAddress/pending/$proposal_id` storage key) +The slash_fund validity predicate will approve a transfer only if the transfer has been made by the protocol (by checking the existence of `/\$GovernanceAddress/pending/\$proposal_id` storage key) It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/main/shared/src/ledger/slash_fund/mod.rs#L70). From 1ec12c4cefbc88748f77cf8f1a8c1484ad09fb62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 14:49:30 +0200 Subject: [PATCH 148/197] wasm/tx_bond: add test seed which makes it fail --- wasm/wasm_source/proptest-regressions/tx_bond.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 wasm/wasm_source/proptest-regressions/tx_bond.txt diff --git a/wasm/wasm_source/proptest-regressions/tx_bond.txt b/wasm/wasm_source/proptest-regressions/tx_bond.txt new file mode 100644 index 0000000000..3a88756618 --- /dev/null +++ b/wasm/wasm_source/proptest-regressions/tx_bond.txt @@ -0,0 +1 @@ +cc e54347c5114ef29538127ba9ad68d1572af839ec63c015318fc0827818853a22 From d04092346e1202fc957be4995ed50f01ad0a759c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 14:50:24 +0200 Subject: [PATCH 149/197] wasm/tx_bond: fix delegation detection to compare source and validator --- wasm/wasm_source/src/tx_bond.rs | 118 +++++++++++++++----------------- 1 file changed, 56 insertions(+), 62 deletions(-) diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index e8a85800f4..ac0c4560ea 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -66,6 +66,8 @@ mod tests { key: key::common::SecretKey, pos_params: PosParams, ) -> TxResult { + let is_delegation = matches!( + &bond.source, Some(source) if *source != bond.validator); let staking_reward_address = address::testing::established_address_1(); let consensus_key = key::testing::keypair_1().ref_to(); let staking_reward_key = key::testing::keypair_2().ref_to(); @@ -154,71 +156,63 @@ mod tests { source: bond_src, }; let bonds_post = ctx().read_bond(&bond_id)?.unwrap(); - match &bond.source { - Some(_) => { - // This bond was a delegation - for epoch in 0..pos_params.pipeline_len { - let bond: Option> = - bonds_post.get(epoch); - assert!( - bond.is_none(), - "Delegation before pipeline offset should be empty - \ - checking epoch {epoch}" - ); - } - for epoch in pos_params.pipeline_len..=pos_params.unbonding_len - { - let start_epoch = - namada_tx_prelude::proof_of_stake::types::Epoch::from( - pos_params.pipeline_len, - ); - let expected_bond = - HashMap::from_iter([(start_epoch, bond.amount)]); - let bond: Bond = - bonds_post.get(epoch).unwrap(); - assert_eq!( - bond.pos_deltas, expected_bond, - "Delegation at and after pipeline offset should be \ - equal to the bonded amount - checking epoch {epoch}" - ); - } + + if is_delegation { + // A delegation is applied at pipeline offset + for epoch in 0..pos_params.pipeline_len { + let bond: Option> = bonds_post.get(epoch); + assert!( + bond.is_none(), + "Delegation before pipeline offset should be empty - \ + checking epoch {epoch}, got {bond:#?}" + ); } - None => { - let genesis_epoch = - namada_tx_prelude::proof_of_stake::types::Epoch::from(0); - // It was a self-bond - for epoch in 0..pos_params.pipeline_len { - let expected_bond = - HashMap::from_iter([(genesis_epoch, initial_stake)]); - let bond: Bond = - bonds_post.get(epoch).expect( - "Genesis validator should already have self-bond", - ); - assert_eq!( - bond.pos_deltas, expected_bond, - "Delegation before pipeline offset should be equal to \ - the genesis initial stake - checking epoch {epoch}" + for epoch in pos_params.pipeline_len..=pos_params.unbonding_len { + let start_epoch = + namada_tx_prelude::proof_of_stake::types::Epoch::from( + pos_params.pipeline_len, ); - } - for epoch in pos_params.pipeline_len..=pos_params.unbonding_len - { - let start_epoch = - namada_tx_prelude::proof_of_stake::types::Epoch::from( - pos_params.pipeline_len, - ); - let expected_bond = HashMap::from_iter([ - (genesis_epoch, initial_stake), - (start_epoch, bond.amount), - ]); - let bond: Bond = - bonds_post.get(epoch).unwrap(); - assert_eq!( - bond.pos_deltas, expected_bond, - "Delegation at and after pipeline offset should \ - contain genesis stake and the bonded amount - \ - checking epoch {epoch}" + let expected_bond = + HashMap::from_iter([(start_epoch, bond.amount)]); + let bond: Bond = bonds_post.get(epoch).unwrap(); + assert_eq!( + bond.pos_deltas, expected_bond, + "Delegation at and after pipeline offset should be equal \ + to the bonded amount - checking epoch {epoch}" + ); + } + } else { + let genesis_epoch = + namada_tx_prelude::proof_of_stake::types::Epoch::from(0); + // It was a self-bond + for epoch in 0..pos_params.pipeline_len { + let expected_bond = + HashMap::from_iter([(genesis_epoch, initial_stake)]); + let bond: Bond = bonds_post + .get(epoch) + .expect("Genesis validator should already have self-bond"); + assert_eq!( + bond.pos_deltas, expected_bond, + "Delegation before pipeline offset should be equal to the \ + genesis initial stake - checking epoch {epoch}" + ); + } + for epoch in pos_params.pipeline_len..=pos_params.unbonding_len { + let start_epoch = + namada_tx_prelude::proof_of_stake::types::Epoch::from( + pos_params.pipeline_len, ); - } + let expected_bond = HashMap::from_iter([ + (genesis_epoch, initial_stake), + (start_epoch, bond.amount), + ]); + let bond: Bond = bonds_post.get(epoch).unwrap(); + assert_eq!( + bond.pos_deltas, expected_bond, + "Delegation at and after pipeline offset should contain \ + genesis stake and the bonded amount - checking epoch \ + {epoch}" + ); } } From 3136a102937eba8be764f54adbb8e392c9c93526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 15:03:14 +0200 Subject: [PATCH 150/197] changelog: add #590 --- .changelog/unreleased/testing/590-fix-tx-bond-test-condition.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/testing/590-fix-tx-bond-test-condition.md diff --git a/.changelog/unreleased/testing/590-fix-tx-bond-test-condition.md b/.changelog/unreleased/testing/590-fix-tx-bond-test-condition.md new file mode 100644 index 0000000000..80435089b2 --- /dev/null +++ b/.changelog/unreleased/testing/590-fix-tx-bond-test-condition.md @@ -0,0 +1,2 @@ +- Fix a condition in tx_bond test that causes a false negative result + ([#590](https://github.com/anoma/namada/pull/590)) \ No newline at end of file From 6ea6bafc3c5893cc7b551fa035dd1d538a316df6 Mon Sep 17 00:00:00 2001 From: Tomas Zemanovic Date: Thu, 13 Oct 2022 18:56:45 +0200 Subject: [PATCH 151/197] Apply suggestions from code review --- wasm/wasm_source/src/tx_bond.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index ac0c4560ea..6718988657 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -193,7 +193,7 @@ mod tests { .expect("Genesis validator should already have self-bond"); assert_eq!( bond.pos_deltas, expected_bond, - "Delegation before pipeline offset should be equal to the \ + "Self-bond before pipeline offset should be equal to the \ genesis initial stake - checking epoch {epoch}" ); } @@ -209,7 +209,7 @@ mod tests { let bond: Bond = bonds_post.get(epoch).unwrap(); assert_eq!( bond.pos_deltas, expected_bond, - "Delegation at and after pipeline offset should contain \ + "Self-bond at and after pipeline offset should contain \ genesis stake and the bonded amount - checking epoch \ {epoch}" ); From d8fea18a5f7e5a4e6804cbb53c0f38d8fddda0d1 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 152/197] 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 422bbe4d5e..782ed17e27 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -631,7 +631,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()?; @@ -992,12 +992,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 2fa19cd9fe112b7627ea2fb138751654de1e6979 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:27:28 +0200 Subject: [PATCH 153/197] changelog: add #594 --- .changelog/unreleased/bug-fixes/594-fix-pred-epoch-height.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/594-fix-pred-epoch-height.md diff --git a/.changelog/unreleased/bug-fixes/594-fix-pred-epoch-height.md b/.changelog/unreleased/bug-fixes/594-fix-pred-epoch-height.md new file mode 100644 index 0000000000..03b7229bcc --- /dev/null +++ b/.changelog/unreleased/bug-fixes/594-fix-pred-epoch-height.md @@ -0,0 +1,2 @@ +- Fix the value recorded for epoch start block height. + ([#594](https://github.com/anoma/namada/pull/594)) \ No newline at end of file From 23018734b9930b4959092990f8fe1625038ad9a8 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 14 Oct 2022 13:11:10 +0100 Subject: [PATCH 154/197] Fix TransactionGasExceededed typo --- shared/src/ledger/gas.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/gas.rs b/shared/src/ledger/gas.rs index 739885971e..c7da7b132c 100644 --- a/shared/src/ledger/gas.rs +++ b/shared/src/ledger/gas.rs @@ -10,7 +10,7 @@ use thiserror::Error; #[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum Error { #[error("Transaction gas limit exceeded")] - TransactionGasExceedededError, + TransactionGasExceededError, #[error("Block gas limit exceeded")] BlockGasExceeded, #[error("Overflow during gas operations")] @@ -69,7 +69,7 @@ impl BlockGasMeter { .ok_or(Error::GasOverflow)?; if self.transaction_gas > TRANSACTION_GAS_LIMIT { - return Err(Error::TransactionGasExceedededError); + return Err(Error::TransactionGasExceededError); } Ok(()) } @@ -148,7 +148,7 @@ impl VpGasMeter { .ok_or(Error::GasOverflow)?; if current_total > TRANSACTION_GAS_LIMIT { - return Err(Error::TransactionGasExceedededError); + return Err(Error::TransactionGasExceededError); } Ok(()) } @@ -258,7 +258,7 @@ mod tests { meter .add(TRANSACTION_GAS_LIMIT) .expect_err("unexpectedly succeeded"), - Error::TransactionGasExceedededError + Error::TransactionGasExceededError ); } @@ -279,7 +279,7 @@ mod tests { meter .add(TRANSACTION_GAS_LIMIT + 1) .expect_err("unexpectedly succeeded"), - Error::TransactionGasExceedededError + Error::TransactionGasExceededError ); } From 660e733765fb13c68d6e5be88e29a1b754cd4d69 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 14 Oct 2022 13:14:38 +0100 Subject: [PATCH 155/197] Add changelog --- .../improvements/605-fix-transaction-gas-exceededed-typo.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/unreleased/improvements/605-fix-transaction-gas-exceededed-typo.md diff --git a/.changelog/unreleased/improvements/605-fix-transaction-gas-exceededed-typo.md b/.changelog/unreleased/improvements/605-fix-transaction-gas-exceededed-typo.md new file mode 100644 index 0000000000..46134ffe7a --- /dev/null +++ b/.changelog/unreleased/improvements/605-fix-transaction-gas-exceededed-typo.md @@ -0,0 +1 @@ +- Fix a typo in an error ([#605](https://github.com/anoma/namada/issues/605)) \ No newline at end of file From fa0e60818f7e5743f637bd07fd236c8d6cf65f01 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 14 Oct 2022 13:17:59 +0100 Subject: [PATCH 156/197] Add changelog --- .../unreleased/improvements/518-mdbook-admonish-for-specs.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/518-mdbook-admonish-for-specs.md diff --git a/.changelog/unreleased/improvements/518-mdbook-admonish-for-specs.md b/.changelog/unreleased/improvements/518-mdbook-admonish-for-specs.md new file mode 100644 index 0000000000..6fa947fbb5 --- /dev/null +++ b/.changelog/unreleased/improvements/518-mdbook-admonish-for-specs.md @@ -0,0 +1,2 @@ +- Enable mdbook-admonish for the specs + ([#518](https://github.com/anoma/namada/issues/518)) \ No newline at end of file From 6feb873913eabdb53152e6bea0ade7515b5adae0 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Fri, 14 Oct 2022 10:50:40 -0400 Subject: [PATCH 157/197] wasm: repair wasm checksums --- wasm/checksums.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 53e7bcdd0d..2dee748987 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.ce4c3f3ef4cc0107c95ad2dfe51221f86eb119ed846eda08c779a180f690045c.wasm", + "tx_bond.wasm": "tx_bond.cc16670ccd2b4a0de0e20de051196dda9058bab885b93c948e9d5dc9fba65e76.wasm", "tx_from_intent.wasm": "tx_from_intent.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_ibc.wasm": "tx_ibc.4c021c92ba92a22e4aa38eae9ddba7d83e25a28f12501eaebd23c873b3e305de.wasm", - "tx_init_account.wasm": "tx_init_account.04ab6caa1826386516bb278e9e055ff0b0f1d10f2c63848ef14cf817d3fae05d.wasm", - "tx_init_nft.wasm": "tx_init_nft.9a2c100853f5e2a9c00559a6674faea08c9e3acfa733331f44cb6107583c2520.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.ec5d0d9e264a85f5d0b1dc47f6d125c57423e14dabb8b00921e35973d2cd0bd3.wasm", - "tx_init_validator.wasm": "tx_init_validator.5e500b8f654aa425ad8dd1b84ba998b68785bd439e52743a716b895da3c6ef63.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.7de6fcd1ab8cf9ef911dcb3846c6c45fe4bb3eb23072e7f888f8c337e0cc75bc.wasm", - "tx_transfer.wasm": "tx_transfer.d0852a8bc49de77205f99607d298765025bf463fb7fef1bf894dee82542a8d4d.wasm", - "tx_unbond.wasm": "tx_unbond.158e931aa691e88ba231019692f8e0a69cb38daff2160825b07dff6ab9f473fb.wasm", - "tx_update_vp.wasm": "tx_update_vp.cbf9af660eebf1ac49a04305a1080c21e025cccf388348c590ffbd19eb3a46b2.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.999287e648d6590a76f6cbb6feafd7780bdcc6998ef064e12f326bab7f8e5964.wasm", - "tx_withdraw.wasm": "tx_withdraw.f9a71e28b50d0e166a2bdf4c1d3daa03da88c6435bb1da7bb1f0129b717624a9.wasm", - "vp_nft.wasm": "vp_nft.1c18d8df4ab9bdaeed2b5c1ec40cf88def441f0bc4efd7305ea3b1efbbf92928.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.897edd60700efa3c91362c582d309dde34ed27b278d83337b15c3f522ea38833.wasm", - "vp_token.wasm": "vp_token.e3a5c4b6c276987d220b81505ac7609ace4794c250c2e9d45d26bcb08cd15fe0.wasm", - "vp_user.wasm": "vp_user.0d2872530305f5853180155bf48f4b9c728487d39ed2f09fe504971d4db92a98.wasm" + "tx_ibc.wasm": "tx_ibc.bc1783aaa0fcb587e3963a03b7629fed3c59ef7dbcd099ce3101260b4fafb758.wasm", + "tx_init_account.wasm": "tx_init_account.8a70c8722e31af7d0875d2f13279da238269fa91053d0e9413c20fc9cd8e8662.wasm", + "tx_init_nft.wasm": "tx_init_nft.28872d99008b7b14593355c6ab6d62108b61a3d2956a0581d80c3d5816b13d4a.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.f704144e1bb74ebe9e937c4e6395148bfd31eaa74608f105f3c0496f62be3077.wasm", + "tx_init_validator.wasm": "tx_init_validator.dddaaf19e7a53eaa8b2b1376cdfdfaf3f776c4e5305cab0d4d2050d520853790.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.272053e50688f04d7a258bc324c704c49a4833839b4e37dd6f691c80b38012e2.wasm", + "tx_transfer.wasm": "tx_transfer.baa846ba31c9e6f93c5d2facf4cec50518990924d1aa71216a6b4e48557f00b4.wasm", + "tx_unbond.wasm": "tx_unbond.86132bec595dfa726445a53a25900fccc2ab6457241d9ddf2ca3c4a8ec799f19.wasm", + "tx_update_vp.wasm": "tx_update_vp.28050dd60d9297b360ade93213a3b7677d424b703e13d3732c767e25da4bd6a1.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.3a698cb7f26d5c96c97efad860a33d123b439c071cb488d6f52be3dc0c170848.wasm", + "tx_withdraw.wasm": "tx_withdraw.600bfe69fc3fe60e96acb38f6cd5f69b0d4f182cb4eebe804cbf2a782e91d1ca.wasm", + "vp_nft.wasm": "vp_nft.736aef15807d2c8233bafbc842f3b87df0df48b1e319ce37358b73d21b73814b.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.ab70bd5fd3c5a6756f947ee86c4518611be7e6ab23b504a7f03681235f70d699.wasm", + "vp_token.wasm": "vp_token.6a8837598c2e9c380c4da1be54c4d18852213457d71f9e2d5fd4b13a1d4d8050.wasm", + "vp_user.wasm": "vp_user.86c1ca9c00248000e1bf071b86ad221cb1d1c4e75ea5ff9fc05ba90b531b4f9f.wasm" } \ No newline at end of file From f9a4260c6a330a9d21dd44d0d5bea50b9eaf937b Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Fri, 14 Oct 2022 11:05:35 -0400 Subject: [PATCH 158/197] wasm: remove tx_from_intent build This module has been removed, but left in the Makefile, so it continues to build an empty (well, 71 byte) wasm. Remove it from the build. --- wasm/checksums.json | 1 - wasm/wasm_source/Makefile | 1 - 2 files changed, 2 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 2dee748987..dbebc6943e 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,6 +1,5 @@ { "tx_bond.wasm": "tx_bond.cc16670ccd2b4a0de0e20de051196dda9058bab885b93c948e9d5dc9fba65e76.wasm", - "tx_from_intent.wasm": "tx_from_intent.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", "tx_ibc.wasm": "tx_ibc.bc1783aaa0fcb587e3963a03b7629fed3c59ef7dbcd099ce3101260b4fafb758.wasm", "tx_init_account.wasm": "tx_init_account.8a70c8722e31af7d0875d2f13279da238269fa91053d0e9413c20fc9cd8e8662.wasm", "tx_init_nft.wasm": "tx_init_nft.28872d99008b7b14593355c6ab6d62108b61a3d2956a0581d80c3d5816b13d4a.wasm", diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index cc6b5c1c95..48d4675ea6 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -6,7 +6,6 @@ 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_from_intent wasms += tx_ibc wasms += tx_init_account wasms += tx_init_nft From bc2b3c5a08fb2f513ac15ae6f44e9ee0952bc2fb Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Fri, 14 Oct 2022 11:51:27 -0400 Subject: [PATCH 159/197] release: update release.toml for cargo-release 0.21.4 --- release.toml | 7 +++---- shared/release.toml | 2 -- 2 files changed, 3 insertions(+), 6 deletions(-) delete mode 100644 shared/release.toml diff --git a/release.toml b/release.toml index e82ec3b4c3..b24197392d 100644 --- a/release.toml +++ b/release.toml @@ -1,10 +1,9 @@ allow-branch = ["main", "maint-*"] consolidate-commits = true -disable-push = true -disable-publish = true -disable-tag = true -no-dev-version = true pre-release-commit-message = "Namada {{version}}" +publish = false +push = false shared-version = true sign-tag = true tag-message = "Namada {{version}}" +tag-name = "v{{version}}" diff --git a/shared/release.toml b/shared/release.toml deleted file mode 100644 index 70e74bcd73..0000000000 --- a/shared/release.toml +++ /dev/null @@ -1,2 +0,0 @@ -disable-tag = false -tag-name = "v{{version}}" From 57f4f41d71ef762e7f491bd0d8b2150169e982bb Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Fri, 14 Oct 2022 12:00:54 -0400 Subject: [PATCH 160/197] release: don't tag from cargo release There are more steps that need to be done after `cargo release` in the root. Tagging is premature. --- release.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/release.toml b/release.toml index b24197392d..632e36bc02 100644 --- a/release.toml +++ b/release.toml @@ -4,6 +4,4 @@ pre-release-commit-message = "Namada {{version}}" publish = false push = false shared-version = true -sign-tag = true -tag-message = "Namada {{version}}" -tag-name = "v{{version}}" +tag = false From 488d4c1dc4f6f8469a6edc29901f8f3e8a12a31f Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Fri, 14 Oct 2022 12:01:47 -0400 Subject: [PATCH 161/197] release: add release.toml to wasm workspace and wasm_for_tests --- wasm/release.toml | 7 +++++++ wasm_for_tests/wasm_source/release.toml | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 wasm/release.toml create mode 100644 wasm_for_tests/wasm_source/release.toml diff --git a/wasm/release.toml b/wasm/release.toml new file mode 100644 index 0000000000..dbacf2cec3 --- /dev/null +++ b/wasm/release.toml @@ -0,0 +1,7 @@ +allow-branch = ["main", "maint-*"] +consolidate-commits = true +pre-release-commit-message = "fixup! Namada {{version}}" +publish = false +push = false +shared-version = true +tag = false diff --git a/wasm_for_tests/wasm_source/release.toml b/wasm_for_tests/wasm_source/release.toml new file mode 100644 index 0000000000..dbacf2cec3 --- /dev/null +++ b/wasm_for_tests/wasm_source/release.toml @@ -0,0 +1,7 @@ +allow-branch = ["main", "maint-*"] +consolidate-commits = true +pre-release-commit-message = "fixup! Namada {{version}}" +publish = false +push = false +shared-version = true +tag = false From 9d014ea6f64401cd6a7dab97c1e5312f879f47a8 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Fri, 14 Oct 2022 12:20:58 -0400 Subject: [PATCH 162/197] release: add scripts/release.sh --- scripts/release.sh | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 scripts/release.sh diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000000..b32ff70764 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,59 @@ +#!/bin/sh +set -e + +if [ -z "$1" ]; then + echo "please specify a version to release" + exit 1 +fi + +REPO_ROOT=$(git rev-parse --show-toplevel) + +if [ "$REPO_ROOT" != "$PWD" ]; then + echo "please run from repository root" + exit 1 +fi + +VERSION="$1" +TAG_NAME="v$1" + +# start from a clean build +git clean -fxd + +# update the main workspace crate versions (1 commit) +HASH_BEFORE=$(git rev-parse HEAD) +cargo release --execute $VERSION +HASH_AFTER=$(git rev-parse HEAD) + +# update the wasm crate versions (2 fixups) +cd $REPO_ROOT/wasm +cargo update -w +git add Cargo.lock +git commit --fixup=$HASH_AFTER +cargo release --execute $VERSION + +# update the wasm_for_tests crate version, and rebuild them (3 fixups) +cd $REPO_ROOT/wasm_for_tests/wasm_source +cargo update -w +git add Cargo.lock +git commit --fixup=$HASH_AFTER +cargo release --execute $VERSION +make all +git add ../*.wasm +git commit --fixup=$HASH_AFTER + +# build the wasm checksums (1 fixup) +cd $REPO_ROOT +make build-wasm-scripts-docker +git add wasm/checksums.json +git commit --fixup=$HASH_AFTER + +# update the changelog (1 fixup) +unclog release --version $TAG_NAME +unclog build > CHANGELOG.md +git add .changelog CHANGELOG.md +git commit --fixup=$HASH_AFTER + +# show the user the result +git rebase --interactive --autosquash --keep-base $HASH_BEFORE + +echo "final $TAG_NAME commit ready for testing" From 2df63e0bfd9afec80aaed2457dd580a6de6924c6 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 14 Oct 2022 13:24:44 -0400 Subject: [PATCH 163/197] specify some default values, other small edits --- .../specs/src/economics/inflation-system.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index 7570693b73..029b5b1248 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -4,7 +4,7 @@ The Namada protocol controls the Namada token NAM (the native staking token), wh ### Proof-of-stake rewards -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. +The security of the proof-of-stake voting power allocation mechanism used by Namada is dependent 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. ### Shielded pool rewards @@ -20,16 +20,16 @@ Inflation is calculated and paid per-epoch as follows. 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_{PoS}$ is the cap of proof-of-stake reward rate, in units of percent per annum (genesis default: 10%) - $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_{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$) +- $EpochsPerYear$ is the number of epochs per year (genesis default: 365) Second, we take as input the following state values: @@ -41,7 +41,7 @@ Second, we take as input the following state values: - $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$) -Public goods funding inflation can be calculated and paid immediately: +Public goods funding inflation can be calculated and paid immediately (in terms of total tokens per epoch): - $I_{PGF} := R_{PGF} * S_{NAM} / EpochsPerYear$ @@ -49,15 +49,15 @@ These tokens are distributed to the public goods funding validity predicate. To run the PD-controllers for proof-of-stake and shielded pool rewards, we first calculate some intermediate values: -- Calculate the staking ratio $R_{PoS}$ as $L_{NAM} / S_{NAM}$ +- Calculate the latest 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}$ +- Calculate PD-controller constants to be used for this epoch Then, for proof-of-stake first, run the PD-controller: From 2b50689701eefbb32d033986312440c4712caf37 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 14 Oct 2022 13:30:11 -0400 Subject: [PATCH 164/197] Use `I` for inflation rates, as `R` reserved for locked ratios --- documentation/specs/src/economics/inflation-system.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index 029b5b1248..6594b4af09 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -22,7 +22,7 @@ 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 (genesis default: 10%) - $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 +- $I_{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$) - ${KP}_{PoS}$ is the proportional gain of the proof-of-stake PD controller, as a fraction of the total input range @@ -43,7 +43,7 @@ Second, we take as input the following state values: Public goods funding inflation can be calculated and paid immediately (in terms of total tokens per epoch): -- $I_{PGF} := R_{PGF} * S_{NAM} / EpochsPerYear$ +- $T_{PGF} := I_{PGF} * S_{NAM} / EpochsPerYear$ These tokens are distributed to the public goods funding validity predicate. From 30f4d3ddb22df61488685e9926835f2a9a132874 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 14 Oct 2022 13:34:59 -0400 Subject: [PATCH 165/197] distinguish between `K` in storage and `K` as intermediate value in inflation calculation --- .../specs/src/economics/inflation-system.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index 6594b4af09..e4c417616c 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -25,11 +25,11 @@ First, we start with the following fixed (governance-alterable) parameters: - $I_{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$) -- ${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$) - $EpochsPerYear$ is the number of epochs per year (genesis default: 365) +- ${KP}_{PoS-nom}$ is the nominal proportional gain of the proof-of-stake PD controller, as a fraction of the total input range +- ${KD}_{PoS-nom}$ is the nominal derivative gain of the proof-of-stake PD controller, as a fraction of the total input range +- ${KP}_{SP_A-nom}$ is the nominal 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-nom}$ is the nominal 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$) Second, we take as input the following state values: @@ -53,11 +53,11 @@ To run the PD-controllers for proof-of-stake and shielded pool rewards, we first - 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$) - - ${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}$ - Calculate PD-controller constants to be used for this epoch + - ${KP}_{PoS} = {KP}_{PoS-nom} * Cap_{PoS-Epoch}$ + - ${KD}_{PoS} = {KD}_{PoS-nom} * Cap_{PoS-Epoch}$ + - ${KP}_{SP_A} = {KP}_{SP_A-nom} * Cap_{SP_A-Epoch}$ + - ${KD}_{SP_A} = {KD}_{SP_A-nom} * Cap_{SP_A-Epoch}$ Then, for proof-of-stake first, run the PD-controller: From 612c940dbd2bf394ab19b16695586c40bec019ee Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 14 Oct 2022 13:36:54 -0400 Subject: [PATCH 166/197] fix: `I` is percent per annum --- documentation/specs/src/economics/inflation-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index e4c417616c..662a2d1b71 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -35,8 +35,8 @@ Second, we take as input the following state values: - $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) +- $I_{PoS}$ is the current proof-of-stake reward rate, in units of percent per annum - $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$) From 0bf96b02fe135e5b573e817e2dc7adc41b17b6d4 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 14 Oct 2022 13:48:11 -0400 Subject: [PATCH 167/197] change storing of error to storing of token ratios (same procedure) bing --- documentation/specs/src/economics/inflation-system.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index 662a2d1b71..fc465e588e 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -35,11 +35,11 @@ Second, we take as input the following state values: - $S_{NAM}$ is the current supply of NAM - $L_{NAM}$ is the current amount of NAM locked in proof-of-stake -- $E_{PoS-last}$ is the error in proof-of-stake lock ratio (stored from the past epoch) - $I_{PoS}$ is the current proof-of-stake reward rate, in units of percent per annum +- $R_{PoS-last}$ is the proof-of-stake locked token ratio from the previous 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$) +- $R_{SP_A-last}$ is the shielded pool locked token ratio for asset $A$ from the previous epoch (separate value for each asset $A$) Public goods funding inflation can be calculated and paid immediately (in terms of total tokens per epoch): @@ -62,18 +62,18 @@ To run the PD-controllers for proof-of-stake and shielded pool rewards, we first Then, for proof-of-stake first, run the PD-controller: - 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}))$ +- Calculate the error derivative $E'_{PoS} = E_{PoS} - E_{PoS-last} = R_{PoS} - R_{PoS-last}$ These tokens are distributed to the proof-of-stake reward distribution validity predicate. Similarly, for each asset $A$ for which shielded pool rewards are being paid: - 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}))$ +- Calculate the error derivative $E'_{SP_A} = E_{SP_A} - E_{SP_A-last} = R_{SP_A} - R_{SP_A-last}$ These tokens are distributed to the shielded pool reward distribution validity predicate. From a96e184426118d71b0e6170a33765eecaaf404e4 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 14 Oct 2022 13:52:33 -0400 Subject: [PATCH 168/197] correct the shielded pool error calculation --- documentation/specs/src/economics/inflation-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index fc465e588e..aac7a5bd57 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -70,9 +70,9 @@ These tokens are distributed to the proof-of-stake reward distribution validity Similarly, for each asset $A$ for which shielded pool rewards are being paid: -- Calculate the error $E_{SP_A} := L_{SP_A-Target} - L_{SP_A}$ - 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}))$ +- Calculate the error $E_{SP_A} = R_{SP_A-Target} - R_{SP_A}$ - Calculate the error derivative $E'_{SP_A} = E_{SP_A} - E_{SP_A-last} = R_{SP_A} - R_{SP_A-last}$ These tokens are distributed to the shielded pool reward distribution validity predicate. From 62c10baf8c2a4823f8dc8b76b14e4e7746419ac5 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 14 Oct 2022 13:53:17 -0400 Subject: [PATCH 169/197] fix md rendering --- documentation/specs/src/economics/inflation-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index aac7a5bd57..0824a1e9ae 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -70,10 +70,10 @@ These tokens are distributed to the proof-of-stake reward distribution validity Similarly, for each asset $A$ for which shielded pool rewards are being paid: -- 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}))$ - Calculate the error $E_{SP_A} = R_{SP_A-Target} - R_{SP_A}$ - Calculate the error derivative $E'_{SP_A} = E_{SP_A} - E_{SP_A-last} = R_{SP_A} - R_{SP_A-last}$ +- Calculate the control value $C_{SP_A} = (KP_{SP_A} * E_{SP_A}) - (KD_{SP_A} * E'_{SP_A})$ These tokens are distributed to the shielded pool reward distribution validity predicate. From bf3454094c9a760ba2ff59157390a2abdbbece0a Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 14 Oct 2022 13:53:56 -0400 Subject: [PATCH 170/197] distinguish new and old inflation rates with a prime --- documentation/specs/src/economics/inflation-system.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index 0824a1e9ae..5cebcf3d73 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -63,17 +63,17 @@ Then, for proof-of-stake first, run the PD-controller: - Calculate the error $E_{PoS} := R_{PoS-Target} - R_{PoS}$ - 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}))$ - Calculate the error derivative $E'_{PoS} = E_{PoS} - E_{PoS-last} = R_{PoS} - R_{PoS-last}$ +- Calculate the new $I'_{PoS} = max(0, min(I_{PoS} + C_{PoS}, Cap_{PoS}))$ These tokens are distributed to the proof-of-stake reward distribution validity predicate. Similarly, for each asset $A$ for which shielded pool rewards are being paid: -- Calculate the new $I_{SP_A} := max(0, min(I_{SP_A} + C_{SP_A}, Cap_{SP_A-Epoch}))$ - Calculate the error $E_{SP_A} = R_{SP_A-Target} - R_{SP_A}$ - Calculate the error derivative $E'_{SP_A} = E_{SP_A} - E_{SP_A-last} = R_{SP_A} - R_{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. From 11058ab1498662262fd4daa22823ce2be8600b1e Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 14 Oct 2022 14:02:51 -0400 Subject: [PATCH 171/197] Remove other `:=` in favor of `=` --- documentation/specs/src/economics/inflation-system.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index 5cebcf3d73..721474d534 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -61,9 +61,9 @@ To run the PD-controllers for proof-of-stake and shielded pool rewards, we first Then, for proof-of-stake first, run the PD-controller: -- Calculate the error $E_{PoS} := R_{PoS-Target} - R_{PoS}$ -- Calculate the control value $C_{PoS} := (KP_{PoS} * E_{PoS}) - (KD_{PoS} * E'_{PoS})$ +- Calculate the error $E_{PoS} = R_{PoS-Target} - R_{PoS}$ - Calculate the error derivative $E'_{PoS} = E_{PoS} - E_{PoS-last} = R_{PoS} - R_{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}))$ These tokens are distributed to the proof-of-stake reward distribution validity predicate. From f9fe25617b7bf93bffa02b6782ffd7f37b4c45b6 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 14 Oct 2022 14:18:44 -0400 Subject: [PATCH 172/197] fix term ordering in error calculation --- documentation/specs/src/economics/inflation-system.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index 721474d534..334c33dabc 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -62,7 +62,7 @@ To run the PD-controllers for proof-of-stake and shielded pool rewards, we first Then, for proof-of-stake first, run the PD-controller: - Calculate the error $E_{PoS} = R_{PoS-Target} - R_{PoS}$ -- Calculate the error derivative $E'_{PoS} = E_{PoS} - E_{PoS-last} = R_{PoS} - R_{PoS-last}$ +- Calculate the error derivative $E'_{PoS} = E_{PoS} - E_{PoS-last} = R_{PoS-last} - R_{PoS}$ - 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}))$ @@ -71,7 +71,7 @@ These tokens are distributed to the proof-of-stake reward distribution validity Similarly, for each asset $A$ for which shielded pool rewards are being paid: - Calculate the error $E_{SP_A} = R_{SP_A-Target} - R_{SP_A}$ -- Calculate the error derivative $E'_{SP_A} = E_{SP_A} - E_{SP_A-last} = R_{SP_A} - R_{SP_A-last}$ +- Calculate the error derivative $E'_{SP_A} = E_{SP_A} - E_{SP_A-last} = R_{SP_A-last} - R_{SP_A}$ - 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}))$ From 46c053bdc78a499a5775a0843a6aa55f49e3d2cf Mon Sep 17 00:00:00 2001 From: brentstone Date: Sat, 15 Oct 2022 13:55:37 -0400 Subject: [PATCH 173/197] change inflation `I` to have units of tokens per epoch --- .../specs/src/economics/inflation-system.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index 334c33dabc..d3cab1c2aa 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -22,9 +22,9 @@ 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 (genesis default: 10%) - $Cap_{SP-A}$ is the cap of shielded pool reward rate for each asset $A$, in units of percent per annum -- $I_{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$) +- $\lambda_{PGF}$ is the public goods funding reward rate, in units of percent per annum - $EpochsPerYear$ is the number of epochs per year (genesis default: 365) - ${KP}_{PoS-nom}$ is the nominal proportional gain of the proof-of-stake PD controller, as a fraction of the total input range - ${KD}_{PoS-nom}$ is the nominal derivative gain of the proof-of-stake PD controller, as a fraction of the total input range @@ -35,24 +35,24 @@ Second, we take as input the following state values: - $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 percent per annum +- $I_{PoS}$ is the current proof-of-stake inflation amount, in units of tokens per epoch - $R_{PoS-last}$ is the proof-of-stake locked token ratio from the previous 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 +- $I_{SP_A}$ is the current shielded pool inflation amount for asset $A$, in units of tokens per epoch - $R_{SP_A-last}$ is the shielded pool locked token ratio for asset $A$ from the previous epoch (separate value for each asset $A$) Public goods funding inflation can be calculated and paid immediately (in terms of total tokens per epoch): -- $T_{PGF} := I_{PGF} * S_{NAM} / EpochsPerYear$ +- $I_{PGF} = \lambda_{PGF} * S_{NAM} / EpochsPerYear$ These tokens are distributed to the public goods funding validity predicate. To run the PD-controllers for proof-of-stake and shielded pool rewards, we first calculate some intermediate values: - Calculate the latest 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 the per-epoch cap on the proof-of-stake and shielded pool token inflation - Calculate PD-controller constants to be used for this epoch - ${KP}_{PoS} = {KP}_{PoS-nom} * Cap_{PoS-Epoch}$ - ${KD}_{PoS} = {KD}_{PoS-nom} * Cap_{PoS-Epoch}$ @@ -64,7 +64,7 @@ Then, for proof-of-stake first, run the PD-controller: - Calculate the error $E_{PoS} = R_{PoS-Target} - R_{PoS}$ - Calculate the error derivative $E'_{PoS} = E_{PoS} - E_{PoS-last} = R_{PoS-last} - R_{PoS}$ - 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}))$ +- Calculate the new $I'_{PoS} = max(0, min(I_{PoS} + C_{PoS}, Cap_{PoS-Epoch}))$ These tokens are distributed to the proof-of-stake reward distribution validity predicate. From e0874c7c7282fa032ee35c4ce2f1080943e66630 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sat, 15 Oct 2022 13:57:29 -0400 Subject: [PATCH 174/197] change `L_{NAM}` -> `L_{PoS}` for clarity and consistency --- documentation/specs/src/economics/inflation-system.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index d3cab1c2aa..806c62f3a1 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -34,7 +34,7 @@ First, we start with the following fixed (governance-alterable) parameters: Second, we take as input the following state values: - $S_{NAM}$ is the current supply of NAM -- $L_{NAM}$ is the current amount of NAM locked in proof-of-stake +- $L_{PoS}$ is the current amount of NAM locked in proof-of-stake - $I_{PoS}$ is the current proof-of-stake inflation amount, in units of tokens per epoch - $R_{PoS-last}$ is the proof-of-stake locked token ratio from the previous epoch - $L_{SP_A}$ is the current amount of asset $A$ locked in the shielded pool (separate value for each asset $A$) @@ -49,9 +49,9 @@ These tokens are distributed to the public goods funding validity predicate. To run the PD-controllers for proof-of-stake and shielded pool rewards, we first calculate some intermediate values: -- Calculate the latest staking ratio $R_{PoS}$ as $L_{NAM} / S_{NAM}$ - $Cap_{PoS-Epoch} := S_{NAM} * Cap_{PoS} / EpochsPerYear$ - $Cap_{SP_A-Epoch} := S_{NAM} * Cap_{SP_A} / EpochsPerYear$ (separate value for each $A$) +- Calculate the latest staking ratio $R_{PoS}$ as $L_{PoS} / S_{NAM}$ - Calculate the per-epoch cap on the proof-of-stake and shielded pool token inflation - Calculate PD-controller constants to be used for this epoch - ${KP}_{PoS} = {KP}_{PoS-nom} * Cap_{PoS-Epoch}$ From cc8f6f7480ced3025f5de730b90fffc7e543330d Mon Sep 17 00:00:00 2001 From: brentstone Date: Sat, 15 Oct 2022 13:59:26 -0400 Subject: [PATCH 175/197] formatting changes --- .../specs/src/economics/inflation-system.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index 806c62f3a1..921e78d4e5 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -21,10 +21,10 @@ Inflation is calculated and paid per-epoch as follows. 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 (genesis default: 10%) -- $Cap_{SP-A}$ is the cap of shielded pool reward rate for each asset $A$, 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$) +- $Cap_{SP_A}$ is the cap of shielded pool reward rate for each asset $A$, in units of percent per annum - $\lambda_{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-nom}$ is the nominal proportional gain of the proof-of-stake PD controller, as a fraction of the total input range - ${KD}_{PoS-nom}$ is the nominal derivative gain of the proof-of-stake PD controller, as a fraction of the total input range @@ -49,10 +49,10 @@ These tokens are distributed to the public goods funding validity predicate. To run the PD-controllers for proof-of-stake and shielded pool rewards, we first calculate some intermediate values: - - $Cap_{PoS-Epoch} := S_{NAM} * Cap_{PoS} / EpochsPerYear$ - - $Cap_{SP_A-Epoch} := S_{NAM} * Cap_{SP_A} / EpochsPerYear$ (separate value for each $A$) - Calculate the latest staking ratio $R_{PoS}$ as $L_{PoS} / S_{NAM}$ - Calculate the per-epoch cap on the proof-of-stake and shielded pool token inflation + - $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 to be used for this epoch - ${KP}_{PoS} = {KP}_{PoS-nom} * Cap_{PoS-Epoch}$ - ${KD}_{PoS} = {KD}_{PoS-nom} * Cap_{PoS-Epoch}$ @@ -61,7 +61,7 @@ To run the PD-controllers for proof-of-stake and shielded pool rewards, we first Then, for proof-of-stake first, run the PD-controller: -- Calculate the error $E_{PoS} = R_{PoS-Target} - R_{PoS}$ +- Calculate the error $E_{PoS} = R_{PoS-target} - R_{PoS}$ - Calculate the error derivative $E'_{PoS} = E_{PoS} - E_{PoS-last} = R_{PoS-last} - R_{PoS}$ - 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-Epoch}))$ @@ -70,11 +70,11 @@ These tokens are distributed to the proof-of-stake reward distribution validity Similarly, for each asset $A$ for which shielded pool rewards are being paid: -- Calculate the error $E_{SP_A} = R_{SP_A-Target} - R_{SP_A}$ +- Calculate the error $E_{SP_A} = R_{SP_A-target} - R_{SP_A}$ - Calculate the error derivative $E'_{SP_A} = E_{SP_A} - E_{SP_A-last} = R_{SP_A-last} - R_{SP_A}$ - 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 +Finally, we store the latest inflation and locked token ratio values for the next epoch's controller round. \ No newline at end of file From 9a830c00650e8643b540c55a5940606e1f46f24b Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Sat, 15 Oct 2022 16:27:03 -0400 Subject: [PATCH 176/197] ci: standardize on one tendermint hash With the shims, we will use only tendermint hash ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 for the time being. --- .github/workflows/build-and-test.yml | 2 +- .github/workflows/build-tendermint.yml | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 0d0112924e..1c4cbd3412 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -117,7 +117,7 @@ jobs: cache_key: anoma cache_version: v1 wait_for: anoma-release (ubuntu-latest, ABCI Release build, anoma-e2e-release, v1) - tendermint_artifact: tendermint-unreleased-559fb33ff9b27503ce7ac1c7d8589fe1d8b3e900 + tendermint_artifact: tendermint-unreleased-ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 env: CARGO_INCREMENTAL: 0 diff --git a/.github/workflows/build-tendermint.yml b/.github/workflows/build-tendermint.yml index 91a5259dac..7914a39a49 100644 --- a/.github/workflows/build-tendermint.yml +++ b/.github/workflows/build-tendermint.yml @@ -21,9 +21,6 @@ jobs: matrix: os: [ubuntu-latest] make: - - name: tendermint-unreleased - repository: heliaxdev/tendermint - tendermint_version: 559fb33ff9b27503ce7ac1c7d8589fe1d8b3e900 - name: tendermint-unreleased repository: heliaxdev/tendermint tendermint_version: ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 From 5f544763772795d9570383a30ffe39a14b08f772 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 17 Oct 2022 04:31:43 -0400 Subject: [PATCH 177/197] wasm: update checksums.json --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 898f6e9763..f2bdcdd57f 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.16097490afa7378c79e6216751b20796cde3a9026c34255c3f1e5ec5a4c9482e.wasm", - "tx_from_intent.wasm": "tx_from_intent.f8d1937b17a3abaf7ea595526c870b3d57ddef8e0c1bc96f8e0a448864b186c7.wasm", - "tx_ibc.wasm": "tx_ibc.378b10551c0b22c2c892d24e2676ee5160d654e2e53a50e7925e0f2c6321497b.wasm", - "tx_init_account.wasm": "tx_init_account.adab66c2b4d635e9c42133936aafb143363f91dddff2a60f94df504ffec951a6.wasm", - "tx_init_nft.wasm": "tx_init_nft.d1065ebd80ba6ea97f29bc2268becf9ba3ba2952641992464f3e9e868df17447.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.184131576a579f9ece96460d1eb20e5970fcd149b0527c8e56b711e5c535aa5f.wasm", - "tx_init_validator.wasm": "tx_init_validator.2990747d24d467b56e19724c5d13df826a3aab83f7e1bf26558dbdf44e260f8a.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.33db14dea4a03ff7508ca44f3ae956d83c0abceb3dae5be844668e54ac22b273.wasm", - "tx_transfer.wasm": "tx_transfer.a601d62296f56f6b4dabb0a2ad082478d195e667c7469f363bdfd5fe41349bd8.wasm", - "tx_unbond.wasm": "tx_unbond.014cbf5b0aa3ac592c0a6940dd502ec8569a3af4d12782e3a5931c15dc13042f.wasm", - "tx_update_vp.wasm": "tx_update_vp.83d4caeb5a9ca3009cd899810493a6b87b4c07fa9ed36f297db99dc881fb9a1c.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.bcb5280be9dfeed0a7650ba5e4a3cebc2c19b76780fd74dcb345be3da766b64a.wasm", - "tx_withdraw.wasm": "tx_withdraw.8fc0a3439ee9ae66047c520519877bc1f540e0cb02abfa31afa8cce8cd069b6f.wasm", - "vp_nft.wasm": "vp_nft.2c820c728d241b82bf0ed3c552ee9e7c046bceaa4f7b6f12d3236a1a3d7c1589.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.6e762f3fda8aa7a252e2b29a4a312db91ded062d6c18b8b489883733c89dc227.wasm", - "vp_token.wasm": "vp_token.c45cc3848f12fc47713702dc206d1312ad740a6bbee7f141556714f6f89d4985.wasm", - "vp_user.wasm": "vp_user.d6cd2f4b5bc26f96df6aa300fddf4d25e1656920d59896209bd54ae8d407ecde.wasm" + "tx_bond.wasm": "tx_bond.82d69c1cdf57188270f50807fa212e6d709e74e853b6c61c026ba29c0161f1b2.wasm", + "tx_from_intent.wasm": "tx_from_intent.7405a87fa5dadbce24ad572b4c0bcc325d123be6a820c86e798e5999d13f01e7.wasm", + "tx_ibc.wasm": "tx_ibc.780cc2e3e9066908d77fcd913289075212f6a55a7fc23ada00a274acd4dad0f3.wasm", + "tx_init_account.wasm": "tx_init_account.42f987c32d5137a16e7ecf13a4b224e6db1108d7de35a507f3c248a227a0c2e4.wasm", + "tx_init_nft.wasm": "tx_init_nft.753ea9cba369f3d253f7a7b207019545136cd4f1044664264360de37f00907d7.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.7367721c814eda33d23eba40a26abce81143ee1402e92955de0fa5556367dce6.wasm", + "tx_init_validator.wasm": "tx_init_validator.10efd51df16e758a35e84992f46d510c3a17e8eeed6113dcbb99cf951f93b4d0.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.419e1061a60ce50bafe9ec86f771f26d81e110eb3c9f64b4db68200a9a65eff8.wasm", + "tx_transfer.wasm": "tx_transfer.15525bdf61259b40c751ae37ad9ed9890b8a23d93bd0aec7de580b96d433b6c9.wasm", + "tx_unbond.wasm": "tx_unbond.cf48deaa9c0e23cd1486e4c693f1e7d9464d41fde6ef5037e22fa8c0526f586b.wasm", + "tx_update_vp.wasm": "tx_update_vp.9dc45326a8b485b4f40d2c35ef9f75f423e28b87c09cee9993002c0dd19312eb.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.e46c995d77207dfd8c9349608ddac950ddcceac37e4ca44dcac93378e7638820.wasm", + "tx_withdraw.wasm": "tx_withdraw.4533a099547e1f7334569b576314bbaeea96fdc2d709996eabe599a3fa543e24.wasm", + "vp_nft.wasm": "vp_nft.e9b5369861c06fb8d1a292d8695ccd888f52f6eb94fcb951cb3821348c93c4b7.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.afb4267fc678f5ccbfc1457147670d7f7c41a9ac8578a72c68e9656325c8345b.wasm", + "vp_token.wasm": "vp_token.481e716492b7b98821f87c4c6898aea9a257b6b80a5959715f8240185adaa91d.wasm", + "vp_user.wasm": "vp_user.68584fc8d1de345d12fb2d2f5f965ab4e5e5944cb8a40a2d1488bc35172adc1f.wasm" } \ No newline at end of file From 23275d908ba46c2f662081471e7c622b2f674fe9 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 17 Oct 2022 05:11:50 -0400 Subject: [PATCH 178/197] build: pin abciplus library revisions --- apps/Cargo.toml | 12 +++++++----- shared/Cargo.toml | 10 ++++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/Cargo.toml b/apps/Cargo.toml index f128d58667..ec206815f9 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -126,10 +126,11 @@ 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} +# branch bat/abciplus +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "2e9e0d058a68e2534974ff7d22b9058d4ebda3be", optional = true} +tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "2e9e0d058a68e2534974ff7d22b9058d4ebda3be", optional = true} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "2e9e0d058a68e2534974ff7d22b9058d4ebda3be", optional = true} +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "2e9e0d058a68e2534974ff7d22b9058d4ebda3be", features = ["http-client", "websocket-client"], optional = true} thiserror = "1.0.30" tokio = {version = "1.8.2", features = ["full"]} toml = "0.5.8" @@ -138,7 +139,8 @@ 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} +# branch bat/abciplus +tower-abci = {git = "https://github.com/heliaxdev/tower-abci", rev = "21623a99bdca5b006d53752a1967849bef3b89ea", 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 bbfb51171b..a047b8a263 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -83,8 +83,9 @@ 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} +# branch bat/abciplus +ibc = {git = "https://github.com/heliaxdev/ibc-rs", rev = "99a761657a51f6e5f074f3217426903e53632934", default-features = false, optional = true} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", rev = "99a761657a51f6e5f074f3217426903e53632934", default-features = false, optional = true} ics23 = "0.6.7" itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} @@ -108,8 +109,9 @@ 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} +# branch bat/abciplus +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "2e9e0d058a68e2534974ff7d22b9058d4ebda3be", optional = true} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "2e9e0d058a68e2534974ff7d22b9058d4ebda3be", optional = true} thiserror = "1.0.30" tracing = "0.1.30" wasmer = {version = "=2.2.0", optional = true} From 84e0efa3e0e15a877f03c097934de5277cd87f66 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 5 Oct 2022 00:06:49 +0200 Subject: [PATCH 179/197] [feat]: Refactored the merkle tree into a more maintainable multi-store --- Cargo.lock | 23 +- apps/Cargo.toml | 3 +- apps/src/lib/node/ledger/shell/queries.rs | 4 +- shared/Cargo.toml | 1 - shared/src/ledger/storage/ics23_specs.rs | 75 ++++ shared/src/ledger/storage/merkle_tree.rs | 450 ++++++---------------- shared/src/ledger/storage/mod.rs | 38 +- shared/src/ledger/storage/traits.rs | 275 +++++++++++++ shared/src/types/hash.rs | 36 -- shared/src/types/storage.rs | 52 ++- wasm/Cargo.lock | 14 +- wasm_for_tests/wasm_source/Cargo.lock | 14 +- 12 files changed, 544 insertions(+), 441 deletions(-) create mode 100644 shared/src/ledger/storage/ics23_specs.rs create mode 100644 shared/src/ledger/storage/traits.rs diff --git a/Cargo.lock b/Cargo.lock index 3811239693..748e68fbd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,9 +316,9 @@ dependencies = [ [[package]] name = "async-std" -version = "1.12.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "d9f06685bad74e0570f5213741bea82158279a4103d988e57bfada11ad230341" dependencies = [ "async-channel", "async-global-executor", @@ -334,6 +334,7 @@ dependencies = [ "kv-log-macro", "log 0.4.17", "memchr", + "num_cpus", "once_cell", "pin-project-lite", "pin-utils", @@ -3053,8 +3054,7 @@ dependencies = [ "serde 1.0.145", "serde_json", "sha2 0.9.9", - "sparse-merkle-tree 0.3.1-pre (git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8)", - "sparse-merkle-tree 0.3.1-pre (git+https://github.com/heliaxdev/sparse-merkle-tree?rev=121c79424646cc3e917f8616e549fbddee775a0a)", + "sparse-merkle-tree", "tempfile", "tendermint", "tendermint-proto", @@ -3129,8 +3129,7 @@ dependencies = [ "serde_json", "sha2 0.9.9", "signal-hook", - "sparse-merkle-tree 0.3.1-pre (git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8)", - "sparse-merkle-tree 0.3.1-pre (git+https://github.com/heliaxdev/sparse-merkle-tree?rev=121c79424646cc3e917f8616e549fbddee775a0a)", + "sparse-merkle-tree", "sysinfo", "tar", "tempfile", @@ -4956,18 +4955,6 @@ dependencies = [ "sha2 0.9.9", ] -[[package]] -name = "sparse-merkle-tree" -version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=121c79424646cc3e917f8616e549fbddee775a0a#121c79424646cc3e917f8616e549fbddee775a0a" -dependencies = [ - "blake2b-rs", - "borsh", - "cfg-if 1.0.0", - "ics23", - "sha2 0.9.9", -] - [[package]] name = "spin" version = "0.5.2" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 12be2bb0f4..856b8a9e46 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -51,7 +51,7 @@ ark-serialize = "0.3.0" ark-std = "0.3.0" # branch = "bat/arse-merkle-tree" arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/heliaxdev/sparse-merkle-tree", rev = "04ad1eeb28901b57a7599bbe433b3822965dabe8", features = ["std", "borsh"]} -async-std = {version = "1.9.0", features = ["unstable"]} +async-std = {version = "=1.9.0", features = ["unstable"]} async-trait = "0.1.51" base64 = "0.13.0" bech32 = "0.8.0" @@ -97,7 +97,6 @@ serde_bytes = "0.11.5" serde_json = {version = "1.0.62", features = ["raw_value"]} sha2 = "0.9.3" signal-hook = "0.3.9" -sparse-merkle-tree = {git = "https://github.com/heliaxdev/sparse-merkle-tree", rev = "121c79424646cc3e917f8616e549fbddee775a0a", 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/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d50dd2405e..5b23078222 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -104,7 +104,7 @@ where let proof_ops = if is_proven { match self.storage.get_existence_proof( key, - value.clone(), + value.clone().into(), height, ) { Ok(proof) => Some(proof.into()), @@ -199,7 +199,7 @@ where for PrefixValue { key, value } in &values { match self.storage.get_existence_proof( key, - value.clone(), + value.clone().into(), height, ) { Ok(p) => { diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 5178b46f90..a32da772da 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -87,7 +87,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", rev = "121c79424646cc3e917f8616e549fbddee775a0a", 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/ics23_specs.rs b/shared/src/ledger/storage/ics23_specs.rs new file mode 100644 index 0000000000..00691bd30e --- /dev/null +++ b/shared/src/ledger/storage/ics23_specs.rs @@ -0,0 +1,75 @@ +//! A module that contains + +use arse_merkle_tree::H256; +use ics23::{HashOp, LeafOp, LengthOp, ProofSpec}; + +use super::traits::StorageHasher; + +/// 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. +pub fn base_leaf_spec() -> 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. +pub fn leaf_spec() -> 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. +pub fn ibc_leaf_spec() -> 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(), + } +} + +/// 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 { + leaf_spec: Some(ibc_leaf_spec::()), + ..spec.clone() + }; + let base_tree_spec = ProofSpec { + 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::()), + ..spec.clone() + }; + let base_tree_spec = ProofSpec { + 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 c7fec10126..753a0439b1 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 tendermint::merkle::proof::{Proof, ProofOp}; 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::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 @@ -244,7 +245,6 @@ 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); - Self { base, account, @@ -253,42 +253,38 @@ impl MerkleTree { } } - fn tree(&self, store_type: &StoreType) -> Either<&Smt, &Amt> { + fn tree(&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(&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 => 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 +292,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)?; - 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)?; + self.tree_mut(&store_type).subtree_delete(&sub_key) } /// Get the root @@ -348,88 +326,69 @@ 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!(), - } + 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, _) = 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) + .subtree_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 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; + let ep = left.as_mut().or(right.as_mut()).expect( + "A left or right existence proof should exist.", + ); + ep.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 +413,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 +436,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 +460,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 +521,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 +529,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] @@ -840,9 +608,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 +663,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 +737,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 422bbe4d5e..dc2dba9c0f 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -1,12 +1,15 @@ //! 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; @@ -20,16 +23,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, }; +pub use crate::ledger::storage::traits::{Sha256Hasher, 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; @@ -527,15 +530,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 }), } } @@ -856,11 +876,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..68ac968bf2 --- /dev/null +++ b/shared/src/ledger/storage/traits.rs @@ -0,0 +1,275 @@ +//! 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::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<(), Error>; +} + +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 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) => Hash::try_from(bytes.as_slice()) + .map_err(|_| Error::InvalidValue)?, + }; + self.update(H::hash(key.to_string()).into(), value) + .map(Hash::from) + .map_err(|err| Error::MerkleTree(err.to_string())) + } + + 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(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())?; + 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 { + 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<(), 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 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/types/hash.rs b/shared/src/types/hash.rs index 0e960ec01f..c33920a8f3 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 serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -109,38 +108,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 c6b7f9dfbf..57d0f66c5d 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; @@ -10,13 +9,14 @@ 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; #[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; @@ -34,6 +34,8 @@ pub enum Error { InvalidKeySeg(String), #[error("Error parsing key segment {0}")] ParseKeySeg(String), + #[error("Could not parse string: '{0}' into requested type: {1}")] + ParseError(String, String), } /// Result for functions that may fail @@ -58,6 +60,7 @@ pub const RESERVED_VP_KEY: &str = "?"; Copy, BorshSerialize, BorshDeserialize, + BorshSchema, PartialEq, Eq, PartialOrd, @@ -111,6 +114,12 @@ impl From for BlockHash { } } +impl From for BlockHeight { + fn from(height: u64) -> Self { + BlockHeight(height) + } +} + impl TryFrom for BlockHeight { type Error = String; @@ -237,14 +246,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 @@ -326,6 +346,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/wasm/Cargo.lock b/wasm/Cargo.lock index f69000550b..9b5fff114e 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1519,8 +1519,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.9.9", - "sparse-merkle-tree 0.3.1-pre (git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8)", - "sparse-merkle-tree 0.3.1-pre (git+https://github.com/heliaxdev/sparse-merkle-tree?rev=121c79424646cc3e917f8616e549fbddee775a0a)", + "sparse-merkle-tree", "tempfile", "tendermint", "tendermint-proto", @@ -2473,17 +2472,6 @@ dependencies = [ "sha2 0.9.9", ] -[[package]] -name = "sparse-merkle-tree" -version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=121c79424646cc3e917f8616e549fbddee775a0a#121c79424646cc3e917f8616e549fbddee775a0a" -dependencies = [ - "borsh", - "cfg-if 1.0.0", - "ics23", - "sha2 0.9.9", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index f77584d79a..88fd209412 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1519,8 +1519,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.9.9", - "sparse-merkle-tree 0.3.1-pre (git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8)", - "sparse-merkle-tree 0.3.1-pre (git+https://github.com/heliaxdev/sparse-merkle-tree?rev=121c79424646cc3e917f8616e549fbddee775a0a)", + "sparse-merkle-tree", "tempfile", "tendermint", "tendermint-proto", @@ -2467,17 +2466,6 @@ dependencies = [ "sha2 0.9.9", ] -[[package]] -name = "sparse-merkle-tree" -version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=121c79424646cc3e917f8616e549fbddee775a0a#121c79424646cc3e917f8616e549fbddee775a0a" -dependencies = [ - "borsh", - "cfg-if 1.0.0", - "ics23", - "sha2 0.9.9", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" From d3d74bfef2cdfb43923c6577ad4c9837d07d11e5 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 5 Oct 2022 15:58:56 +0200 Subject: [PATCH 180/197] [fix]: Fixed some tests. --- .../ledger/storage/mod.txt | 7 +++++++ shared/src/ledger/storage/merkle_tree.rs | 4 ++-- shared/src/ledger/storage/traits.rs | 21 ++++++++++--------- 3 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 shared/proptest-regressions/ledger/storage/mod.txt diff --git a/shared/proptest-regressions/ledger/storage/mod.txt b/shared/proptest-regressions/ledger/storage/mod.txt new file mode 100644 index 0000000000..01a6a63afe --- /dev/null +++ b/shared/proptest-regressions/ledger/storage/mod.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 4bd39dfdbdf34e1167050c2a12091a76e79832d369517f2f21805112ccf97bf0 # shrinks to (epoch_duration, max_expected_time_per_block, start_height, start_time, block_height, block_time, min_blocks_delta, min_duration_delta, max_time_per_block_delta) = (EpochDuration { min_num_of_blocks: 1, min_duration: DurationSecs(1) }, 1, BlockHeight(0), DateTimeUtc(1970-01-01T00:00:00Z), BlockHeight(1), DateTimeUtc(1970-01-01T00:00:01Z), 0, 0, 0) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 753a0439b1..44ee5085dc 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -337,7 +337,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 @@ -352,7 +352,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 diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index 68ac968bf2..b9b437b2c6 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}; @@ -44,9 +44,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( @@ -84,10 +85,9 @@ 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()) }; - 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())) } @@ -103,9 +103,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( From 56ef297ea89fe23e0514d1e49a9345654da787da Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 5 Oct 2022 16:29:31 +0200 Subject: [PATCH 181/197] [fix]: Fixed last ibc unit test --- shared/proptest-regressions/ledger/storage/mod.txt | 7 ------- shared/src/ledger/storage/traits.rs | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 shared/proptest-regressions/ledger/storage/mod.txt diff --git a/shared/proptest-regressions/ledger/storage/mod.txt b/shared/proptest-regressions/ledger/storage/mod.txt deleted file mode 100644 index 01a6a63afe..0000000000 --- a/shared/proptest-regressions/ledger/storage/mod.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Seeds for failure cases proptest has generated in the past. It is -# automatically read and these particular cases re-run before any -# novel cases are generated. -# -# It is recommended to check this file in to source control so that -# everyone who runs the test benefits from these saved cases. -cc 4bd39dfdbdf34e1167050c2a12091a76e79832d369517f2f21805112ccf97bf0 # shrinks to (epoch_duration, max_expected_time_per_block, start_height, start_time, block_height, block_time, min_blocks_delta, min_duration_delta, max_time_per_block_delta) = (EpochDuration { min_num_of_blocks: 1, min_duration: DurationSecs(1) }, 1, BlockHeight(0), DateTimeUtc(1970-01-01T00:00:00Z), BlockHeight(1), DateTimeUtc(1970-01-01T00:00:01Z), 0, 0, 0) diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index b9b437b2c6..e64b0ea53e 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -85,7 +85,7 @@ impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Smt { value: MerkleValue, ) -> Result { let value = match value { - MerkleValue::Bytes(bytes) => H::hash(bytes.as_slice()) + MerkleValue::Bytes(bytes) => H::hash(bytes.as_slice()), }; self.update(H::hash(key.to_string()).into(), value.into()) .map(Hash::from) @@ -105,7 +105,7 @@ impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Amt { 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())) + Err(e) => Err(Error::MerkleTree(e.to_string())), } } From d25d752b1b0f16b6e85ebacd9611568b6ae507c7 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 7 Oct 2022 11:42:59 +0200 Subject: [PATCH 182/197] [fix]: Update the main tree when deleting a key --- shared/src/ledger/storage/merkle_tree.rs | 67 ++++++++++++++++++++++-- shared/src/ledger/storage/traits.rs | 10 ++-- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 44ee5085dc..62d946bfd6 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -308,7 +308,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 @@ -546,6 +551,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(); @@ -561,10 +567,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 e64b0ea53e..e382f34d73 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -39,7 +39,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 { @@ -92,10 +92,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())) } } @@ -150,11 +150,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))) } } From c8174da695057fc444acb22ab07c9689b821e555 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 10 Oct 2022 11:08:25 +0200 Subject: [PATCH 183/197] [fix]: Fixed proof specs for non-existence proofs --- shared/src/ledger/storage/merkle_tree.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 62d946bfd6..9e64f0efe6 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -379,10 +379,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!(), } From dc744990c05041964463abbd3c1bd04ea387f47e Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 12:04:54 +0200 Subject: [PATCH 184/197] [chore]: Lints and formatting --- shared/src/types/hash.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index c33920a8f3..d2c27f96d4 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -4,6 +4,7 @@ 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 serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -108,3 +109,9 @@ impl From for TmHash { TmHash::Sha256(hash.0) } } + +impl From for TreeHash { + fn from(hash: Hash) -> Self { + Self::from(hash.0) + } +} From 4c67c148c90ecc1a1549d0dd6336ed935b962014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 17 Oct 2022 14:46:42 +0200 Subject: [PATCH 185/197] wasm: update checksums --- wasm/checksums.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 2dee748987..4d655d535a 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.cc16670ccd2b4a0de0e20de051196dda9058bab885b93c948e9d5dc9fba65e76.wasm", + "tx_bond.wasm": "tx_bond.f0c520b6562e23785df2471c326e0d387b57ecb3ec0313b4a845d464a43dd596.wasm", "tx_from_intent.wasm": "tx_from_intent.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_ibc.wasm": "tx_ibc.bc1783aaa0fcb587e3963a03b7629fed3c59ef7dbcd099ce3101260b4fafb758.wasm", - "tx_init_account.wasm": "tx_init_account.8a70c8722e31af7d0875d2f13279da238269fa91053d0e9413c20fc9cd8e8662.wasm", - "tx_init_nft.wasm": "tx_init_nft.28872d99008b7b14593355c6ab6d62108b61a3d2956a0581d80c3d5816b13d4a.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f704144e1bb74ebe9e937c4e6395148bfd31eaa74608f105f3c0496f62be3077.wasm", - "tx_init_validator.wasm": "tx_init_validator.dddaaf19e7a53eaa8b2b1376cdfdfaf3f776c4e5305cab0d4d2050d520853790.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.272053e50688f04d7a258bc324c704c49a4833839b4e37dd6f691c80b38012e2.wasm", - "tx_transfer.wasm": "tx_transfer.baa846ba31c9e6f93c5d2facf4cec50518990924d1aa71216a6b4e48557f00b4.wasm", - "tx_unbond.wasm": "tx_unbond.86132bec595dfa726445a53a25900fccc2ab6457241d9ddf2ca3c4a8ec799f19.wasm", - "tx_update_vp.wasm": "tx_update_vp.28050dd60d9297b360ade93213a3b7677d424b703e13d3732c767e25da4bd6a1.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.3a698cb7f26d5c96c97efad860a33d123b439c071cb488d6f52be3dc0c170848.wasm", - "tx_withdraw.wasm": "tx_withdraw.600bfe69fc3fe60e96acb38f6cd5f69b0d4f182cb4eebe804cbf2a782e91d1ca.wasm", - "vp_nft.wasm": "vp_nft.736aef15807d2c8233bafbc842f3b87df0df48b1e319ce37358b73d21b73814b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.ab70bd5fd3c5a6756f947ee86c4518611be7e6ab23b504a7f03681235f70d699.wasm", - "vp_token.wasm": "vp_token.6a8837598c2e9c380c4da1be54c4d18852213457d71f9e2d5fd4b13a1d4d8050.wasm", - "vp_user.wasm": "vp_user.86c1ca9c00248000e1bf071b86ad221cb1d1c4e75ea5ff9fc05ba90b531b4f9f.wasm" + "tx_ibc.wasm": "tx_ibc.a7cd11392c0f977a30effeb373c70de6d1365741da166cf7e871c92b166075dc.wasm", + "tx_init_account.wasm": "tx_init_account.844517c18297a87fbc5dccf47e21e4731db5f72ff3b8bfa49fda5037e56e7c93.wasm", + "tx_init_nft.wasm": "tx_init_nft.bf1e9e2e69044f551722c990fda480f45b38aca59a2f5f0f264ea5e198d36fda.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.668b6cfe3641980c6ed398ded6c323e52d5b3ad6d08ad1d67568585ddd72c8d8.wasm", + "tx_init_validator.wasm": "tx_init_validator.2af86bcf16eaa3551f1022cfb605c0e7d25ebcf22ca2df50e6020db331e91ab9.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.17505172a619a49e1af50638023dd0a69ec4b3471c950e040f9e1859e7626327.wasm", + "tx_transfer.wasm": "tx_transfer.8b5b381c7d6d45a68222e3264f733be7005fb9251235b7055475d44c2a5cb146.wasm", + "tx_unbond.wasm": "tx_unbond.b96655ad430461abc787edabf93ba2ae503c8a5b4b64d496fd6a6c577a6138d3.wasm", + "tx_update_vp.wasm": "tx_update_vp.3b709f301e55cb970ec1df98c85c2486561c3243ab1c191d3e078ee345b8b93a.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.8e4d5f40390b9ddac71d8d6c6ae7245ed78103c5a2f835b7a337b6c75b1e3187.wasm", + "tx_withdraw.wasm": "tx_withdraw.54e9d9257c8e00c856b2f5680edc53f21357f5da5337e129e86fe57ee47c4a96.wasm", + "vp_nft.wasm": "vp_nft.03b0a64aa71f7d4e34d18c27d0fc821ebcdd4b5c3a5878c71b2c8a47923854f5.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.10211e52c82ca4377ef87db386c1a0a833327127ae7407ed891f7b84a228fdf7.wasm", + "vp_token.wasm": "vp_token.a955c30bb91c6480360b1d87197bd6df5655260f580747d07e6787b2d93dfe80.wasm", + "vp_user.wasm": "vp_user.2c1f06168ea914dc50ed52b57c6d697a684a8e2aa714c4d0a78458e9171fb2b8.wasm" } \ No newline at end of file From a9c5f7bbf5db1315d9f78da4d8e038a7766ffbc3 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 17 Oct 2022 09:44:40 -0400 Subject: [PATCH 186/197] changelog: add #547 --- .changelog/unreleased/improvements/547-multistore-refactor.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/547-multistore-refactor.md diff --git a/.changelog/unreleased/improvements/547-multistore-refactor.md b/.changelog/unreleased/improvements/547-multistore-refactor.md new file mode 100644 index 0000000000..2aeca6ccfa --- /dev/null +++ b/.changelog/unreleased/improvements/547-multistore-refactor.md @@ -0,0 +1,2 @@ +- Extend Merkle tree storage to support multiple Merkle trees with a uniform + interface. ([#547](https://github.com/anoma/namada/pull/547)) \ No newline at end of file From f25f33538aabe555abd32ea4acf643127aa5f80b Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 17 Oct 2022 10:06:07 -0400 Subject: [PATCH 187/197] Namada 0.8.0 --- .../bug-fixes/1099-wasm-reading.md | 0 .../bug-fixes/1249-fix-shell-last-epoch.md | 0 .../bug-fixes/279-new-merkle-tree.md | 0 .../bug-fixes/326-fix-validator-raw-hash.md | 0 .../384-fix-new-epoch-start-height.md | 0 .../bug-fixes/419-fix-rustdoc.md | 0 .../bug-fixes/594-fix-pred-epoch-height.md | 0 .../features/132-multitoken-transfer.md | 0 .../features/503-lazy-vec-and-map.md | 0 .../1093-unify-native-and-wasm-vp.md | 0 .../1138-change-wallet-bihashmap.md | 0 .../1148-allow-absolute-wasm-dir.md | 0 .../1159-anomac-download-wasms.md | 0 .../improvements/1161-anomaw-address-find.md | 0 .../improvements/1168-pbkdf-iterations.md | 0 .../improvements/1176-genesis-config-error.md | 0 .../1231-refactor-ledger-run-with-cleanup.md | 0 .../1248-remove-evidence-params.md | 0 .../240-host-env-vp-write-check.md | 0 .../improvements/318-refactor-pos-vp.md | 0 .../324-common-read-storage-trait.md | 0 .../331-common-write-storage-trait.md | 0 .../334-refactor-storage-read-write.md | 0 .../335-refactor-storage-prefix-iter.md | 0 .../improvements/337-wasm-cargo-target-dir.md | 0 .../380-vp-env-pre-post-via-storage-api.md | 0 .../improvements/409-sorted-prefix-iter.md | 0 .../465-vp-tx-env-conrete-error.md | 0 .../improvements/467-governance-fixes.md | 0 .../518-mdbook-admonish-for-specs.md | 0 .../improvements/547-multistore-refactor.md | 0 ...605-fix-transaction-gas-exceededed-typo.md | 0 .../miscellaneous/1096-wasm-workspace.md | 0 .../miscellaneous/1243-debug-wasm-build.md | 0 .../miscellaneous/452-update-rocksdb.md | 0 .../493-remove-intent-gossiper.md | 0 .changelog/v0.8.0/summary.md | 1 + .../testing/1221-e2e-keep-temp-fix.md | 0 .../testing/462-pos-tx-tests.md | 0 .../testing/590-fix-tx-bond-test-condition.md | 0 CHANGELOG.md | 103 ++++++++++++++++++ Cargo.lock | 18 +-- apps/Cargo.toml | 2 +- encoding_spec/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- proof_of_stake/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- tx_prelude/Cargo.toml | 2 +- vm_env/Cargo.toml | 2 +- vp_prelude/Cargo.toml | 2 +- wasm/Cargo.lock | 20 ++-- wasm/checksums.json | 30 ++--- wasm/tx_template/Cargo.toml | 2 +- wasm/vp_template/Cargo.toml | 2 +- wasm/wasm_source/Cargo.toml | 2 +- wasm_for_tests/tx_memory_limit.wasm | Bin 135517 -> 135502 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 237558 -> 237898 bytes wasm_for_tests/tx_no_op.wasm | Bin 25030 -> 25027 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 212123 -> 212404 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 154935 -> 154989 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 228357 -> 228604 bytes wasm_for_tests/vp_always_false.wasm | Bin 160645 -> 161050 bytes wasm_for_tests/vp_always_true.wasm | Bin 160647 -> 160341 bytes wasm_for_tests/vp_eval.wasm | Bin 161591 -> 162008 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 163308 -> 163344 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 176382 -> 176398 bytes wasm_for_tests/wasm_source/Cargo.lock | 16 +-- wasm_for_tests/wasm_source/Cargo.toml | 2 +- 69 files changed, 159 insertions(+), 55 deletions(-) rename .changelog/{unreleased => v0.8.0}/bug-fixes/1099-wasm-reading.md (100%) rename .changelog/{unreleased => v0.8.0}/bug-fixes/1249-fix-shell-last-epoch.md (100%) rename .changelog/{unreleased => v0.8.0}/bug-fixes/279-new-merkle-tree.md (100%) rename .changelog/{unreleased => v0.8.0}/bug-fixes/326-fix-validator-raw-hash.md (100%) rename .changelog/{unreleased => v0.8.0}/bug-fixes/384-fix-new-epoch-start-height.md (100%) rename .changelog/{unreleased => v0.8.0}/bug-fixes/419-fix-rustdoc.md (100%) rename .changelog/{unreleased => v0.8.0}/bug-fixes/594-fix-pred-epoch-height.md (100%) rename .changelog/{unreleased => v0.8.0}/features/132-multitoken-transfer.md (100%) rename .changelog/{unreleased => v0.8.0}/features/503-lazy-vec-and-map.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/1093-unify-native-and-wasm-vp.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/1138-change-wallet-bihashmap.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/1148-allow-absolute-wasm-dir.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/1159-anomac-download-wasms.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/1161-anomaw-address-find.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/1168-pbkdf-iterations.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/1176-genesis-config-error.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/1231-refactor-ledger-run-with-cleanup.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/1248-remove-evidence-params.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/240-host-env-vp-write-check.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/318-refactor-pos-vp.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/324-common-read-storage-trait.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/331-common-write-storage-trait.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/334-refactor-storage-read-write.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/335-refactor-storage-prefix-iter.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/337-wasm-cargo-target-dir.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/380-vp-env-pre-post-via-storage-api.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/409-sorted-prefix-iter.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/465-vp-tx-env-conrete-error.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/467-governance-fixes.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/518-mdbook-admonish-for-specs.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/547-multistore-refactor.md (100%) rename .changelog/{unreleased => v0.8.0}/improvements/605-fix-transaction-gas-exceededed-typo.md (100%) rename .changelog/{unreleased => v0.8.0}/miscellaneous/1096-wasm-workspace.md (100%) rename .changelog/{unreleased => v0.8.0}/miscellaneous/1243-debug-wasm-build.md (100%) rename .changelog/{unreleased => v0.8.0}/miscellaneous/452-update-rocksdb.md (100%) rename .changelog/{unreleased => v0.8.0}/miscellaneous/493-remove-intent-gossiper.md (100%) create mode 100644 .changelog/v0.8.0/summary.md rename .changelog/{unreleased => v0.8.0}/testing/1221-e2e-keep-temp-fix.md (100%) rename .changelog/{unreleased => v0.8.0}/testing/462-pos-tx-tests.md (100%) rename .changelog/{unreleased => v0.8.0}/testing/590-fix-tx-bond-test-condition.md (100%) diff --git a/.changelog/unreleased/bug-fixes/1099-wasm-reading.md b/.changelog/v0.8.0/bug-fixes/1099-wasm-reading.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1099-wasm-reading.md rename to .changelog/v0.8.0/bug-fixes/1099-wasm-reading.md diff --git a/.changelog/unreleased/bug-fixes/1249-fix-shell-last-epoch.md b/.changelog/v0.8.0/bug-fixes/1249-fix-shell-last-epoch.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1249-fix-shell-last-epoch.md rename to .changelog/v0.8.0/bug-fixes/1249-fix-shell-last-epoch.md diff --git a/.changelog/unreleased/bug-fixes/279-new-merkle-tree.md b/.changelog/v0.8.0/bug-fixes/279-new-merkle-tree.md similarity index 100% rename from .changelog/unreleased/bug-fixes/279-new-merkle-tree.md rename to .changelog/v0.8.0/bug-fixes/279-new-merkle-tree.md diff --git a/.changelog/unreleased/bug-fixes/326-fix-validator-raw-hash.md b/.changelog/v0.8.0/bug-fixes/326-fix-validator-raw-hash.md similarity index 100% rename from .changelog/unreleased/bug-fixes/326-fix-validator-raw-hash.md rename to .changelog/v0.8.0/bug-fixes/326-fix-validator-raw-hash.md diff --git a/.changelog/unreleased/bug-fixes/384-fix-new-epoch-start-height.md b/.changelog/v0.8.0/bug-fixes/384-fix-new-epoch-start-height.md similarity index 100% rename from .changelog/unreleased/bug-fixes/384-fix-new-epoch-start-height.md rename to .changelog/v0.8.0/bug-fixes/384-fix-new-epoch-start-height.md diff --git a/.changelog/unreleased/bug-fixes/419-fix-rustdoc.md b/.changelog/v0.8.0/bug-fixes/419-fix-rustdoc.md similarity index 100% rename from .changelog/unreleased/bug-fixes/419-fix-rustdoc.md rename to .changelog/v0.8.0/bug-fixes/419-fix-rustdoc.md diff --git a/.changelog/unreleased/bug-fixes/594-fix-pred-epoch-height.md b/.changelog/v0.8.0/bug-fixes/594-fix-pred-epoch-height.md similarity index 100% rename from .changelog/unreleased/bug-fixes/594-fix-pred-epoch-height.md rename to .changelog/v0.8.0/bug-fixes/594-fix-pred-epoch-height.md diff --git a/.changelog/unreleased/features/132-multitoken-transfer.md b/.changelog/v0.8.0/features/132-multitoken-transfer.md similarity index 100% rename from .changelog/unreleased/features/132-multitoken-transfer.md rename to .changelog/v0.8.0/features/132-multitoken-transfer.md diff --git a/.changelog/unreleased/features/503-lazy-vec-and-map.md b/.changelog/v0.8.0/features/503-lazy-vec-and-map.md similarity index 100% rename from .changelog/unreleased/features/503-lazy-vec-and-map.md rename to .changelog/v0.8.0/features/503-lazy-vec-and-map.md diff --git a/.changelog/unreleased/improvements/1093-unify-native-and-wasm-vp.md b/.changelog/v0.8.0/improvements/1093-unify-native-and-wasm-vp.md similarity index 100% rename from .changelog/unreleased/improvements/1093-unify-native-and-wasm-vp.md rename to .changelog/v0.8.0/improvements/1093-unify-native-and-wasm-vp.md diff --git a/.changelog/unreleased/improvements/1138-change-wallet-bihashmap.md b/.changelog/v0.8.0/improvements/1138-change-wallet-bihashmap.md similarity index 100% rename from .changelog/unreleased/improvements/1138-change-wallet-bihashmap.md rename to .changelog/v0.8.0/improvements/1138-change-wallet-bihashmap.md diff --git a/.changelog/unreleased/improvements/1148-allow-absolute-wasm-dir.md b/.changelog/v0.8.0/improvements/1148-allow-absolute-wasm-dir.md similarity index 100% rename from .changelog/unreleased/improvements/1148-allow-absolute-wasm-dir.md rename to .changelog/v0.8.0/improvements/1148-allow-absolute-wasm-dir.md diff --git a/.changelog/unreleased/improvements/1159-anomac-download-wasms.md b/.changelog/v0.8.0/improvements/1159-anomac-download-wasms.md similarity index 100% rename from .changelog/unreleased/improvements/1159-anomac-download-wasms.md rename to .changelog/v0.8.0/improvements/1159-anomac-download-wasms.md diff --git a/.changelog/unreleased/improvements/1161-anomaw-address-find.md b/.changelog/v0.8.0/improvements/1161-anomaw-address-find.md similarity index 100% rename from .changelog/unreleased/improvements/1161-anomaw-address-find.md rename to .changelog/v0.8.0/improvements/1161-anomaw-address-find.md diff --git a/.changelog/unreleased/improvements/1168-pbkdf-iterations.md b/.changelog/v0.8.0/improvements/1168-pbkdf-iterations.md similarity index 100% rename from .changelog/unreleased/improvements/1168-pbkdf-iterations.md rename to .changelog/v0.8.0/improvements/1168-pbkdf-iterations.md diff --git a/.changelog/unreleased/improvements/1176-genesis-config-error.md b/.changelog/v0.8.0/improvements/1176-genesis-config-error.md similarity index 100% rename from .changelog/unreleased/improvements/1176-genesis-config-error.md rename to .changelog/v0.8.0/improvements/1176-genesis-config-error.md diff --git a/.changelog/unreleased/improvements/1231-refactor-ledger-run-with-cleanup.md b/.changelog/v0.8.0/improvements/1231-refactor-ledger-run-with-cleanup.md similarity index 100% rename from .changelog/unreleased/improvements/1231-refactor-ledger-run-with-cleanup.md rename to .changelog/v0.8.0/improvements/1231-refactor-ledger-run-with-cleanup.md diff --git a/.changelog/unreleased/improvements/1248-remove-evidence-params.md b/.changelog/v0.8.0/improvements/1248-remove-evidence-params.md similarity index 100% rename from .changelog/unreleased/improvements/1248-remove-evidence-params.md rename to .changelog/v0.8.0/improvements/1248-remove-evidence-params.md diff --git a/.changelog/unreleased/improvements/240-host-env-vp-write-check.md b/.changelog/v0.8.0/improvements/240-host-env-vp-write-check.md similarity index 100% rename from .changelog/unreleased/improvements/240-host-env-vp-write-check.md rename to .changelog/v0.8.0/improvements/240-host-env-vp-write-check.md diff --git a/.changelog/unreleased/improvements/318-refactor-pos-vp.md b/.changelog/v0.8.0/improvements/318-refactor-pos-vp.md similarity index 100% rename from .changelog/unreleased/improvements/318-refactor-pos-vp.md rename to .changelog/v0.8.0/improvements/318-refactor-pos-vp.md diff --git a/.changelog/unreleased/improvements/324-common-read-storage-trait.md b/.changelog/v0.8.0/improvements/324-common-read-storage-trait.md similarity index 100% rename from .changelog/unreleased/improvements/324-common-read-storage-trait.md rename to .changelog/v0.8.0/improvements/324-common-read-storage-trait.md diff --git a/.changelog/unreleased/improvements/331-common-write-storage-trait.md b/.changelog/v0.8.0/improvements/331-common-write-storage-trait.md similarity index 100% rename from .changelog/unreleased/improvements/331-common-write-storage-trait.md rename to .changelog/v0.8.0/improvements/331-common-write-storage-trait.md diff --git a/.changelog/unreleased/improvements/334-refactor-storage-read-write.md b/.changelog/v0.8.0/improvements/334-refactor-storage-read-write.md similarity index 100% rename from .changelog/unreleased/improvements/334-refactor-storage-read-write.md rename to .changelog/v0.8.0/improvements/334-refactor-storage-read-write.md diff --git a/.changelog/unreleased/improvements/335-refactor-storage-prefix-iter.md b/.changelog/v0.8.0/improvements/335-refactor-storage-prefix-iter.md similarity index 100% rename from .changelog/unreleased/improvements/335-refactor-storage-prefix-iter.md rename to .changelog/v0.8.0/improvements/335-refactor-storage-prefix-iter.md diff --git a/.changelog/unreleased/improvements/337-wasm-cargo-target-dir.md b/.changelog/v0.8.0/improvements/337-wasm-cargo-target-dir.md similarity index 100% rename from .changelog/unreleased/improvements/337-wasm-cargo-target-dir.md rename to .changelog/v0.8.0/improvements/337-wasm-cargo-target-dir.md diff --git a/.changelog/unreleased/improvements/380-vp-env-pre-post-via-storage-api.md b/.changelog/v0.8.0/improvements/380-vp-env-pre-post-via-storage-api.md similarity index 100% rename from .changelog/unreleased/improvements/380-vp-env-pre-post-via-storage-api.md rename to .changelog/v0.8.0/improvements/380-vp-env-pre-post-via-storage-api.md diff --git a/.changelog/unreleased/improvements/409-sorted-prefix-iter.md b/.changelog/v0.8.0/improvements/409-sorted-prefix-iter.md similarity index 100% rename from .changelog/unreleased/improvements/409-sorted-prefix-iter.md rename to .changelog/v0.8.0/improvements/409-sorted-prefix-iter.md diff --git a/.changelog/unreleased/improvements/465-vp-tx-env-conrete-error.md b/.changelog/v0.8.0/improvements/465-vp-tx-env-conrete-error.md similarity index 100% rename from .changelog/unreleased/improvements/465-vp-tx-env-conrete-error.md rename to .changelog/v0.8.0/improvements/465-vp-tx-env-conrete-error.md diff --git a/.changelog/unreleased/improvements/467-governance-fixes.md b/.changelog/v0.8.0/improvements/467-governance-fixes.md similarity index 100% rename from .changelog/unreleased/improvements/467-governance-fixes.md rename to .changelog/v0.8.0/improvements/467-governance-fixes.md diff --git a/.changelog/unreleased/improvements/518-mdbook-admonish-for-specs.md b/.changelog/v0.8.0/improvements/518-mdbook-admonish-for-specs.md similarity index 100% rename from .changelog/unreleased/improvements/518-mdbook-admonish-for-specs.md rename to .changelog/v0.8.0/improvements/518-mdbook-admonish-for-specs.md diff --git a/.changelog/unreleased/improvements/547-multistore-refactor.md b/.changelog/v0.8.0/improvements/547-multistore-refactor.md similarity index 100% rename from .changelog/unreleased/improvements/547-multistore-refactor.md rename to .changelog/v0.8.0/improvements/547-multistore-refactor.md diff --git a/.changelog/unreleased/improvements/605-fix-transaction-gas-exceededed-typo.md b/.changelog/v0.8.0/improvements/605-fix-transaction-gas-exceededed-typo.md similarity index 100% rename from .changelog/unreleased/improvements/605-fix-transaction-gas-exceededed-typo.md rename to .changelog/v0.8.0/improvements/605-fix-transaction-gas-exceededed-typo.md diff --git a/.changelog/unreleased/miscellaneous/1096-wasm-workspace.md b/.changelog/v0.8.0/miscellaneous/1096-wasm-workspace.md similarity index 100% rename from .changelog/unreleased/miscellaneous/1096-wasm-workspace.md rename to .changelog/v0.8.0/miscellaneous/1096-wasm-workspace.md diff --git a/.changelog/unreleased/miscellaneous/1243-debug-wasm-build.md b/.changelog/v0.8.0/miscellaneous/1243-debug-wasm-build.md similarity index 100% rename from .changelog/unreleased/miscellaneous/1243-debug-wasm-build.md rename to .changelog/v0.8.0/miscellaneous/1243-debug-wasm-build.md diff --git a/.changelog/unreleased/miscellaneous/452-update-rocksdb.md b/.changelog/v0.8.0/miscellaneous/452-update-rocksdb.md similarity index 100% rename from .changelog/unreleased/miscellaneous/452-update-rocksdb.md rename to .changelog/v0.8.0/miscellaneous/452-update-rocksdb.md diff --git a/.changelog/unreleased/miscellaneous/493-remove-intent-gossiper.md b/.changelog/v0.8.0/miscellaneous/493-remove-intent-gossiper.md similarity index 100% rename from .changelog/unreleased/miscellaneous/493-remove-intent-gossiper.md rename to .changelog/v0.8.0/miscellaneous/493-remove-intent-gossiper.md diff --git a/.changelog/v0.8.0/summary.md b/.changelog/v0.8.0/summary.md new file mode 100644 index 0000000000..b23b8369d9 --- /dev/null +++ b/.changelog/v0.8.0/summary.md @@ -0,0 +1 @@ +Namada 0.8.0 is a regular minor release. diff --git a/.changelog/unreleased/testing/1221-e2e-keep-temp-fix.md b/.changelog/v0.8.0/testing/1221-e2e-keep-temp-fix.md similarity index 100% rename from .changelog/unreleased/testing/1221-e2e-keep-temp-fix.md rename to .changelog/v0.8.0/testing/1221-e2e-keep-temp-fix.md diff --git a/.changelog/unreleased/testing/462-pos-tx-tests.md b/.changelog/v0.8.0/testing/462-pos-tx-tests.md similarity index 100% rename from .changelog/unreleased/testing/462-pos-tx-tests.md rename to .changelog/v0.8.0/testing/462-pos-tx-tests.md diff --git a/.changelog/unreleased/testing/590-fix-tx-bond-test-condition.md b/.changelog/v0.8.0/testing/590-fix-tx-bond-test-condition.md similarity index 100% rename from .changelog/unreleased/testing/590-fix-tx-bond-test-condition.md rename to .changelog/v0.8.0/testing/590-fix-tx-bond-test-condition.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 12656661af..fd88cff1d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,108 @@ # CHANGELOG +## v0.8.0 + +Namada 0.8.0 is a regular minor release. + +### BUG FIXES + +- Switch to a alternative sparse merkle tree implementation for IBC sub-tree + to be able to support proofs compatible with the current version of ICS23 + ([#279](https://github.com/anoma/namada/pull/279)) +- Fixed validator raw hash corresponding to validator address in Tendermint + ([#326](https://github.com/anoma/namada/pull/326)) +- Fix the value recorded for epoch start block height. + ([#384](https://github.com/anoma/namada/issues/384)) +- Fix the rustdoc build. ([#419](https://github.com/anoma/namada/issues/419)) +- Fix the value recorded for epoch start block height. + ([#594](https://github.com/anoma/namada/pull/594)) +- Make read_wasm return an error instead of exiting in InitChain + ([#1099](https://github.com/anoma/anoma/pull/1099)) +- 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)) + +### FEATURES + +- Added multitoken transfer and query for bridges + ([#132](https://github.com/anoma/namada/issues/132)) +- Added lazy vector and map data structures for ledger storage + ([#503](https://github.com/anoma/namada/pull/503)) + +### IMPROVEMENTS + +- Validate WASM code of validity predicates written by transactions. + ([#240](https://github.com/anoma/anoma/pull/240)) +- Refactored PoS VP logic ([#318](https://github.com/anoma/namada/pull/318)) +- Added a StorageRead trait for a common interface for VPs prior and posterior + state, transactions and direct storage access for protocol and RPC handlers + ([#324](https://github.com/anoma/namada/pull/324)) +- Added a StorageWrite trait for a common interface for transactions and direct + storage access for protocol ([#331](https://github.com/anoma/namada/pull/331)) +- Re-use encoding/decoding storage write/read and handle any errors + ([#334](https://github.com/anoma/namada/pull/334)) +- Added a simpler prefix iterator API that returns `std::iter::Iterator` with + the storage keys parsed and a variant that also decodes stored values with + Borsh ([#335](https://github.com/anoma/namada/pull/335)) +- Handles the case where a custom `$CARGO_TARGET_DIR` is set during WASM build + ([#337](https://github.com/anoma/anoma/pull/337)) +- Added `pre/post` methods into `trait VpEnv` that return objects implementing + `trait StorageRead` for re-use of library code written on top of `StorageRead` + inside validity predicates. ([#380](https://github.com/anoma/namada/pull/380)) +- Fix order of prefix iterator to be sorted by storage + keys and add support for a reverse order prefix iterator. + ([#409](https://github.com/anoma/namada/issues/409)) +- Re-use `storage_api::Error` type that supports wrapping custom error in `VpEnv` and `TxEnv` traits. + ([#465](https://github.com/anoma/namada/pull/465)) +- Fixed governance parameters, tally, tx whitelist and renamed treasury + ([#467](https://github.com/anoma/namada/issues/467)) +- Enable mdbook-admonish for the specs + ([#518](https://github.com/anoma/namada/issues/518)) +- Extend Merkle tree storage to support multiple Merkle trees with a uniform + interface. ([#547](https://github.com/anoma/namada/pull/547)) +- Fix a typo in an error ([#605](https://github.com/anoma/namada/issues/605)) +- Added WASM transaction and validity predicate `Ctx` with methods for host + environment functions to unify the interface of native VPs and WASM VPs under + `trait VpEnv` ([#1093](https://github.com/anoma/anoma/pull/1093)) +- Allows simple retrival of aliases from addresses in the wallet without + the need for multiple hashmaps. This is the first step to improving the + UI if one wants to show aliases when fetching addresses from anoma wallet + ([#1138](https://github.com/anoma/anoma/pull/1138)) +- Allow specifying an absolute path for the wasm directory + ([#1148](https://github.com/anoma/anoma/issues/1148)) +- Add functionality to anomac to download wasms for a given chain + ([#1159](https://github.com/anoma/anoma/pull/1159)) +- Improved CLI experience for 'anomaw address find' + ([#1161](https://github.com/anoma/anoma/pull/1161)) +- Wallet: Increase the number of iterations used for keys encryption to the + recommended value. ([#1168](https://github.com/anoma/anoma/issues/1168)) +- Improve the error message that is displayed when anoma binaries are run without + having joined a chain ([#1176](https://github.com/anoma/anoma/pull/1176)) +- Refactored ledger startup code + ([#1231](https://github.com/anoma/anoma/pull/1231)) +- Replace Tendermint consensus evidence parameters with + application level evidence filter for outdated evidence. + ([#1248](https://github.com/anoma/anoma/pull/1248)) + +### MISCELLANEOUS + +- Updated rockDB dependency to 0.19.0 and enabled its jemalloc feature. + ([#452](https://github.com/anoma/namada/pull/452)) +- Removed intent gossiper and matchmaker code + ([#493](https://github.com/anoma/namada/issues/493)) +- Use a cargo workspace for some of our wasm crates + ([#1096](https://github.com/anoma/anoma/pull/1096)) +- Added a make recipe to build WASM in debug mode with `make debug-wasm-scripts` + ([#1243](https://github.com/anoma/anoma/pull/1243)) + +### TESTING + +- Test PoS transaction for bonding, unbonding and withdrawal. Fixed an issue + found on unbonding. ([#462](https://github.com/anoma/anoma/issues/462)) +- Fix a condition in tx_bond test that causes a false negative result + ([#590](https://github.com/anoma/namada/pull/590)) +- Fixed ANOMA_E2E_KEEP_TEMP=true to work in e2e::setup::network + ([#1221](https://github.com/anoma/anoma/issues/1221)) + ## v0.7.1 Namada 0.7.1 is a patch release of the Namada software, continuing the diff --git a/Cargo.lock b/Cargo.lock index 748e68fbd0..74fe790a69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3018,7 +3018,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.1" +version = "0.8.0" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3075,7 +3075,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.7.1" +version = "0.8.0" dependencies = [ "ark-serialize", "ark-std", @@ -3154,7 +3154,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "itertools", @@ -3165,7 +3165,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.1" +version = "0.8.0" dependencies = [ "quote", "syn", @@ -3173,7 +3173,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "derivative", @@ -3183,7 +3183,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.1" +version = "0.8.0" dependencies = [ "assert_cmd", "borsh", @@ -3217,7 +3217,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "namada", @@ -3229,7 +3229,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "namada", @@ -3237,7 +3237,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "namada", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 856b8a9e46..47fe704408 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_apps" readme = "../README.md" resolver = "2" -version = "0.7.1" +version = "0.8.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/encoding_spec/Cargo.toml b/encoding_spec/Cargo.toml index 428db752b7..625f7a83d6 100644 --- a/encoding_spec/Cargo.toml +++ b/encoding_spec/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_encoding_spec" readme = "../README.md" resolver = "2" -version = "0.7.1" +version = "0.8.0" [features] default = [] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 19cb20fd9d..9f32c04b4b 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_macros" resolver = "2" -version = "0.7.1" +version = "0.8.0" [lib] proc-macro = true diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 5daa9ad1e3..21d4cc0b21 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_proof_of_stake" readme = "../README.md" resolver = "2" -version = "0.7.1" +version = "0.8.0" [features] default = [] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index a32da772da..946dd6268e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada" resolver = "2" -version = "0.7.1" +version = "0.8.0" # 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 e98927d4f3..0f927adf25 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tests" resolver = "2" -version = "0.7.1" +version = "0.8.0" [features] default = ["wasm-runtime"] diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 2b3c34e7f9..23b73d6659 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tx_prelude" resolver = "2" -version = "0.7.1" +version = "0.8.0" [features] default = [] diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index 0e48665e5a..7e7b55a058 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vm_env" resolver = "2" -version = "0.7.1" +version = "0.8.0" [features] default = [] diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index 00273c6622..d9dadec9be 100644 --- a/vp_prelude/Cargo.toml +++ b/vp_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vp_prelude" resolver = "2" -version = "0.7.1" +version = "0.8.0" [features] default = [] diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 9b5fff114e..9287c81fc4 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1489,7 +1489,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.1" +version = "0.8.0" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1538,7 +1538,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.1" +version = "0.8.0" dependencies = [ "quote", "syn", @@ -1546,7 +1546,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "derivative", @@ -1556,7 +1556,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.1" +version = "0.8.0" dependencies = [ "chrono", "concat-idents", @@ -1575,7 +1575,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "namada", @@ -1587,7 +1587,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "namada", @@ -1595,7 +1595,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "namada", @@ -1607,7 +1607,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "getrandom", @@ -2806,7 +2806,7 @@ dependencies = [ [[package]] name = "tx_template" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "getrandom", @@ -2891,7 +2891,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.7.1" +version = "0.8.0" dependencies = [ "borsh", "getrandom", diff --git a/wasm/checksums.json b/wasm/checksums.json index 8767ca510c..8ddbe1dc85 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,18 +1,18 @@ { - "tx_bond.wasm": "tx_bond.f0c520b6562e23785df2471c326e0d387b57ecb3ec0313b4a845d464a43dd596.wasm", - "tx_ibc.wasm": "tx_ibc.a7cd11392c0f977a30effeb373c70de6d1365741da166cf7e871c92b166075dc.wasm", - "tx_init_account.wasm": "tx_init_account.844517c18297a87fbc5dccf47e21e4731db5f72ff3b8bfa49fda5037e56e7c93.wasm", - "tx_init_nft.wasm": "tx_init_nft.bf1e9e2e69044f551722c990fda480f45b38aca59a2f5f0f264ea5e198d36fda.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.668b6cfe3641980c6ed398ded6c323e52d5b3ad6d08ad1d67568585ddd72c8d8.wasm", - "tx_init_validator.wasm": "tx_init_validator.2af86bcf16eaa3551f1022cfb605c0e7d25ebcf22ca2df50e6020db331e91ab9.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.17505172a619a49e1af50638023dd0a69ec4b3471c950e040f9e1859e7626327.wasm", - "tx_transfer.wasm": "tx_transfer.8b5b381c7d6d45a68222e3264f733be7005fb9251235b7055475d44c2a5cb146.wasm", - "tx_unbond.wasm": "tx_unbond.b96655ad430461abc787edabf93ba2ae503c8a5b4b64d496fd6a6c577a6138d3.wasm", + "tx_bond.wasm": "tx_bond.865260ca3ca9ed4c611cc5cbc8b4d864232f447f06c59afa0b7c1c63c3fa897c.wasm", + "tx_ibc.wasm": "tx_ibc.1360fdf276c4591d937aaac3bb40ddb573abeba382651474ae23138aac65c3e5.wasm", + "tx_init_account.wasm": "tx_init_account.72d9e1daa43998ce617eafba1547b34f26b128f0fb6e02cfdbc85ecf1f345fd4.wasm", + "tx_init_nft.wasm": "tx_init_nft.22a886305fda24438dbf3fc0f864ee32db4a398bf748edd2fddba1fc4679bc35.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.649bde547179ebee5449f5954425cd266c5063fae97f98f06de327387e8898ba.wasm", + "tx_init_validator.wasm": "tx_init_validator.d40077c1bf1263e1e8e42f54cd893473c5d9c1f395e5d30f44a34d98d9b8dde4.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.78b3a43c5c5b4362cb3023c8e7e25755447e9022952306a62f2d8aab6e99dbee.wasm", + "tx_transfer.wasm": "tx_transfer.4c06f6af1f008fccdd42fefdc8015adea9fa60f802603a8be149ec2c6421656e.wasm", + "tx_unbond.wasm": "tx_unbond.c3d54895e1a271c86838d54d821d52b5bf5764928811cff30767a4445ebbf653.wasm", "tx_update_vp.wasm": "tx_update_vp.3b709f301e55cb970ec1df98c85c2486561c3243ab1c191d3e078ee345b8b93a.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.8e4d5f40390b9ddac71d8d6c6ae7245ed78103c5a2f835b7a337b6c75b1e3187.wasm", - "tx_withdraw.wasm": "tx_withdraw.54e9d9257c8e00c856b2f5680edc53f21357f5da5337e129e86fe57ee47c4a96.wasm", - "vp_nft.wasm": "vp_nft.03b0a64aa71f7d4e34d18c27d0fc821ebcdd4b5c3a5878c71b2c8a47923854f5.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.10211e52c82ca4377ef87db386c1a0a833327127ae7407ed891f7b84a228fdf7.wasm", - "vp_token.wasm": "vp_token.a955c30bb91c6480360b1d87197bd6df5655260f580747d07e6787b2d93dfe80.wasm", - "vp_user.wasm": "vp_user.2c1f06168ea914dc50ed52b57c6d697a684a8e2aa714c4d0a78458e9171fb2b8.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.671b46a77d03d10ca4e406f2bbc48adba0e541272a2963fd88d3a8c60e888fcd.wasm", + "tx_withdraw.wasm": "tx_withdraw.39e0012e110b19c7000400d11adc355bd8e89605b3c9ca10017c4766eed0ad69.wasm", + "vp_nft.wasm": "vp_nft.b23df820ae3faa13c9ed73121886b712fa0c613b7f43567f528cda05ef75fab6.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.45c740dcfa6e803d55a56cb51b02f2086147dfef3e955c0e8c3f18bf93ae0227.wasm", + "vp_token.wasm": "vp_token.74bca3c9999640c39bbd1c1364b65ffe8086e2d3ed124413251295d527326b57.wasm", + "vp_user.wasm": "vp_user.1c75ea1e55eb1244f023a525f9e20f0d34e52aebf0bd008dcf122fdc13bdf16a.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index fdc24f4efa..d207438d16 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.7.1" +version = "0.8.0" [lib] crate-type = ["cdylib"] diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index a3fdb02769..e01ad1b22f 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.7.1" +version = "0.8.0" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 81e72c6dda..5291d81390 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm" resolver = "2" -version = "0.7.1" +version = "0.8.0" [lib] crate-type = ["cdylib"] diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index e845bfd6f14179ab08fd931b7a1555bf5777e6f5..7fbbaaac3b84cdb1ddd6c695de571de7de45fa5a 100755 GIT binary patch delta 1237 zcmZuweP~-%6u;-b_mY>swSCQ!CTZH`<~7~Yv|D4^rd^u4-mRrs+eJ6g3hHcEP|48Q z&TnTUn+#>L4-S6gtfPp3h=LF8kcv|ax*?9@L}3$MnWCFe{L6p#hv&VP!NAAkcfRgD zobx;9{&GtH{*->aD>pp`WhjqnW#=P>u~nUoDbudUKbol zmBKiH!ZZLCs(k>=`8jX}2;Glsp!+m9@$&|$)_w^7)r|SsGfcirU`i}`AHK<6@@@;wx*>~IGQkCxdP%4P z%&nN$)1H+WPU#6HWL(2D5VSwk{{fh{XDW^=Fpq6jQE0G-tCke)c1~Pihrc^0KyY)Q zj`C2-QkFBxjNk2p{#O7afeelWp0oc5oQ0rU855fHm4s4~8-%jgjNn4;IjF;9p}PtH z3_U|Q5k3rc_SNvOpe$DzSkqODOLYN5up~DvMZ(}DD$(7X6>%b3CShNRPI;)fQELm- z+pk%7aERiy#A&c_wrP~`deeDo;k)%u0A~feKY1Uo70i%-zeFT8Fhgig2SkE}bE!sH zj~7#d(p^urlHT013!3Z$EteJ8fVuc#6NFgu0N;w=ulV59c!L=}z9`%Ic zY>jH*;^2c2!R7ScM1&linvl0}> zrAk&*%YtcmDAQlz)XXJJ8gJoZrnSZAFvBd51G-cv2h=`6Mp!PsK&$Wrf$&8omx}OV zvV9l$@Y(hz(D7XtziIDpu=FfX3*8-LoEAf)wUBQ+x49X&n>*qFaUAa4PxwXW6~fu< zlEeRtU2hUOjoo`Vv3xi;L3lM6!?S?~^!4;X2#0$1QTw0uJR%}SoQWH)Ryz%>60xFS zA)g7>8*7ON>Vw$POO6C{6CTSa;UJ#QABiO?2bakli_wAiPEvg;*|C!RfOIp7dwRzq zX)pBt#%q#t-idqZ;KEAqlfH!n5jy{lb2P5d#95Y`LXmUik+cKyf*Z0=%AYi4lkp=C zr_=cUpud_GEf%Ha2GgA?_7>{!jr5(=+2+Dj?f(xJzbrge#R`-ahNq?~88aAO8hH{f zxb@A zR3>oej{7+q!jE^H<7@<{i}yO>Qt?yB+j!eWAhBuzeLP7guuvLh(*r-@gDHc``9}WFs(Led+4-q`G359~g@ZEFH zoqOh-?>qPVGx{%Q^pkyAJFnU919l#4IF@H@Bf#?NM%+{R!2QCzZn>s%Y-v87;)>4; zYOT*-tuPLtFbzP38XbUDUakO8+f91Xn+5$BuGZ=kHz7`T=R;-zrFFPUH++_V%46A}D1e308y zI2^HQaV|wl9x63ujX<+gvZ&4`{JQ-!XhSioDm zD#!`RYYQ|TKN1K%BDqwAj?lXoblle)10No#;OXA6mJCl)7c?ENvK9@C>&SQAML3kPyy5rDU-m3!45c%9}m6`o!CFrOgKAKfKKQ0 zq2KwYPPzOZmFcAuD`c3Rcy4qhCYRrL?Wij*%W#tfyGRkK(QnP7yk6OLPRX(c2`o;G z)!I`QixB5vhDa$Dw@rqzJn;Y(-!oYX{=a(o>Ew%kJ4d-;x>WKrW?+48V*L)fBTsko zvD`~{__uNo!cBB`JsjmWjkVUnRm&@n&Rd>w5|<(#;UU#Y+lbRWbqiFLO2UC%FW}hj uISw=U;qI#(Ch_ph0eAUs<^ohZ3wwSBTJraKP25w6A73h5Tq?j9=l=s#d^6(! diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index a44fc35c42255a3c6fce26256c661135533e67dc..d89c21a89433f90146d8de7f555b3284a9006712 100755 GIT binary patch delta 20529 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 xed?Cf8qgvySt6ML9!cm8SY3jD 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 diff --git a/wasm_for_tests/tx_no_op.wasm b/wasm_for_tests/tx_no_op.wasm index 649a1b72f152044560b29408aec863baf09a4e4b..bdab4054d9fe3a54897713c81028396ec6a24f0e 100755 GIT binary patch delta 287 zcmY+8y-EW?6ov1d|LnLBEL0F=SP6*M?2y(%4Yv^ALRg8G z!m8*q*d%tAK7ioFR5`_WIGpBttFWr@X0cHekh2gV$ACpH!zdVdIwd43WWY7!&}$tT zsdUXI$yM*+*bchS;O`^qh8lP>v2Sr>_Wl3LcnXd==FO>79_uyw!F4tMXo+1j*rF$P z5UZTwD+~wZGO$wsdtS44_1Jqm*kTk?KE}mT=`*tcm4p+?NBHH delta 286 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 aS|=JyN$^M~nzY=JPI0l<9O=@A%>M%*4R{m) delta 7604 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 N#l!aUOl3pXKLMg*Rrmk^ diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index c50ed11940ef94e27b4f26d1693415a950d6c4ee..255652118e4f7e1c071b495dd02b67c1709c87fb 100755 GIT binary patch delta 3381 zcma)83vd(H6@BmRY9)ES_8Ra{=JPCn5Ex?R4wdS!N514_QXTv~vK^@G{&&NO7N#Wm0E2j4~yv zDOR#rA-DHSaJeHqJlQSd+)6*$AbI zjY?t0a4J#YFdLk2L$4Dsy( zUfnf8&U zqPSw>VtaDGcpLn|K0#xu;WDVMxS3x|g;Pp3O@-Ooi1DeM8N((M2eJqI`-qpxoN?L9 zNIX+2$+{GK&G7F z?8H>QIpUKb)ww>Y>u4Y0lF>zjdc0qw@k628S(a~=YC}{b-jLC`?AMaHjBcfsK1R4~ zID%+;5B6bU<|;Z=B*sgAjliO~o1$LjY91EFY*G{0VEJ}v>5SCCo7%70k#5+KFcX>i ztAsK}4O35=BIBl6pL~TXawT_g8HT643%64}tgjmq)4GM1-n3(C#Ox!+Z+iQPz6e&N zH$Q?uPM?b}(M*Cf-4~i~xk%X5mM`rh8GnmmOwmneoDGPsisE>>x#777xn5yxy73)Fc z&sLlkH11k44;o)uaZb>95X13K)yR+D3gXv>q4#64t^QEGh|6QKG1fV&0>-*z#Te@Y zE5=w?4}mczea=^S%9tBG!X~R4y4_~Q=ysgE6*8@-Ac>r<-&#=39C80+snux;EvS_=938Bm))gOOVJQT}MN zFaIl=Rmz!%Rl0HTcvx}JEgP57+CzvJy~~D-Rq25n1-Y~g?1fX<^&apS{uP;7GNF{x za&zrdIn;HdTRc7s@PWbURm9m;=4082u64udiT`9<-hh)u)hyBj|0t@-<8dD5aLb(F z6h$4(7X#_hG>q~XbwR2+e}pxH91kfz^4??#sK3g)FJ5!HxcIO9FbA|`}<<%0G4 z79%tD;t;jXV)%}P@ol5wR7p}Yr<~~xE*vj7X@WS%4WiyQKP=fvVS6YUHkOVsyGv6P zVSUy}lcl=}Dh`SixZ@wDa^hjSTQX>M8>m$cICr3_DlEp&=T&>+gvraZ*HSSM>9ZG@ zz!&FzU?0bA1`TEsZp$acO^vh;BW*2uA@y>Y6C36sS7}u2QZB)nB~^4ce7$5`o>PP) zZbke!47tsZHA0YP9NmOPIF3KunPD(P;!c>?Fo!zfNJ9>Fz?JCl7Y)s!WI!Vo=m-UzvuV+uYY;CR+Ui*_nJn+m0$!`7W<)oWt_q?uviA1 zFZRN$rfqZ^%x&Z!ix21Z(eJr)4>c>-qt1bKb4-NNQw?w`Drw$$&F0WZ+NpnEw z|40*Tp{@VLEhY(iTPoDHKSy6O542yDN8o*myoC?LxJ-O%Ikr{D*Wx!P@PgPlH8-xy zQbgmAbbg;EkQz=6S{Djw1mA%0{>Japo3M1lqLJL@VY1t9e6P)|$YFd06|Ue_HE3Ig z%^>FQHhfB<=tY13J6pWS@f{v{as4CMv0=U>6AopOjf~8IOOeIMOup$3qY+c>`iT}M z!wDhrNHWq0Br6^{AX}~LWE8GBQYMlcDGSM}n~gXJDHlmc5+8~*3@MV{FB#;*wz0iE z5bA(m?0kLFlq8+hpy)VK9MT}ZgzFTfZw&*xUj0|{pu^rFI$@}Zf0xO7aCq0dO_8BG zkx&w7?;zU;*7I5d>(&Im(-BzCUuX=iYHaD?t6G)^)=lPw>_HvZlVYL#@go(iY;6t{ zgc{cujNcXZ(E?canz!Uh^_wy5f7B1IzBU>XUcZ4Q;6wryu0_z-MzHt!oL9^ Cd<4$` delta 3228 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 delta 16903 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