diff --git a/.changelog/unreleased/bug-fixes/1455-persist-epoch-update-delay.md b/.changelog/unreleased/bug-fixes/1455-persist-epoch-update-delay.md new file mode 100644 index 0000000000..7560aa3f4c --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1455-persist-epoch-update-delay.md @@ -0,0 +1,3 @@ +- Persists a newly added storage field for epoch update blocks delay to be + available after node restart when not `None` which may break consensus. + ([\#1455](https://github.com/anoma/namada/pull/1455)) \ No newline at end of file diff --git a/.changelog/unreleased/bug-fixes/1456-fix-max-wait-tries.md b/.changelog/unreleased/bug-fixes/1456-fix-max-wait-tries.md new file mode 100644 index 0000000000..fdf18ff5ec --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1456-fix-max-wait-tries.md @@ -0,0 +1,2 @@ +- Client: Fixed an off-by-one error to stop waiting for start or catch-up when + max tries are reached. ([\#1456](https://github.com/anoma/namada/pull/1456)) \ No newline at end of file diff --git a/.changelog/unreleased/features/1110-wallet-deterministic.md b/.changelog/unreleased/features/1110-wallet-deterministic.md new file mode 100644 index 0000000000..d9551b5ea6 --- /dev/null +++ b/.changelog/unreleased/features/1110-wallet-deterministic.md @@ -0,0 +1,2 @@ +- Implements HD wallet derivation / recovery from a given mnemonic code + ([\#1110](https://github.com/anoma/namada/pull/1110)) \ No newline at end of file diff --git a/.changelog/unreleased/features/1344-find-validator-by-tm.md b/.changelog/unreleased/features/1344-find-validator-by-tm.md new file mode 100644 index 0000000000..f0e923c31a --- /dev/null +++ b/.changelog/unreleased/features/1344-find-validator-by-tm.md @@ -0,0 +1,3 @@ +- PoS: Added a client command `find-validator --tm-address
` + to find validator's Namada address by Tendermint address. + ([\#1344](https://github.com/anoma/namada/pull/1344)) \ No newline at end of file diff --git a/.changelog/unreleased/features/892-cubic-slashing.md b/.changelog/unreleased/features/892-cubic-slashing.md new file mode 100644 index 0000000000..cdd079bfc3 --- /dev/null +++ b/.changelog/unreleased/features/892-cubic-slashing.md @@ -0,0 +1,5 @@ +- The implementation of the cubic slashing system that touches virtually all + parts of the proof-of-stake system. Slashes tokens are currently kept in the + PoS address rather than being transferred to the Slash Pool address. This PR + also includes significant testing infrastructure, highlighted by the PoS state + machine test with slashing. ([#892](https://github.com/anoma/namada/pull/892)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1093-signable-txs.md b/.changelog/unreleased/improvements/1093-signable-txs.md new file mode 100644 index 0000000000..e1e244bd0f --- /dev/null +++ b/.changelog/unreleased/improvements/1093-signable-txs.md @@ -0,0 +1,2 @@ +- Make Namada transactions signable on hardware constrained wallets by making + them smaller. ([#1093](https://github.com/anoma/namada/pull/1093)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1238-optinal-multicore-feature.md b/.changelog/unreleased/improvements/1238-optinal-multicore-feature.md new file mode 100644 index 0000000000..ee1a0c1ba1 --- /dev/null +++ b/.changelog/unreleased/improvements/1238-optinal-multicore-feature.md @@ -0,0 +1,4 @@ +- Added `multicore` feature flag to the namada and namada_core + crate that can be switched off for JS WASM build. + Additionally, changed the `trait ShieldedUtils` to be async. + ([\#1238](https://github.com/anoma/namada/pull/1238)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1425-wallet-zeroize.md b/.changelog/unreleased/improvements/1425-wallet-zeroize.md new file mode 100644 index 0000000000..ab32b10f65 --- /dev/null +++ b/.changelog/unreleased/improvements/1425-wallet-zeroize.md @@ -0,0 +1,2 @@ +- Zeroizes memory containing passphrases in wallet. + ([\#1425](https://github.com/anoma/namada/issues/1425)) \ No newline at end of file diff --git a/.changelog/unreleased/miscellaneous/cwgoes-remove-unused-named-address.md b/.changelog/unreleased/miscellaneous/cwgoes-remove-unused-named-address.md new file mode 100644 index 0000000000..3152915455 --- /dev/null +++ b/.changelog/unreleased/miscellaneous/cwgoes-remove-unused-named-address.md @@ -0,0 +1 @@ +Remove unused named address file diff --git a/.changelog/unreleased/unreleased/1444.md b/.changelog/unreleased/unreleased/1444.md new file mode 100644 index 0000000000..652f9ad334 --- /dev/null +++ b/.changelog/unreleased/unreleased/1444.md @@ -0,0 +1 @@ +Common sub-expression elimination in inflation calculation diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index eec0bb533f..b07e2212e9 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -4,13 +4,11 @@ on: push: branches: - main - - maint-* - "!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: - main - - maint-* - "!eth-bridge-integration" types: [opened, synchronize, reopened] workflow_dispatch: diff --git a/Cargo.lock b/Cargo.lock index 43e8327787..f88a6ad938 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,10 +19,11 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" -version = "0.4.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ + "crypto-common", "generic-array 0.14.7", ] @@ -33,7 +34,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", "cpufeatures", "opaque-debug 0.3.0", ] @@ -49,17 +50,6 @@ dependencies = [ "version_check 0.9.4", ] -[[package]] -name = "ahash" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" -dependencies = [ - "cfg-if 1.0.0", - "once_cell", - "version_check 0.9.4", -] - [[package]] name = "aho-corasick" version = "1.0.1" @@ -393,7 +383,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -410,7 +400,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -527,6 +517,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +[[package]] +name = "base58" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" + +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + [[package]] name = "base64" version = "0.9.3" @@ -586,25 +588,36 @@ dependencies = [ "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.8", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "lazy_static", "log 0.4.17", "num_cpus", - "pairing", + "pairing 0.21.0", "rand_core 0.6.4", "rayon", - "subtle", + "subtle 2.4.1", ] [[package]] -name = "bigint" -version = "4.4.3" +name = "bellman" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0e8c8a600052b52482eff2cf4d810e462fdff1f656ac1ecb6232132a1ed7def" +checksum = "a4dd656ef4fdf7debb6d87d4dd92642fcbcdb78cbf6600c13e25c87e4d1a3807" dependencies = [ + "bitvec 1.0.1", + "blake2s_simd 1.0.1", "byteorder", - "crunchy 0.1.6", + "crossbeam-channel 0.5.8", + "ff 0.12.1", + "group 0.12.1", + "lazy_static", + "log 0.4.17", + "num_cpus", + "pairing 0.22.0", + "rand_core 0.6.4", + "rayon", + "subtle 2.4.1", ] [[package]] @@ -637,13 +650,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", - "prettyplease 0.2.4", + "prettyplease 0.2.5", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -683,7 +696,7 @@ checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" dependencies = [ "bech32 0.9.1", "bitcoin_hashes", - "secp256k1 0.24.3", + "secp256k1", "serde 1.0.163", ] @@ -745,17 +758,6 @@ dependencies = [ "cty", ] -[[package]] -name = "blake2b_simd" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "constant_time_eq 0.1.5", -] - [[package]] name = "blake2b_simd" version = "1.0.1" @@ -840,7 +842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" dependencies = [ "block-padding 0.2.1", - "cipher", + "cipher 0.3.0", ] [[package]] @@ -879,52 +881,42 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" dependencies = [ - "ff", - "group", - "pairing", + "ff 0.11.1", + "group 0.11.0", + "pairing 0.21.0", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] -name = "borsh" -version = "0.9.4" -source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c54cf8259b7ec5ec4073a#cd5223e5103c4f139e0c54cf8259b7ec5ec4073a" -dependencies = [ - "borsh-derive 0.9.4", - "hashbrown 0.11.2", -] - -[[package]] -name = "borsh" -version = "0.10.3" +name = "bls12_381" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +checksum = "a3c196a77437e7cc2fb515ce413a6401291578b5afc8ecb29a3c7ab957f05941" dependencies = [ - "borsh-derive 0.10.3", - "hashbrown 0.13.2", + "ff 0.12.1", + "group 0.12.1", + "pairing 0.22.0", + "rand_core 0.6.4", + "subtle 2.4.1", ] [[package]] -name = "borsh-derive" +name = "borsh" version = "0.9.4" source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c54cf8259b7ec5ec4073a#cd5223e5103c4f139e0c54cf8259b7ec5ec4073a" dependencies = [ - "borsh-derive-internal 0.9.4", - "borsh-schema-derive-internal 0.9.4", - "proc-macro-crate 0.1.5", - "proc-macro2", - "syn 1.0.109", + "borsh-derive", + "hashbrown 0.11.2", ] [[package]] name = "borsh-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +version = "0.9.4" +source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c54cf8259b7ec5ec4073a#cd5223e5103c4f139e0c54cf8259b7ec5ec4073a" dependencies = [ - "borsh-derive-internal 0.10.3", - "borsh-schema-derive-internal 0.10.3", + "borsh-derive-internal", + "borsh-schema-derive-internal", "proc-macro-crate 0.1.5", "proc-macro2", "syn 1.0.109", @@ -940,17 +932,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "borsh-derive-internal" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "borsh-schema-derive-internal" version = "0.9.4" @@ -961,17 +942,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "borsh-schema-derive-internal" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "bs58" version = "0.4.0" @@ -1152,20 +1122,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", "cpufeatures", - "zeroize", ] [[package]] -name = "chacha20poly1305" +name = "chacha20" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if 1.0.0", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20", - "cipher", + "chacha20 0.9.1", + "cipher 0.4.4", "poly1305", "zeroize", ] @@ -1197,6 +1177,17 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "circular-queue" version = "0.2.6" @@ -1515,12 +1506,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crunchy" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" - [[package]] name = "crunchy" version = "0.2.2" @@ -1535,7 +1520,7 @@ checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1551,37 +1536,32 @@ dependencies = [ [[package]] name = "crypto-mac" -version = "0.8.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" dependencies = [ - "generic-array 0.14.7", - "subtle", + "generic-array 0.12.4", + "subtle 1.0.0", ] [[package]] name = "crypto-mac" -version = "0.11.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ "generic-array 0.14.7", - "subtle", + "subtle 2.4.1", ] [[package]] -name = "crypto_api" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f855e87e75a4799e18b8529178adcde6fd4f97c1449ff4821e747ff728bb102" - -[[package]] -name = "crypto_api_chachapoly" -version = "0.4.3" +name = "crypto-mac" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930b6a026ce9d358a17f9c9046c55d90b14bb847f36b6ebb6b19365d4feffb8" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "crypto_api", + "generic-array 0.14.7", + "subtle 2.4.1", ] [[package]] @@ -1624,7 +1604,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1661,7 +1641,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -1672,7 +1652,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -1756,7 +1736,7 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.4", "crypto-common", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -1808,7 +1788,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -1935,12 +1915,12 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "ff", + "ff 0.11.1", "generic-array 0.14.7", - "group", + "group 0.11.0", "rand_core 0.6.4", "sec1", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1991,16 +1971,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.15", -] - -[[package]] -name = "equihash" -version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "blake2b_simd 1.0.1", - "byteorder", + "syn 2.0.16", ] [[package]] @@ -2118,8 +2089,8 @@ dependencies = [ "ark-std", "bincode", "blake2", - "blake2b_simd 1.0.1", - "borsh 0.9.4", + "blake2b_simd", + "borsh", "digest 0.10.6", "ed25519-dalek", "either", @@ -2136,7 +2107,7 @@ dependencies = [ "serde_bytes", "serde_json", "subproductdomain", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -2161,7 +2132,18 @@ checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ "bitvec 0.22.3", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "bitvec 1.0.1", + "rand_core 0.6.4", + "subtle 2.4.1", ] [[package]] @@ -2272,7 +2254,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" dependencies = [ "block-modes", - "cipher", + "cipher 0.3.0", "libm", "num-bigint", "num-integer", @@ -2395,7 +2377,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -2454,8 +2436,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -2465,8 +2449,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -2526,9 +2512,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "byteorder", - "ff", + "ff 0.11.1", + "rand_core 0.6.4", + "subtle 2.4.1", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "memuse", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -2543,8 +2541,8 @@ dependencies = [ "ark-poly", "ark-serialize", "ark-std", - "blake2b_simd 1.0.1", - "chacha20", + "blake2b_simd", + "chacha20 0.8.2", "hex", "itertools", "miracl_core", @@ -2600,27 +2598,13 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" -[[package]] -name = "halo2" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f186b85ed81082fb1cf59d52b0111f02915e89a4ac61d292b38d075e570f3a9" -dependencies = [ - "blake2b_simd 0.5.11", - "ff", - "group", - "pasta_curves", - "rand 0.8.5", - "rayon", -] - [[package]] name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash 0.7.6", + "ahash", ] [[package]] @@ -2629,23 +2613,14 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", -] - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash 0.8.3", + "ahash", ] [[package]] name = "hdpath" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ae1615f843ce3981b47468f3f7c435ac17deb33c2261e64d7f1e87f5c11acc" +checksum = "dfa5bc9db2c17d2660f53ce217b778d06d68de13d1cd01c0f4e5de4b7918935f" dependencies = [ "byteorder", ] @@ -2721,6 +2696,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[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" @@ -2750,6 +2735,17 @@ dependencies = [ "digest 0.10.6", ] +[[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" @@ -2761,6 +2757,12 @@ dependencies = [ "hmac 0.8.1", ] +[[package]] +name = "hmac-sha512" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e806677ce663d0a199541030c816847b36e8dc095f70dae4a4f4ad63da5383" + [[package]] name = "http" version = "0.2.9" @@ -2939,38 +2941,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ibc" -version = "0.36.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=db14744bfba6239cc5f58345ff90f8b7d42637d6#db14744bfba6239cc5f58345ff90f8b7d42637d6" -dependencies = [ - "bytes 1.4.0", - "cfg-if 1.0.0", - "derive_more", - "displaydoc", - "dyn-clone", - "erased-serde", - "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", - "ics23", - "num-traits 0.2.15", - "parking_lot 0.12.1", - "primitive-types", - "prost", - "safe-regex", - "serde 1.0.163", - "serde_derive", - "serde_json", - "sha2 0.10.6", - "subtle-encoding", - "tendermint 0.23.5", - "tendermint-light-client-verifier 0.23.5", - "tendermint-proto 0.23.5", - "tendermint-testgen 0.23.5", - "time 0.3.17", - "tracing 0.1.37", - "uint", -] - [[package]] name = "ibc" version = "0.36.0" @@ -2982,7 +2952,7 @@ dependencies = [ "displaydoc", "dyn-clone", "erased-serde", - "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=6f4038fcf4981f1ed70771d1cd89931267f917af)", + "ibc-proto", "ics23", "num-traits 0.2.15", "parking_lot 0.12.1", @@ -2994,10 +2964,10 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.6", - "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-testgen 0.23.6", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-testgen", "time 0.3.17", "tracing 0.1.37", "uint", @@ -3014,27 +2984,10 @@ dependencies = [ "prost", "serde 1.0.163", "subtle-encoding", - "tendermint-proto 0.23.6", + "tendermint-proto", "tonic", ] -[[package]] -name = "ibc-proto" -version = "0.26.0" -source = "git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a#dd8ba23110a144ffe2074a0b889676468266435a" -dependencies = [ - "base64 0.13.1", - "borsh 0.10.3", - "bytes 1.4.0", - "flex-error", - "parity-scale-codec", - "prost", - "scale-info", - "serde 1.0.163", - "subtle-encoding", - "tendermint-proto 0.23.5", -] - [[package]] name = "ibc-relayer" version = "0.22.0" @@ -3060,7 +3013,7 @@ dependencies = [ "http", "humantime", "humantime-serde", - "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=6f4038fcf4981f1ed70771d1cd89931267f917af)", + "ibc-proto", "ibc-relayer-types", "itertools", "moka", @@ -3070,7 +3023,7 @@ dependencies = [ "regex", "retry", "ripemd", - "secp256k1 0.24.3", + "secp256k1", "semver 1.0.17", "serde 1.0.163", "serde_derive", @@ -3079,19 +3032,19 @@ dependencies = [ "signature", "strum", "subtle-encoding", - "tendermint 0.23.6", + "tendermint", "tendermint-light-client", - "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.6", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-rpc", "thiserror", - "tiny-bip39", + "tiny-bip39 1.0.0", "tiny-keccak", "tokio", "toml", "tonic", "tracing 0.1.37", - "uuid 1.3.2", + "uuid 1.3.3", ] [[package]] @@ -3104,7 +3057,7 @@ dependencies = [ "dyn-clone", "erased-serde", "flex-error", - "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=6f4038fcf4981f1ed70771d1cd89931267f917af)", + "ibc-proto", "ics23", "itertools", "num-rational", @@ -3115,11 +3068,11 @@ dependencies = [ "serde_derive", "serde_json", "subtle-encoding", - "tendermint 0.23.6", - "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.6", - "tendermint-testgen 0.23.6", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-rpc", + "tendermint-testgen", "time 0.3.17", "uint", ] @@ -3197,9 +3150,9 @@ dependencies = [ [[package]] name = "incrementalmerkletree" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186fd3ab92aeac865d4b80b410de9a7b341d31ba8281373caed0b6d17b2b5e96" +checksum = "d5ad43a3f5795945459d577f6589cf62a476e92c79b75e70cd954364e14ce17b" dependencies = [ "serde 1.0.163", ] @@ -3215,7 +3168,7 @@ name = "index-set" version = "0.7.1" source = "git+https://github.com/heliaxdev/index-set?tag=v0.7.1#dc24cdbbe3664514d59f1a4c4031863fc565f1c2" dependencies = [ - "borsh 0.9.4", + "borsh", "serde 1.0.163", ] @@ -3230,6 +3183,15 @@ dependencies = [ "serde 1.0.163", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "input_buffer" version = "0.4.0" @@ -3303,25 +3265,25 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.62" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68c16e1bfd491478ab155fd8b4896b86f9ede344949b641e61501e07c2b8b4d5" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] [[package]] name = "jubjub" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" +checksum = "a575df5f985fe1cd5b2b05664ff6accfc46559032b954529fd225a2168d27b0f" dependencies = [ - "bitvec 0.22.3", - "bls12_381", - "ff", - "group", + "bitvec 1.0.1", + "bls12_381 0.7.1", + "ff 0.12.1", + "group 0.12.1", "rand_core 0.6.4", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -3434,9 +3396,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "librocksdb-sys" @@ -3454,6 +3416,22 @@ 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" @@ -3462,7 +3440,7 @@ dependencies = [ "arrayref", "base64 0.13.1", "digest 0.9.0", - "hmac-drbg", + "hmac-drbg 0.3.0", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", @@ -3477,9 +3455,9 @@ name = "libsecp256k1-core" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "crunchy 0.2.2", + "crunchy", "digest 0.9.0", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -3621,60 +3599,69 @@ dependencies = [ "serde_yaml", ] +[[package]] +name = "masp_note_encryption" +version = "0.2.0" +source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" +dependencies = [ + "borsh", + "chacha20 0.9.1", + "chacha20poly1305", + "cipher 0.4.4", + "rand_core 0.6.4", + "subtle 2.4.1", +] + [[package]] name = "masp_primitives" -version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +version = "0.9.0" +source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" dependencies = [ "aes", "bip0039", - "bitvec 0.22.3", - "blake2b_simd 1.0.1", + "bitvec 1.0.1", + "blake2b_simd", "blake2s_simd 1.0.1", - "bls12_381", - "borsh 0.9.4", + "bls12_381 0.7.1", + "borsh", "byteorder", - "chacha20poly1305", - "crypto_api_chachapoly", - "ff", + "ff 0.12.1", "fpe", - "group", + "group 0.12.1", "hex", "incrementalmerkletree", "jubjub", "lazy_static", + "masp_note_encryption", + "memuse", + "nonempty", "rand 0.8.5", "rand_core 0.6.4", - "ripemd160", - "secp256k1 0.20.3", - "serde 1.0.163", "sha2 0.9.9", - "subtle", + "subtle 2.4.1", "zcash_encoding", - "zcash_primitives", ] [[package]] name = "masp_proofs" -version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +version = "0.9.0" +source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" dependencies = [ - "bellman", - "blake2b_simd 1.0.1", - "bls12_381", - "byteorder", + "bellman 0.13.1", + "blake2b_simd", + "bls12_381 0.7.1", "directories", - "ff", - "group", + "getrandom 0.2.9", + "group 0.12.1", "itertools", "jubjub", "lazy_static", "masp_primitives", "minreq", "rand_core 0.6.4", + "redjubjub", + "tracing 0.1.37", "wagyu-zcash-parameters", - "zcash_primitives", - "zcash_proofs", ] [[package]] @@ -3752,9 +3739,12 @@ name = "memuse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2145869435ace5ea6ea3d35f59be559317ec9a0d04e1812d5f185a87b6d36f1a" -dependencies = [ - "nonempty", -] + +[[package]] +name = "memzero" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93c0d11ac30a033511ae414355d80f70d9f29a44a49140face477117a1ee90db" [[package]] name = "merlin" @@ -3899,7 +3889,7 @@ dependencies = [ "tagptr", "thiserror", "triomphe", - "uuid 1.3.2", + "uuid 1.3.3", ] [[package]] @@ -3921,21 +3911,22 @@ dependencies = [ "assert_matches", "async-std", "async-trait", - "bellman", + "base58 0.2.0", + "bellman 0.11.2", "bimap", - "bls12_381", - "borsh 0.9.4", + "bls12_381 0.6.1", + "borsh", "byte-unit", "circular-queue", "clru", "data-encoding", + "derivation-path", "derivative", - "ibc 0.36.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=db14744bfba6239cc5f58345ff90f8b7d42637d6)", - "ibc 0.36.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=e71bc2cc79f8c2b32e970d95312f251398c93d9e)", - "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=6f4038fcf4981f1ed70771d1cd89931267f917af)", - "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", + "hex", + "ibc", + "ibc-proto", "itertools", - "libsecp256k1", + "libsecp256k1 0.7.0", "loupe", "masp_primitives", "masp_proofs", @@ -3952,17 +3943,21 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rayon", + "ripemd", "rust_decimal", "rust_decimal_macros", "serde 1.0.163", "serde_json", "sha2 0.9.9", + "slip10_ed25519", "tempfile", - "tendermint 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.6", + "tendermint", + "tendermint-proto", + "tendermint-rpc", "test-log", "thiserror", + "tiny-bip39 0.8.2", + "tiny-hderive", "tokio", "toml", "tracing 0.1.37", @@ -3991,7 +3986,7 @@ dependencies = [ "bimap", "bit-set", "blake2b-rs", - "borsh 0.9.4", + "borsh", "byte-unit", "byteorder", "clap", @@ -4029,6 +4024,7 @@ dependencies = [ "rayon", "regex", "reqwest", + "ripemd", "rlimit", "rocksdb", "rpassword", @@ -4043,10 +4039,10 @@ dependencies = [ "sysinfo", "tar", "tempfile", - "tendermint 0.23.6", - "tendermint-config 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.6", + "tendermint", + "tendermint-config", + "tendermint-proto", + "tendermint-rpc", "test-log", "thiserror", "tokio", @@ -4054,13 +4050,13 @@ dependencies = [ "toml", "tonic", "tower", - "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci.git?rev=367d8d958b83c501ed2c09e9c4595f8bf75a0b01)", - "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=a31ce06533f5fbd943508676059d44de27395792)", + "tower-abci", "tracing 0.1.37", "tracing-log", "tracing-subscriber 0.3.17", "websocket", "winapi 0.3.9", + "zeroize", ] [[package]] @@ -4072,8 +4068,8 @@ dependencies = [ "ark-serialize", "assert_matches", "bech32 0.8.1", - "bellman", - "borsh 0.9.4", + "bellman 0.11.2", + "borsh", "chrono", "data-encoding", "derivative", @@ -4081,14 +4077,12 @@ dependencies = [ "ferveo", "ferveo-common", "group-threshold-cryptography", - "ibc 0.36.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=db14744bfba6239cc5f58345ff90f8b7d42637d6)", - "ibc 0.36.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=e71bc2cc79f8c2b32e970d95312f251398c93d9e)", - "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=6f4038fcf4981f1ed70771d1cd89931267f917af)", - "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", + "ibc", + "ibc-proto", "ics23", "index-set", "itertools", - "libsecp256k1", + "libsecp256k1 0.7.0", "masp_primitives", "namada_macros", "pretty_assertions", @@ -4104,8 +4098,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "sparse-merkle-tree", - "tendermint 0.23.6", - "tendermint-proto 0.23.6", + "tendermint", + "tendermint-proto", "test-log", "thiserror", "tonic-build", @@ -4118,7 +4112,7 @@ dependencies = [ name = "namada_encoding_spec" version = "0.16.0" dependencies = [ - "borsh 0.9.4", + "borsh", "itertools", "lazy_static", "madato", @@ -4138,7 +4132,7 @@ dependencies = [ name = "namada_proof_of_stake" version = "0.16.0" dependencies = [ - "borsh 0.9.4", + "borsh", "data-encoding", "derivative", "hex", @@ -4158,7 +4152,7 @@ dependencies = [ name = "namada_test_utils" version = "0.16.0" dependencies = [ - "borsh 0.9.4", + "borsh", "namada_core", "strum", ] @@ -4168,7 +4162,7 @@ name = "namada_tests" version = "0.16.0" dependencies = [ "assert_cmd", - "borsh 0.9.4", + "borsh", "chrono", "color-eyre", "concat-idents", @@ -4198,10 +4192,10 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.6", - "tendermint-config 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.6", + "tendermint", + "tendermint-config", + "tendermint-proto", + "tendermint-rpc", "test-log", "tokio", "toml", @@ -4213,7 +4207,7 @@ dependencies = [ name = "namada_tx_prelude" version = "0.16.0" dependencies = [ - "borsh 0.9.4", + "borsh", "masp_primitives", "namada_core", "namada_macros", @@ -4228,10 +4222,9 @@ dependencies = [ name = "namada_vm_env" version = "0.16.0" dependencies = [ - "borsh 0.9.4", + "borsh", "hex", "masp_primitives", - "masp_proofs", "namada_core", ] @@ -4239,7 +4232,7 @@ dependencies = [ name = "namada_vp_prelude" version = "0.16.0" dependencies = [ - "borsh 0.9.4", + "borsh", "namada_core", "namada_macros", "namada_proof_of_stake", @@ -4520,7 +4513,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -4541,33 +4534,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "orchard" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31f03b6d0aee6d993cac35388b818e04f076ded0ab284979e4d7cd5a8b3c2be" -dependencies = [ - "aes", - "arrayvec 0.7.2", - "bigint", - "bitvec 0.22.3", - "blake2b_simd 1.0.1", - "ff", - "fpe", - "group", - "halo2", - "incrementalmerkletree", - "lazy_static", - "memuse", - "nonempty", - "pasta_curves", - "rand 0.8.5", - "reddsa", - "serde 1.0.163", - "subtle", - "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "orion" version = "0.16.1" @@ -4576,7 +4542,7 @@ checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", "getrandom 0.2.9", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -4613,7 +4579,16 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" dependencies = [ - "group", + "group 0.11.0", +] + +[[package]] +name = "pairing" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" +dependencies = [ + "group 0.12.1", ] [[package]] @@ -4707,26 +4682,11 @@ dependencies = [ name = "password-hash" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "pasta_curves" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d647d91972bad78120fd61e06b225fcda117805c9bbf17676b51bd03a251278b" -dependencies = [ - "blake2b_simd 0.5.11", - "ff", - "group", - "lazy_static", - "rand 0.8.5", - "static_assertions", - "subtle", +checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle 2.4.1", ] [[package]] @@ -4735,6 +4695,15 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac 0.8.0", +] + [[package]] name = "pbkdf2" version = "0.9.0" @@ -4836,7 +4805,7 @@ checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -4873,9 +4842,9 @@ dependencies = [ [[package]] name = "poly1305" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", "opaque-debug 0.3.0", @@ -4939,12 +4908,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ceca8aaf45b5c46ec7ed39fff75f57290368c1846d33d24a122ca81416ab058" +checksum = "617feabb81566b593beb4886fb8c1f38064169dae4dccad0e3220160c3b37203" dependencies = [ "proc-macro2", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -5004,9 +4973,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" dependencies = [ "unicode-ident", ] @@ -5406,17 +5375,15 @@ dependencies = [ ] [[package]] -name = "reddsa" -version = "0.1.0" +name = "redjubjub" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e2c94bca3445cae0d55dff7370e29c24885d2403a1665ce19c106c79455e6" +checksum = "6039ff156887caf92df308cbaccdc058c9d3155a913da046add6e48c4cdbd91d" dependencies = [ - "blake2b_simd 0.5.11", + "blake2b_simd", "byteorder", "digest 0.9.0", - "group", "jubjub", - "pasta_curves", "rand_core 0.6.4", "serde 1.0.163", "thiserror", @@ -5524,9 +5491,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.17" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ "base64 0.21.0", "bytes 1.4.0", @@ -5625,7 +5592,7 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid 1.3.2", + "uuid 1.3.3", ] [[package]] @@ -5681,7 +5648,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec 0.7.2", - "borsh 0.9.4", + "borsh", "num-traits 0.2.15", "serde 1.0.163", ] @@ -5899,30 +5866,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scale-info" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfdef77228a4c05dc94211441595746732131ad7f6530c6c18f045da7b7ab937" -dependencies = [ - "cfg-if 1.0.0", - "derive_more", - "parity-scale-codec", - "scale-info-derive", -] - -[[package]] -name = "scale-info-derive" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53012eae69e5aa5c14671942a5dd47de59d4cdcff8532a6dd0e081faf1119482" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "schannel" version = "0.1.21" @@ -5981,19 +5924,10 @@ checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", "generic-array 0.14.7", - "subtle", + "subtle 2.4.1", "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" -dependencies = [ - "secp256k1-sys 0.4.2", -] - [[package]] name = "secp256k1" version = "0.24.3" @@ -6002,19 +5936,10 @@ checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys 0.6.1", + "secp256k1-sys", "serde 1.0.163", ] -[[package]] -name = "secp256k1-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" -dependencies = [ - "cc", -] - [[package]] name = "secp256k1-sys" version = "0.6.1" @@ -6143,7 +6068,7 @@ checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -6165,7 +6090,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -6228,6 +6153,18 @@ dependencies = [ "digest 0.10.6", ] +[[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" @@ -6342,6 +6279,15 @@ dependencies = [ "autocfg 1.1.0", ] +[[package]] +name = "slip10_ed25519" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be0ff28bf14f9610a342169084e87a4f435ad798ec528dc7579a3678fa9dc9a" +dependencies = [ + "hmac-sha512", +] + [[package]] name = "smallvec" version = "0.6.14" @@ -6373,7 +6319,7 @@ version = "0.3.1-pre" source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=e086b235ed6e68929bf73f617dd61cd17b000a56#e086b235ed6e68929bf73f617dd61cd17b000a56" dependencies = [ "blake2b-rs", - "borsh 0.9.4", + "borsh", "cfg-if 1.0.0", "ics23", "sha2 0.9.9", @@ -6438,6 +6384,12 @@ dependencies = [ "ark-std", ] +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "subtle" version = "2.4.1" @@ -6472,9 +6424,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" dependencies = [ "proc-macro2", "quote", @@ -6543,34 +6495,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "tendermint" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916#a3a0ad5f07d380976bbd5321239aec9cc3a8f916" -dependencies = [ - "async-trait", - "bytes 1.4.0", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures 0.3.28", - "num-traits 0.2.15", - "once_cell", - "prost", - "prost-types", - "serde 1.0.163", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto 0.23.5", - "time 0.3.17", - "zeroize", -] - [[package]] name = "tendermint" version = "0.23.6" @@ -6594,26 +6518,13 @@ dependencies = [ "serde_repr", "sha2 0.9.9", "signature", - "subtle", + "subtle 2.4.1", "subtle-encoding", - "tendermint-proto 0.23.6", + "tendermint-proto", "time 0.3.17", "zeroize", ] -[[package]] -name = "tendermint-config" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916#a3a0ad5f07d380976bbd5321239aec9cc3a8f916" -dependencies = [ - "flex-error", - "serde 1.0.163", - "serde_json", - "tendermint 0.23.5", - "toml", - "url 2.3.1", -] - [[package]] name = "tendermint-config" version = "0.23.6" @@ -6622,7 +6533,7 @@ dependencies = [ "flex-error", "serde 1.0.163", "serde_json", - "tendermint 0.23.6", + "tendermint", "toml", "url 2.3.1", ] @@ -6641,26 +6552,13 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint 0.23.6", - "tendermint-light-client-verifier 0.23.6", - "tendermint-rpc 0.23.6", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-rpc", "time 0.3.17", "tokio", ] -[[package]] -name = "tendermint-light-client-verifier" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916#a3a0ad5f07d380976bbd5321239aec9cc3a8f916" -dependencies = [ - "derive_more", - "flex-error", - "serde 1.0.163", - "tendermint 0.23.5", - "tendermint-rpc 0.23.5", - "time 0.3.17", -] - [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" @@ -6669,24 +6567,7 @@ dependencies = [ "derive_more", "flex-error", "serde 1.0.163", - "tendermint 0.23.6", - "time 0.3.17", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916#a3a0ad5f07d380976bbd5321239aec9cc3a8f916" -dependencies = [ - "bytes 1.4.0", - "flex-error", - "num-derive", - "num-traits 0.2.15", - "prost", - "prost-types", - "serde 1.0.163", - "serde_bytes", - "subtle-encoding", + "tendermint", "time 0.3.17", ] @@ -6707,30 +6588,6 @@ dependencies = [ "time 0.3.17", ] -[[package]] -name = "tendermint-rpc" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916#a3a0ad5f07d380976bbd5321239aec9cc3a8f916" -dependencies = [ - "bytes 1.4.0", - "flex-error", - "getrandom 0.2.9", - "peg", - "pin-project", - "serde 1.0.163", - "serde_bytes", - "serde_json", - "subtle-encoding", - "tendermint 0.23.5", - "tendermint-config 0.23.5", - "tendermint-proto 0.23.5", - "thiserror", - "time 0.3.17", - "url 2.3.1", - "uuid 0.8.2", - "walkdir", -] - [[package]] name = "tendermint-rpc" version = "0.23.6" @@ -6752,9 +6609,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint 0.23.6", - "tendermint-config 0.23.6", - "tendermint-proto 0.23.6", + "tendermint", + "tendermint-config", + "tendermint-proto", "thiserror", "time 0.3.17", "tokio", @@ -6764,21 +6621,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "tendermint-testgen" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916#a3a0ad5f07d380976bbd5321239aec9cc3a8f916" -dependencies = [ - "ed25519-dalek", - "gumdrop", - "serde 1.0.163", - "serde_json", - "simple-error", - "tempfile", - "tendermint 0.23.5", - "time 0.3.17", -] - [[package]] name = "tendermint-testgen" version = "0.23.6" @@ -6790,7 +6632,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.6", + "tendermint", "time 0.3.17", ] @@ -6846,7 +6688,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -6905,6 +6747,24 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "git+https://github.com/anoma/tiny-bip39.git?rev=bf0f6d8713589b83af7a917366ec31f5275c0e57#bf0f6d8713589b83af7a917366ec31f5275c0e57" +dependencies = [ + "anyhow", + "hmac 0.8.1", + "once_cell", + "pbkdf2 0.4.0", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.9", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + [[package]] name = "tiny-bip39" version = "1.0.0" @@ -6924,13 +6784,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tiny-hderive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b874a4992538d4b2f4fbbac11b9419d685f4b39bdc3fed95b04e07bfd76040" +dependencies = [ + "base58 0.1.0", + "hmac 0.7.1", + "libsecp256k1 0.3.5", + "memzero", + "sha2 0.8.2", +] + [[package]] name = "tiny-keccak" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ - "crunchy 0.2.2", + "crunchy", ] [[package]] @@ -7029,7 +6902,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -7181,15 +7054,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" [[package]] name = "toml_edit" -version = "0.19.8" +version = "0.19.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "92d964908cec0d030b812013af25a0e57fddfadb1e066ecc6681d86253129d4f" dependencies = [ "indexmap", "toml_datetime", @@ -7274,25 +7147,7 @@ dependencies = [ "futures 0.3.28", "pin-project", "prost", - "tendermint-proto 0.23.6", - "tokio", - "tokio-stream", - "tokio-util 0.6.10", - "tower", - "tracing 0.1.30", - "tracing-tower", -] - -[[package]] -name = "tower-abci" -version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?rev=a31ce06533f5fbd943508676059d44de27395792#a31ce06533f5fbd943508676059d44de27395792" -dependencies = [ - "bytes 1.4.0", - "futures 0.3.28", - "pin-project", - "prost", - "tendermint-proto 0.23.5", + "tendermint-proto", "tokio", "tokio-stream", "tokio-util 0.6.10", @@ -7364,7 +7219,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -7543,7 +7398,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", - "crunchy 0.2.2", + "crunchy", "hex", "static_assertions", ] @@ -7607,12 +7462,12 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" dependencies = [ - "generic-array 0.14.7", - "subtle", + "crypto-common", + "subtle 2.4.1", ] [[package]] @@ -7663,9 +7518,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2" +checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" dependencies = [ "getrandom 0.2.9", ] @@ -7815,9 +7670,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.85" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b6cb788c4e39112fbe1822277ef6fb3c55cd86b95cb3d3c4c1c9597e4ac74b4" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -7825,24 +7680,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.85" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e522ed4105a9d626d885b35d62501b30d9666283a5c8be12c14a8bdafe7822" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log 0.4.17", "once_cell", "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.35" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "083abe15c5d88556b77bdf7aef403625be9e327ad37c62c4e4129af740168163" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -7852,9 +7707,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.85" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "358a79a0cb89d21db8120cbfb91392335913e4890665b1a7981d9e956903b434" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7862,28 +7717,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.85" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.85" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a901d592cafaa4d711bc324edfaff879ac700b19c3dfd60058d2b445be2691eb" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "wasm-encoder" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05d0b6fcd0aeb98adf16e7975331b3c17222aa815148f5b976370ce589d80ef" +checksum = "e77053dc709db790691d3732cfc458adc5acc881dec524965c608effdcd9c581" dependencies = [ "leb128", ] @@ -8126,9 +7981,9 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "57.0.0" +version = "58.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eb0f5ed17ac4421193c7477da05892c2edafd67f9639e3c11a82086416662dc" +checksum = "372eecae2d10a5091c2005b32377d7ecd6feecdf2c05838056d02d8b4f07c429" dependencies = [ "leb128", "memchr", @@ -8138,18 +7993,18 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9ab0d87337c3be2bb6fc5cd331c4ba9fd6bcb4ee85048a0dd59ed9ecf92e53" +checksum = "6d47446190e112ab1579ab40b3ad7e319d859d74e5134683f04e9f0747bf4173" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.62" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b5f940c7edfdc6d12126d98c9ef4d1b3d470011c47c76a6581df47ad9ba721" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -8563,83 +8418,10 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "byteorder", - "nonempty", -] - -[[package]] -name = "zcash_note_encryption" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33f84ae538f05a8ac74c82527f06b77045ed9553a0871d9db036166a4c344e3a" -dependencies = [ - "chacha20", - "chacha20poly1305", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "zcash_note_encryption" -version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash?rev=43c18d0#43c18d000fcbe45363b2d53585d5102841eff99e" dependencies = [ - "chacha20", - "chacha20poly1305", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "zcash_primitives" -version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "aes", - "bip0039", - "bitvec 0.22.3", - "blake2b_simd 1.0.1", - "blake2s_simd 1.0.1", - "bls12_381", "byteorder", - "chacha20poly1305", - "equihash", - "ff", - "fpe", - "group", - "hex", - "incrementalmerkletree", - "jubjub", - "lazy_static", - "memuse", "nonempty", - "orchard", - "rand 0.8.5", - "rand_core 0.6.4", - "sha2 0.9.9", - "subtle", - "zcash_encoding", - "zcash_note_encryption 0.1.0 (git+https://github.com/zcash/librustzcash/?rev=2425a08)", -] - -[[package]] -name = "zcash_proofs" -version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "bellman", - "blake2b_simd 1.0.1", - "bls12_381", - "byteorder", - "directories", - "ff", - "group", - "jubjub", - "lazy_static", - "rand_core 0.6.4", - "zcash_primitives", ] [[package]] @@ -8659,7 +8441,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] diff --git a/Makefile b/Makefile index a05dffd398..2f8b1b7465 100644 --- a/Makefile +++ b/Makefile @@ -41,15 +41,6 @@ check: make -C $(wasms_for_tests) check && \ $(foreach wasm,$(wasm_templates),$(check-wasm) && ) true -check-abcipp: - $(cargo) +$(nightly) check \ - --workspace \ - --exclude namada_tests \ - --all-targets \ - --no-default-features \ - --features "abcipp ibc-mocks-abcipp testing" \ - -Z unstable-options - check-mainnet: $(cargo) check --workspace --features "mainnet" @@ -61,25 +52,6 @@ clippy: make -C $(wasms_for_tests) clippy && \ $(foreach wasm,$(wasm_templates),$(clippy-wasm) && ) true -clippy-abcipp: - NAMADA_DEV=false $(cargo) +$(nightly) clippy --all-targets \ - --manifest-path ./apps/Cargo.toml \ - --no-default-features \ - --features "std testing abcipp" && \ - $(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 ferveo-tpke" && \ - $(cargo) +$(nightly) clippy \ - --all-targets \ - --manifest-path ./vm_env/Cargo.toml \ - --no-default-features && \ - make -C $(wasms) clippy && \ - $(foreach wasm,$(wasm_templates),$(clippy-wasm) && ) true - clippy-mainnet: $(cargo) +$(nightly) clippy --all-targets --features "mainnet" -- -D warnings @@ -137,36 +109,6 @@ else -Z unstable-options --report-time endif -test-unit-abcipp: - $(cargo) test \ - --manifest-path ./apps/Cargo.toml \ - --no-default-features \ - --features "testing std abcipp" \ - -Z unstable-options \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time && \ - $(cargo) test \ - --manifest-path \ - ./proof_of_stake/Cargo.toml \ - --features "testing" \ - -Z unstable-options \ - $(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" \ - -Z unstable-options \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time && \ - $(cargo) test \ - --manifest-path ./vm_env/Cargo.toml \ - --no-default-features \ - --features "abcipp" \ - -Z unstable-options \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time - test-unit: $(cargo) +$(nightly) test \ $(TEST_FILTER) \ @@ -278,4 +220,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 debug-wasm-scripts-docker build-wasm-scripts debug-wasm-scripts clean-wasm-scripts dev-deps test-miri test-unit test-unit-abcipp clippy-abcipp +.PHONY : build check build-release clippy install run-ledger run-gossip reset-ledger test test-debug fmt watch clean build-doc doc build-wasm-scripts-docker debug-wasm-scripts-docker build-wasm-scripts debug-wasm-scripts clean-wasm-scripts dev-deps test-miri test-unit diff --git a/apps/Cargo.toml b/apps/Cargo.toml index d79451a358..710d02daf3 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -45,20 +45,10 @@ mainnet = [ "namada/mainnet", ] dev = ["namada/dev"] -std = ["ed25519-consensus/std", "rand/std", "rand_core/std"] +std = ["ed25519-consensus/std", "rand/std", "rand_core/std", "namada/std"] # for integration tests and test utilies testing = ["dev"] -abcipp = [ - "namada/abcipp", - "namada/tendermint-rpc-abcipp", - "tendermint-abcipp", - "tendermint-config-abcipp", - "tendermint-proto-abcipp", - "tendermint-rpc-abcipp", - "tower-abci-abcipp", -] - abciplus = [ "namada/abciplus", "namada/tendermint-rpc", @@ -70,7 +60,7 @@ abciplus = [ ] [dependencies] -namada = {path = "../shared", default-features = false, features = ["wasm-runtime", "ferveo-tpke", "masp-tx-gen"]} +namada = {path = "../shared", default-features = false, features = ["wasm-runtime", "ferveo-tpke", "masp-tx-gen", "multicore"]} ark-serialize = "0.3.0" ark-std = "0.3.0" # branch = "bat/arse-merkle-tree" @@ -90,6 +80,7 @@ color-eyre = "0.5.10" config = "0.11.0" data-encoding = "2.3.2" derivative = "2.2.0" +directories = "4.0.1" ed25519-consensus = "1.2.0" ferveo = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} @@ -113,6 +104,7 @@ rand_core = {version = "0.6", default-features = false} rayon = "=1.5.3" regex = "1.4.5" reqwest = "0.11.4" +ripemd = "0.1" rlimit = "0.5.4" rocksdb = {version = "0.21.0", features = ['zstd', 'jemalloc'], default-features = false} rpassword = "5.0.1" @@ -125,10 +117,6 @@ signal-hook = "0.3.9" sysinfo = {version = "=0.21.1", default-features = false} tar = "0.4.37" # temporarily using fork work-around -tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "02b256829e80f8cfecf3fa0d625c2a76c79cd043", optional = true} -tendermint-config-abcipp = {package = "tendermint-config", git = "https://github.com/heliaxdev/tendermint-rs", rev = "02b256829e80f8cfecf3fa0d625c2a76c79cd043", optional = true} -tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "02b256829e80f8cfecf3fa0d625c2a76c79cd043", optional = true} -tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "02b256829e80f8cfecf3fa0d625c2a76c79cd043", 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} @@ -140,20 +128,19 @@ tonic = "0.8.3" 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 = "a31ce06533f5fbd943508676059d44de27395792", 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", "json"]} websocket = "0.26.2" winapi = "0.3.9" -#libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", features = ["transparent-inputs"] } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", features = ["bundled-prover", "download-params"] } +# branch = "murisi/namada-integration" +masp_primitives = { git = "https://github.com/anoma/masp", rev = "cfea8c95d3f73077ca3e25380fd27e5b46e828fd", features = ["transparent-inputs"] } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "cfea8c95d3f73077ca3e25380fd27e5b46e828fd", features = ["bundled-prover", "download-params"] } bimap = {version = "0.6.2", features = ["serde"]} rust_decimal = "=1.26.1" rust_decimal_macros = "=1.26.1" -directories = "4.0.1" +zeroize = "1.5.5" [dev-dependencies] namada = {path = "../shared", default-features = false, features = ["testing", "wasm-runtime"]} diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 10316cb6bf..25d61de3ad 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -253,6 +253,13 @@ pub async fn main() -> Result<()> { rpc::query_delegations(&client, &mut ctx.wallet, args) .await; } + Sub::QueryFindValidator(QueryFindValidator(args)) => { + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); + let args = args.to_sdk(&mut ctx); + rpc::query_find_validator(&client, args).await; + } Sub::QueryResult(QueryResult(args)) => { wait_until_node_is_synched(&args.query.ledger_address) .await; @@ -341,7 +348,7 @@ async fn wait_until_node_is_synched(ledger_address: &TendermintAddress) { if is_at_least_height_one && !is_catching_up { return; } else { - if try_count > MAX_TRIES { + if try_count == MAX_TRIES { println!( "Node is still catching up, wait for it to finish \ synching." diff --git a/apps/src/bin/namada-wallet/cli.rs b/apps/src/bin/namada-wallet/cli.rs index 2ec7a3a31c..fb7599ace3 100644 --- a/apps/src/bin/namada-wallet/cli.rs +++ b/apps/src/bin/namada-wallet/cli.rs @@ -8,14 +8,14 @@ use color_eyre::eyre::Result; use itertools::sorted; use masp_primitives::zip32::ExtendedFullViewingKey; use namada::ledger::masp::find_valid_diversifier; -use namada::ledger::wallet::FindKeyError; +use namada::ledger::wallet::{DecryptionError, FindKeyError}; use namada::types::key::*; use namada::types::masp::{MaspValue, PaymentAddress}; use namada_apps::cli; use namada_apps::cli::args::CliToSdk; use namada_apps::cli::{args, cmds, Context}; use namada_apps::wallet::{ - read_and_confirm_pwd, CliWalletUtils, DecryptionError, + read_and_confirm_encryption_password, CliWalletUtils, }; use rand_core::OsRng; @@ -23,6 +23,9 @@ pub fn main() -> Result<()> { let (cmd, mut ctx) = cli::namada_wallet_cli()?; match cmd { cmds::NamadaWallet::Key(sub) => match sub { + cmds::WalletKey::Restore(cmds::KeyRestore(args)) => { + key_and_address_restore(ctx, args) + } cmds::WalletKey::Gen(cmds::KeyGen(args)) => { key_and_address_gen(ctx, args) } @@ -36,6 +39,9 @@ pub fn main() -> Result<()> { cmds::WalletAddress::Gen(cmds::AddressGen(args)) => { key_and_address_gen(ctx, args) } + cmds::WalletAddress::Restore(cmds::AddressRestore(args)) => { + key_and_address_restore(ctx, args) + } cmds::WalletAddress::Find(cmds::AddressOrAliasFind(args)) => { address_or_alias_find(ctx, args) } @@ -205,7 +211,7 @@ fn spending_key_gen( ) { let mut wallet = ctx.wallet; let alias = alias.to_lowercase(); - let password = read_and_confirm_pwd(unsafe_dont_encrypt); + let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let (alias, _key) = wallet.gen_spending_key(alias, password, alias_force); namada_apps::wallet::save(&wallet) .unwrap_or_else(|err| eprintln!("{}", err)); @@ -273,7 +279,8 @@ fn address_key_add( (alias, "viewing key") } MaspValue::ExtendedSpendingKey(spending_key) => { - let password = read_and_confirm_pwd(unsafe_dont_encrypt); + let password = + read_and_confirm_encryption_password(unsafe_dont_encrypt); let alias = ctx .wallet .encrypt_insert_spending_key( @@ -307,6 +314,41 @@ fn address_key_add( ); } +/// Restore a keypair and an implicit address from the mnemonic code in the +/// wallet. +fn key_and_address_restore( + ctx: Context, + args::KeyAndAddressRestore { + scheme, + alias, + alias_force, + unsafe_dont_encrypt, + derivation_path, + }: args::KeyAndAddressRestore, +) { + let mut wallet = ctx.wallet; + let encryption_password = + read_and_confirm_encryption_password(unsafe_dont_encrypt); + let (alias, _key) = wallet + .derive_key_from_user_mnemonic_code( + scheme, + alias, + alias_force, + derivation_path, + encryption_password, + ) + .unwrap_or_else(|err| { + eprintln!("{}", err); + cli::safe_exit(1) + }); + namada_apps::wallet::save(&wallet) + .unwrap_or_else(|err| eprintln!("{}", err)); + println!( + "Successfully added a key and an address with alias: \"{}\"", + alias + ); +} + /// Generate a new keypair and derive implicit address from it and store them in /// the wallet. fn key_and_address_gen( @@ -316,11 +358,27 @@ fn key_and_address_gen( alias, alias_force, unsafe_dont_encrypt, + derivation_path, }: args::KeyAndAddressGen, ) { let mut wallet = ctx.wallet; - let password = read_and_confirm_pwd(unsafe_dont_encrypt); - let (alias, _key) = wallet.gen_key(scheme, alias, password, alias_force); + let encryption_password = + read_and_confirm_encryption_password(unsafe_dont_encrypt); + let mut rng = OsRng; + let derivation_path_and_mnemonic_rng = + derivation_path.map(|p| (p, &mut rng)); + let (alias, _key) = wallet + .gen_key( + scheme, + alias, + alias_force, + encryption_password, + derivation_path_and_mnemonic_rng, + ) + .unwrap_or_else(|err| { + eprintln!("{}", err); + cli::safe_exit(1); + }); namada_apps::wallet::save(&wallet) .unwrap_or_else(|err| eprintln!("{}", err)); println!( diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index daecbf5eee..5e7c464b0f 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -175,6 +175,7 @@ pub mod cmds { .subcommand(QueryBondedStake::def().display_order(3)) .subcommand(QuerySlashes::def().display_order(3)) .subcommand(QueryDelegations::def().display_order(3)) + .subcommand(QueryFindValidator::def().display_order(3)) .subcommand(QueryResult::def().display_order(3)) .subcommand(QueryRawBytes::def().display_order(3)) .subcommand(QueryProposal::def().display_order(3)) @@ -213,6 +214,8 @@ pub mod cmds { let query_slashes = Self::parse_with_ctx(matches, QuerySlashes); let query_delegations = Self::parse_with_ctx(matches, QueryDelegations); + let query_find_validator = + Self::parse_with_ctx(matches, QueryFindValidator); let query_result = Self::parse_with_ctx(matches, QueryResult); let query_raw_bytes = Self::parse_with_ctx(matches, QueryRawBytes); let query_proposal = Self::parse_with_ctx(matches, QueryProposal); @@ -242,6 +245,7 @@ pub mod cmds { .or(query_bonded_stake) .or(query_slashes) .or(query_delegations) + .or(query_find_validator) .or(query_result) .or(query_raw_bytes) .or(query_proposal) @@ -306,6 +310,7 @@ pub mod cmds { QueryCommissionRate(QueryCommissionRate), QuerySlashes(QuerySlashes), QueryDelegations(QueryDelegations), + QueryFindValidator(QueryFindValidator), QueryRawBytes(QueryRawBytes), QueryProposal(QueryProposal), QueryProposalResult(QueryProposalResult), @@ -359,6 +364,7 @@ pub mod cmds { #[derive(Clone, Debug)] #[allow(clippy::large_enum_variant)] pub enum WalletKey { + Restore(KeyRestore), Gen(KeyGen), Find(KeyFind), List(KeyList), @@ -371,10 +377,11 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).and_then(|matches| { let generate = SubCmd::parse(matches).map(Self::Gen); + let restore = SubCmd::parse(matches).map(Self::Restore); let lookup = SubCmd::parse(matches).map(Self::Find); let list = SubCmd::parse(matches).map(Self::List); let export = SubCmd::parse(matches).map(Self::Export); - generate.or(lookup).or(list).or(export) + generate.or(restore).or(lookup).or(list).or(export) }) } @@ -385,6 +392,7 @@ pub mod cmds { look-up keys.", ) .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(KeyRestore::def()) .subcommand(KeyGen::def()) .subcommand(KeyFind::def()) .subcommand(KeyList::def()) @@ -392,6 +400,31 @@ pub mod cmds { } } + /// Restore a keypair and implicit address from the mnemonic code + #[derive(Clone, Debug)] + pub struct KeyRestore(pub args::KeyAndAddressRestore); + + impl SubCmd for KeyRestore { + const CMD: &'static str = "restore"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::KeyAndAddressRestore::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Restores a keypair from the given mnemonic code and HD \ + derivation path and derives the implicit address from \ + its public key. Stores the keypair and the address with \ + the given alias.", + ) + .add_args::() + } + } + /// Generate a new keypair and an implicit address derived from it #[derive(Clone, Debug)] pub struct KeyGen(pub args::KeyAndAddressGen); @@ -408,7 +441,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about( - "Generates a keypair with a given alias and derive the \ + "Generates a keypair with a given alias and derives the \ implicit address from its public key. The address will \ be stored with the same alias.", ) @@ -640,6 +673,7 @@ pub mod cmds { #[derive(Clone, Debug)] pub enum WalletAddress { Gen(AddressGen), + Restore(AddressRestore), Find(AddressOrAliasFind), List(AddressList), Add(AddressAdd), @@ -651,10 +685,11 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).and_then(|matches| { let gen = SubCmd::parse(matches).map(Self::Gen); + let restore = SubCmd::parse(matches).map(Self::Restore); let find = SubCmd::parse(matches).map(Self::Find); let list = SubCmd::parse(matches).map(Self::List); let add = SubCmd::parse(matches).map(Self::Add); - gen.or(find).or(list).or(add) + gen.or(restore).or(find).or(list).or(add) }) } @@ -666,6 +701,7 @@ pub mod cmds { ) .setting(AppSettings::SubcommandRequiredElseHelp) .subcommand(AddressGen::def()) + .subcommand(AddressRestore::def()) .subcommand(AddressOrAliasFind::def()) .subcommand(AddressList::def()) .subcommand(AddressAdd::def()) @@ -688,7 +724,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about( - "Generates a keypair with a given alias and derive the \ + "Generates a keypair with a given alias and derives the \ implicit address from its public key. The address will \ be stored with the same alias.", ) @@ -696,6 +732,30 @@ pub mod cmds { } } + /// Restore a keypair and an implicit address from the mnemonic code + #[derive(Clone, Debug)] + pub struct AddressRestore(pub args::KeyAndAddressRestore); + + impl SubCmd for AddressRestore { + const CMD: &'static str = "restore"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + AddressRestore(args::KeyAndAddressRestore::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Restores a keypair from the given mnemonic code and \ + derives the implicit address from its public key. Stores \ + the keypair and the address with the given alias.", + ) + .add_args::() + } + } + /// Find an address by its alias #[derive(Clone, Debug)] pub struct AddressOrAliasFind(pub args::AddressOrAliasFind); @@ -1406,6 +1466,28 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct QueryFindValidator(pub args::QueryFindValidator); + + impl SubCmd for QueryFindValidator { + const CMD: &'static str = "find-validator"; + + fn parse(matches: &ArgMatches) -> Option + where + Self: Sized, + { + matches.subcommand_matches(Self::CMD).map(|matches| { + QueryFindValidator(args::QueryFindValidator::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Find a PoS validator by its Tendermint address.") + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct QueryRawBytes(pub args::QueryRawBytes); @@ -1684,6 +1766,7 @@ pub mod args { pub const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; pub const TX_CHANGE_COMMISSION_WASM: &str = "tx_change_validator_commission.wasm"; + pub const TX_UNJAIL_VALIDATOR_WASM: &str = "tx_unjail_validator.wasm"; pub const ADDRESS: Arg = arg("address"); pub const ALIAS_OPT: ArgOpt = ALIAS.opt(); @@ -1734,6 +1817,9 @@ pub mod args { pub const GENESIS_VALIDATOR: ArgOpt = arg("genesis-validator").opt(); pub const HALT_ACTION: ArgFlag = flag("halt"); + pub const HD_WALLET_DERIVATION_PATH: Arg = arg("hd-path"); + pub const HD_WALLET_DERIVATION_PATH_OPT: ArgOpt = + HD_WALLET_DERIVATION_PATH.opt(); pub const HISTORIC: ArgFlag = flag("historic"); pub const LEDGER_ADDRESS_ABOUT: &str = "Address of a ledger node as \"{scheme}://{host}:{port}\". If the \ @@ -1789,6 +1875,7 @@ pub mod args { pub const TENDERMINT_TX_INDEX: ArgFlag = flag("tx-index"); pub const TIMEOUT_HEIGHT: ArgOpt = arg_opt("timeout-height"); pub const TIMEOUT_SEC_OFFSET: ArgOpt = arg_opt("timeout-sec-offset"); + pub const TM_ADDRESS: Arg = arg("tm-address"); pub const TOKEN_OPT: ArgOpt = TOKEN.opt(); pub const TOKEN: Arg = arg("token"); pub const TRANSFER_SOURCE: Arg = arg("source"); @@ -2054,7 +2141,7 @@ pub mod args { sub_prefix: self.sub_prefix, amount: self.amount, native_token: ctx.native_token.clone(), - tx_code_path: ctx.read_wasm(self.tx_code_path), + tx_code_path: self.tx_code_path.to_path_buf(), } } } @@ -2109,7 +2196,7 @@ pub mod args { channel_id: self.channel_id, timeout_height: self.timeout_height, timeout_sec_offset: self.timeout_sec_offset, - tx_code_path: ctx.read_wasm(self.tx_code_path), + tx_code_path: self.tx_code_path.to_path_buf(), } } } @@ -2170,15 +2257,8 @@ pub mod args { TxInitAccount:: { tx: self.tx.to_sdk(ctx), source: ctx.get(&self.source), - vp_code: ctx.read_wasm(self.vp_code), - vp_code_path: self - .vp_code_path - .as_path() - .to_str() - .unwrap() - .to_string() - .into_bytes(), - tx_code_path: ctx.read_wasm(self.tx_code_path), + vp_code_path: self.vp_code_path.to_path_buf(), + tx_code_path: self.tx_code_path.to_path_buf(), public_key: ctx.get_cached(&self.public_key), } } @@ -2191,13 +2271,11 @@ pub mod args { let vp_code_path = CODE_PATH_OPT .parse(matches) .unwrap_or_else(|| PathBuf::from(VP_USER_WASM)); - let vp_code = vp_code_path.clone(); let tx_code_path = PathBuf::from(TX_INIT_ACCOUNT_WASM); let public_key = PUBLIC_KEY.parse(matches); Self { tx, source, - vp_code, vp_code_path, public_key, tx_code_path, @@ -2234,13 +2312,9 @@ pub mod args { max_commission_rate_change: self.max_commission_rate_change, validator_vp_code_path: self .validator_vp_code_path - .as_path() - .to_str() - .unwrap() - .to_string() - .into_bytes(), + .to_path_buf(), unsafe_dont_encrypt: self.unsafe_dont_encrypt, - tx_code_path: ctx.read_wasm(self.tx_code_path), + tx_code_path: self.tx_code_path.to_path_buf(), } } } @@ -2324,20 +2398,8 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> TxUpdateVp { TxUpdateVp:: { tx: self.tx.to_sdk(ctx), - vp_code_path: self - .vp_code_path - .as_path() - .to_str() - .unwrap() - .to_string() - .into_bytes(), - tx_code_path: self - .tx_code_path - .as_path() - .to_str() - .unwrap() - .to_string() - .into_bytes(), + vp_code_path: self.vp_code_path, + tx_code_path: self.tx_code_path, addr: ctx.get(&self.addr), } } @@ -2379,7 +2441,7 @@ pub mod args { amount: self.amount, source: self.source.map(|x| ctx.get(&x)), native_token: ctx.native_token.clone(), - tx_code_path: ctx.read_wasm(self.tx_code_path), + tx_code_path: self.tx_code_path.to_path_buf(), } } } @@ -2419,7 +2481,7 @@ pub mod args { validator: ctx.get(&self.validator), amount: self.amount, source: self.source.map(|x| ctx.get(&x)), - tx_code_path: ctx.read_wasm(self.tx_code_path), + tx_code_path: self.tx_code_path.to_path_buf(), } } } @@ -2466,7 +2528,7 @@ pub mod args { /// Native token address pub native_token: C::NativeAddress, /// Path to the TX WASM code file - pub tx_code_path: C::Data, + pub tx_code_path: PathBuf, } impl CliToSdk> for InitProposal { @@ -2476,7 +2538,7 @@ pub mod args { proposal_data: self.proposal_data, offline: self.offline, native_token: ctx.native_token.clone(), - tx_code_path: ctx.read_wasm(self.tx_code_path), + tx_code_path: self.tx_code_path, } } } @@ -2527,7 +2589,7 @@ pub mod args { /// The proposal file path pub proposal_data: Option, /// Path to the TX WASM code file - pub tx_code_path: C::Data, + pub tx_code_path: PathBuf, } impl CliToSdk> for VoteProposal { @@ -2538,7 +2600,7 @@ pub mod args { vote: self.vote, offline: self.offline, proposal_data: self.proposal_data, - tx_code_path: ctx.read_wasm(self.tx_code_path), + tx_code_path: self.tx_code_path.to_path_buf(), proposal_pgf: self.proposal_pgf, proposal_eth: self.proposal_eth, } @@ -2763,7 +2825,7 @@ pub mod args { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), source: self.source.map(|x| ctx.get(&x)), - tx_code_path: ctx.read_wasm(self.tx_code_path), + tx_code_path: self.tx_code_path.to_path_buf(), } } } @@ -2995,7 +3057,7 @@ pub mod args { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), rate: self.rate, - tx_code_path: ctx.read_wasm(self.tx_code_path), + tx_code_path: self.tx_code_path.to_path_buf(), } } } @@ -3015,7 +3077,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(VALIDATOR.def().about( "The validator's address whose commission rate to change.", )) @@ -3027,6 +3089,43 @@ pub mod args { } } + impl CliToSdk> for TxUnjailValidator { + fn to_sdk(self, ctx: &mut Context) -> TxUnjailValidator { + TxUnjailValidator { + tx: self.tx.to_sdk(ctx), + validator: ctx.get(&self.validator), + tx_code_path: self + .tx_code_path + .as_path() + .to_str() + .unwrap() + .to_string() + .into_bytes(), + } + } + } + + impl Args for TxUnjailValidator { + fn parse(matches: &ArgMatches) -> Self { + let tx = Tx::parse(matches); + let validator = VALIDATOR.parse(matches); + let tx_code_path = PathBuf::from(TX_UNJAIL_VALIDATOR_WASM); + Self { + tx, + validator, + tx_code_path, + } + } + + fn def(app: App) -> App { + app.add_args::>().arg( + VALIDATOR.def().about( + "The address of the jailed validator to re-activate.", + ), + ) + } + } + impl CliToSdk> for QueryCommissionRate { fn to_sdk(self, ctx: &mut Context) -> QueryCommissionRate { QueryCommissionRate:: { @@ -3111,6 +3210,31 @@ pub mod args { } } + impl Args for QueryFindValidator { + fn parse(matches: &ArgMatches) -> Self { + let query = Query::parse(matches); + let tm_addr = TM_ADDRESS.parse(matches); + Self { query, tm_addr } + } + + fn def(app: App) -> App { + app.add_args::>().arg( + TM_ADDRESS + .def() + .about("The address of the validator in Tendermint."), + ) + } + } + + impl CliToSdk> for QueryFindValidator { + fn to_sdk(self, ctx: &mut Context) -> QueryFindValidator { + QueryFindValidator:: { + query: self.query.to_sdk(ctx), + tm_addr: self.tm_addr, + } + } + } + impl CliToSdk> for QueryRawBytes { fn to_sdk(self, ctx: &mut Context) -> QueryRawBytes { QueryRawBytes:: { @@ -3165,7 +3289,7 @@ pub mod args { gas_limit: self.gas_limit, signing_key: self.signing_key.map(|x| ctx.get_cached(&x)), signer: self.signer.map(|x| ctx.get(&x)), - tx_code_path: ctx.read_wasm(self.tx_code_path), + tx_reveal_code_path: self.tx_reveal_code_path, password: self.password, expiration: self.expiration, chain_id: self.chain_id, @@ -3201,6 +3325,9 @@ pub mod args { initialized, the alias will be the prefix of each new \ address joined with a number.", )) + .arg(WALLET_ALIAS_FORCE.def().about( + "Override the alias without confirmation if it already exists.", + )) .arg(GAS_AMOUNT.def().about( "The amount being paid for the inclusion of this transaction", )) @@ -3251,7 +3378,7 @@ pub mod args { let expiration = EXPIRATION_OPT.parse(matches); let signing_key = SIGNING_KEY_OPT.parse(matches); let signer = SIGNER.parse(matches); - let tx_code_path = PathBuf::from(TX_REVEAL_PK); + let tx_reveal_code_path = PathBuf::from(TX_REVEAL_PK); let chain_id = CHAIN_ID_OPT.parse(matches); let password = None; Self { @@ -3268,7 +3395,7 @@ pub mod args { expiration, signing_key, signer, - tx_code_path, + tx_reveal_code_path, password, chain_id, } @@ -3318,6 +3445,9 @@ pub mod args { .def() .about("An alias to be associated with the new entry."), ) + .arg(ALIAS_FORCE.def().about( + "Override the alias without confirmation if it already exists.", + )) .arg( MASP_VALUE .def() @@ -3386,6 +3516,9 @@ pub mod args { "An alias to be associated with the payment address.", ), ) + .arg(ALIAS_FORCE.def().about( + "Override the alias without confirmation if it already exists.", + )) .arg(VIEWING_KEY.def().about("The viewing key.")) .arg(PIN.def().about( "Require that the single transaction to this address be \ @@ -3394,17 +3527,65 @@ pub mod args { } } + impl Args for KeyAndAddressRestore { + fn parse(matches: &ArgMatches) -> Self { + let scheme = SCHEME.parse(matches); + let alias = ALIAS_OPT.parse(matches); + let alias_force = ALIAS_FORCE.parse(matches); + let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); + let derivation_path = HD_WALLET_DERIVATION_PATH_OPT.parse(matches); + Self { + scheme, + alias, + alias_force, + unsafe_dont_encrypt, + derivation_path, + } + } + + fn def(app: App) -> App { + app.arg(SCHEME.def().about( + "The type of key that should be added. Argument must be \ + either ed25519 or secp256k1. If none provided, the default \ + key scheme is ed25519.", + )) + .arg(ALIAS_OPT.def().about( + "The key and address alias. If none provided, the alias will \ + be the public key hash.", + )) + .arg( + ALIAS_FORCE + .def() + .about("Force overwrite the alias if it already exists."), + ) + .arg(UNSAFE_DONT_ENCRYPT.def().about( + "UNSAFE: Do not encrypt the keypair. Do not use this for keys \ + used in a live network.", + )) + .arg(HD_WALLET_DERIVATION_PATH_OPT.def().about( + "HD key derivation path. Use keyword `default` to refer to a \ + scheme default path:\n- m/44'/60'/0'/0/0 for secp256k1 \ + scheme\n- m/44'/877'/0'/0'/0' for ed25519 scheme.\nFor \ + ed25519, all path indices will be promoted to hardened \ + indexes. If none is specified, the scheme default path is \ + used.", + )) + } + } + impl Args for KeyAndAddressGen { fn parse(matches: &ArgMatches) -> Self { let scheme = SCHEME.parse(matches); let alias = ALIAS_OPT.parse(matches); let alias_force = ALIAS_FORCE.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); + let derivation_path = HD_WALLET_DERIVATION_PATH_OPT.parse(matches); Self { scheme, alias, alias_force, unsafe_dont_encrypt, + derivation_path, } } @@ -3418,10 +3599,22 @@ pub mod args { "The key and address alias. If none provided, the alias will \ be the public key hash.", )) + .arg(ALIAS_FORCE.def().about( + "Override the alias without confirmation if it already exists.", + )) .arg(UNSAFE_DONT_ENCRYPT.def().about( "UNSAFE: Do not encrypt the keypair. Do not use this for keys \ used in a live network.", )) + .arg(HD_WALLET_DERIVATION_PATH_OPT.def().about( + "Generate a new key and wallet using BIP39 mnemonic code and \ + HD derivation path. Use keyword `default` to refer to a \ + scheme default path:\n- m/44'/60'/0'/0/0 for secp256k1 \ + scheme\n- m/44'/877'/0'/0'/0' for ed25519 scheme.\nFor \ + ed25519, all path indices will be promoted to hardened \ + indexes. If none specified, mnemonic code and derivation \ + path are not used.", + )) } } @@ -3586,6 +3779,9 @@ pub mod args { .def() .about("An alias to be associated with the address."), ) + .arg(ALIAS_FORCE.def().about( + "Override the alias without confirmation if it already exists.", + )) .arg( RAW_ADDRESS .def() diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 42c1bb6c94..630d329515 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -1,6 +1,6 @@ //! CLI input types can be used for command arguments -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::env; use std::marker::PhantomData; use std::path::{Path, PathBuf}; @@ -194,10 +194,31 @@ impl Context { wasm_loader::read_wasm_or_exit(self.wasm_dir(), file_name) } - /// Get address with vp type + /// Try to find an alias for a given address from the wallet. If not found, + /// formats the address into a string. + pub fn lookup_alias(&self, addr: &Address) -> String { + match self.wallet.find_alias(addr) { + Some(alias) => alias.to_string(), + None => addr.to_string(), + } + } + + /// Get addresses with tokens VP type. pub fn tokens(&self) -> HashSet
{ self.wallet.get_addresses_with_vp_type(AddressVpType::Token) } + + /// Get addresses with tokens VP type associated with their aliases. + pub fn tokens_with_aliases(&self) -> HashMap { + self.wallet + .get_addresses_with_vp_type(AddressVpType::Token) + .into_iter() + .map(|addr| { + let alias = self.lookup_alias(&addr); + (addr, alias) + }) + .collect() + } } /// Load global config from expected path in the `base_dir` or try to generate a diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 109b0ffa2d..70167f59e2 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -16,8 +16,7 @@ use data_encoding::HEXLOWER; use itertools::Either; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; -use masp_primitives::primitives::ViewingKey; -use masp_primitives::sapling::Node; +use masp_primitives::sapling::{Node, ViewingKey}; use masp_primitives::transaction::components::Amount; use masp_primitives::zip32::ExtendedFullViewingKey; use namada::core::types::transaction::governance::ProposalType; @@ -1562,6 +1561,30 @@ pub async fn query_delegations( } } +pub async fn query_find_validator( + client: &C, + args: args::QueryFindValidator, +) { + let args::QueryFindValidator { query: _, tm_addr } = args; + if tm_addr.len() != 40 { + eprintln!( + "Expected 40 characters in Tendermint address, got {}", + tm_addr.len() + ); + cli::safe_exit(1); + } + let tm_addr = tm_addr.to_ascii_uppercase(); + let validator = unwrap_client_response::( + RPC.vp().pos().validator_by_tm_addr(client, &tm_addr).await, + ); + match validator { + Some(address) => println!("Found validator address \"{address}\"."), + None => { + println!("No validator with Tendermint address {tm_addr} found.") + } + } +} + /// Dry run a transaction pub async fn dry_run_tx( client: &C, diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index eec89b4f80..c44da88f9b 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -5,10 +5,10 @@ use namada::ledger::rpc::TxBroadcastData; use namada::ledger::signing::TxSigningKey; use namada::ledger::tx; use namada::ledger::wallet::{Wallet, WalletUtils}; +use namada::proof_of_stake::Epoch; use namada::proto::Tx; use namada::types::address::Address; use namada::types::key::*; -use namada::types::storage::Epoch; use crate::cli::args; @@ -77,8 +77,12 @@ pub async fn sign_tx< /// Create a wrapper tx from a normal tx. Get the hash of the /// wrapper and its payload which is needed for monitoring its /// progress on chain. -pub async fn sign_wrapper( +pub async fn sign_wrapper< + C: namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, + wallet: &mut Wallet, args: &args::Tx, epoch: Epoch, tx: Tx, @@ -87,6 +91,7 @@ pub async fn sign_wrapper( ) -> TxBroadcastData { namada::ledger::signing::sign_wrapper( client, + wallet, args, epoch, tx, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index ce334adb41..20bcf7f83b 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -7,6 +7,7 @@ use std::path::PathBuf; use async_std::io; use async_std::io::prelude::WriteExt; +use async_trait::async_trait; use borsh::{BorshDeserialize, BorshSerialize}; use data_encoding::HEXLOWER_PERMISSIVE; use masp_proofs::prover::LocalTxProver; @@ -15,19 +16,21 @@ use namada::ledger::rpc::{TxBroadcastData, TxResponse}; use namada::ledger::signing::TxSigningKey; use namada::ledger::wallet::{Wallet, WalletUtils}; use namada::ledger::{masp, tx}; -use namada::proto::Tx; +use namada::proto::{Code, Data, Section, Tx}; use namada::types::address::Address; use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, VoteType, }; +use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::{Epoch, Key}; use namada::types::token; use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, }; -use namada::types::transaction::InitValidator; +use namada::types::transaction::{InitValidator, TxType}; use rust_decimal::Decimal; +use sha2::{Digest as Sha2Digest, Sha256}; use tendermint_rpc::HttpClient; use super::rpc; @@ -35,9 +38,12 @@ use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::rpc::query_wasm_code_hash; use crate::client::signing::find_keypair; +use crate::client::tx::tx::ProcessTxResponse; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::node::ledger::tendermint_node; -use crate::wallet::{gen_validator_keys, read_and_confirm_pwd, CliWalletUtils}; +use crate::wallet::{ + gen_validator_keys, read_and_confirm_encryption_password, CliWalletUtils, +}; pub async fn submit_custom( client: &C, @@ -111,14 +117,17 @@ pub async fn submit_init_validator< let consensus_key_alias = format!("{}-consensus-key", alias); let account_key = account_key.unwrap_or_else(|| { println!("Generating validator account key..."); - let password = read_and_confirm_pwd(unsafe_dont_encrypt); + let password = + read_and_confirm_encryption_password(unsafe_dont_encrypt); ctx.wallet .gen_key( scheme, Some(validator_key_alias.clone()), - password, tx_args.wallet_alias_force, + password, + None, ) + .expect("Key generation should not fail.") .1 .ref_to() }); @@ -133,15 +142,18 @@ pub async fn submit_init_validator< }) .unwrap_or_else(|| { println!("Generating consensus key..."); - let password = read_and_confirm_pwd(unsafe_dont_encrypt); + let password = + read_and_confirm_encryption_password(unsafe_dont_encrypt); ctx.wallet .gen_key( // Note that TM only allows ed25519 for consensus key SchemeType::Ed25519, Some(consensus_key_alias.clone()), - password, tx_args.wallet_alias_force, + password, + None, ) + .expect("Key generation should not fail.") .1 }); @@ -160,11 +172,12 @@ pub async fn submit_init_validator< .expect("DKG sessions keys should have been created") .public(); - let vp_code_path = String::from_utf8(validator_vp_code_path).unwrap(); - let validator_vp_code_hash = - query_wasm_code_hash::(client, vp_code_path) - .await - .unwrap(); + let validator_vp_code_hash = query_wasm_code_hash::( + client, + validator_vp_code_path.to_str().unwrap(), + ) + .await + .unwrap(); // Validate the commission rate data if commission_rate > Decimal::ONE || commission_rate < Decimal::ZERO { @@ -192,6 +205,12 @@ pub async fn submit_init_validator< .await .unwrap(); + let mut tx = Tx::new(TxType::Raw); + let extra = tx.add_section(Section::ExtraData(Code::from_hash( + validator_vp_code_hash, + ))); + let extra_hash = + Hash(extra.hash(&mut Sha256::new()).finalize_reset().into()); let data = InitValidator { account_key, consensus_key: consensus_key.ref_to(), @@ -199,15 +218,14 @@ pub async fn submit_init_validator< dkg_key, commission_rate, max_commission_rate_change, - validator_vp_code_hash, + validator_vp_code_hash: extra_hash, }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new( - tx_code_hash.to_vec(), - Some(data), - tx_args.chain_id.clone().unwrap(), - tx_args.expiration, - ); + tx.header.chain_id = tx_args.chain_id.clone().unwrap(); + tx.header.expiration = tx_args.expiration; + tx.set_data(Data::new(data)); + tx.set_code(Code::from_hash(tx_code_hash)); + let (mut ctx, result) = process_tx( client, ctx, @@ -315,7 +333,7 @@ impl CLIShieldedUtils { && output_path.exists()) { println!("MASP parameters not present, downloading..."); - masp_proofs::download_parameters() + masp_proofs::download_masp_parameters(None) .expect("MASP parameters not present or downloadable"); println!("MASP parameter download complete, resuming execution..."); } @@ -336,6 +354,7 @@ impl Default for CLIShieldedUtils { } } +#[async_trait(?Send)] impl masp::ShieldedUtils for CLIShieldedUtils { type C = tendermint_rpc::HttpClient; @@ -354,7 +373,7 @@ impl masp::ShieldedUtils for CLIShieldedUtils { /// Try to load the last saved shielded context from the given context /// directory. If this fails, then leave the current context unchanged. - fn load(self) -> std::io::Result> { + async fn load(self) -> std::io::Result> { // Try to load shielded context from file let mut ctx_file = File::open(self.context_dir.join(FILE_NAME))?; let mut bytes = Vec::new(); @@ -367,7 +386,10 @@ impl masp::ShieldedUtils for CLIShieldedUtils { } /// Save this shielded context into its associated context directory - fn save(&self, ctx: &masp::ShieldedContext) -> std::io::Result<()> { + async fn save( + &self, + ctx: &masp::ShieldedContext, + ) -> std::io::Result<()> { // TODO: use mktemp crate? let tmp_path = self.context_dir.join(TMP_FILE_NAME); { @@ -434,9 +456,9 @@ pub async fn submit_init_proposal( serde_json::from_reader(file).expect("JSON was not well-formatted"); let signer = WalletAddress::new(proposal.clone().author.to_string()); - let governance_parameters = rpc::get_governance_parameters(client).await; let current_epoch = rpc::query_and_print_epoch(client).await; + let governance_parameters = rpc::get_governance_parameters(client).await; if proposal.voting_start_epoch <= current_epoch || proposal.voting_start_epoch.0 % governance_parameters.min_proposal_period @@ -527,13 +549,10 @@ pub async fn submit_init_proposal( safe_exit(1) }; - let balance = rpc::get_token_balance( - client, - &args.native_token, - &proposal.author, - ) - .await - .unwrap_or_default(); + let balance = + rpc::get_token_balance(client, &ctx.native_token, &proposal.author) + .await + .unwrap_or_default(); if balance < token::Amount::from(governance_parameters.min_proposal_fund) { @@ -551,18 +570,17 @@ pub async fn submit_init_proposal( safe_exit(1); } + let mut tx = Tx::new(TxType::Raw); let data = init_proposal_data .try_to_vec() .expect("Encoding proposal data shouldn't fail"); let tx_code_hash = query_wasm_code_hash(client, args::TX_INIT_PROPOSAL) .await .unwrap(); - let tx = Tx::new( - tx_code_hash.to_vec(), - Some(data), - ctx.config.ledger.chain_id.clone(), - args.tx.expiration, - ); + tx.header.chain_id = ctx.config.ledger.chain_id.clone(); + tx.header.expiration = args.tx.expiration; + tx.set_data(Data::new(data)); + tx.set_code(Code::from_hash(tx_code_hash)); process_tx::( client, @@ -806,8 +824,18 @@ pub async fn submit_vote_proposal( let data = tx_data .try_to_vec() .expect("Encoding proposal data shouldn't fail"); - let tx_code = args.tx_code_path; - let tx = Tx::new(tx_code, Some(data), chain_id, expiration); + + let tx_code_hash = query_wasm_code_hash( + client, + args.tx_code_path.to_str().unwrap(), + ) + .await + .unwrap(); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = chain_id; + tx.header.expiration = expiration; + tx.set_data(Data::new(data)); + tx.set_code(Code::from_hash(tx_code_hash)); process_tx::( client, @@ -873,7 +901,7 @@ pub async fn submit_reveal_pk_aux( ctx: &mut Context, public_key: &common::PublicKey, args: &args::Tx, -) -> Result<(), tx::Error> { +) -> Result { let args = args::Tx { chain_id: args .clone() @@ -990,6 +1018,20 @@ pub async fn submit_validator_commission_change< .await } +pub async fn submit_unjail_validator< + C: namada::ledger::queries::Client + Sync, +>( + client: &C, + mut ctx: Context, + mut args: args::TxUnjailValidator, +) -> Result<(), tx::Error> { + args.tx.chain_id = args + .tx + .chain_id + .or_else(|| Some(ctx.config.ledger.chain_id.clone())); + tx::submit_unjail_validator::(client, &mut ctx.wallet, args).await +} + /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. async fn process_tx( @@ -1001,7 +1043,10 @@ async fn process_tx( #[cfg(not(feature = "mainnet"))] requires_pow: bool, ) -> Result<(Context, Vec
), tx::Error> { let args = args::Tx { - chain_id: args.clone().chain_id.or_else(|| Some(tx.chain_id.clone())), + chain_id: args + .clone() + .chain_id + .or_else(|| Some(tx.header.chain_id.clone())), ..args.clone() }; let res: Vec
= tx::process_tx::( @@ -1013,7 +1058,8 @@ async fn process_tx( #[cfg(not(feature = "mainnet"))] requires_pow, ) - .await?; + .await? + .initialized_accounts(); Ok((ctx, res)) } diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 107c5465dd..cb3f42b377 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -31,7 +31,9 @@ use crate::config::{self, Config, TendermintMode}; use crate::facade::tendermint::node::Id as TendermintNodeId; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::node::ledger::tendermint_node; -use crate::wallet::{pre_genesis, read_and_confirm_pwd, CliWalletUtils}; +use crate::wallet::{ + pre_genesis, read_and_confirm_encryption_password, CliWalletUtils, +}; use crate::wasm_loader; pub const NET_ACCOUNTS_DIR: &str = "setup"; @@ -491,7 +493,7 @@ pub fn init_network( config.address = Some(address.to_string()); // Generate the consensus, account and reward keys, unless they're - // pre-defined. + // pre-defined. Do not use mnemonic code / HD derivation path. let mut wallet = crate::wallet::load_or_new(&chain_dir); let consensus_pk = try_parse_public_key( @@ -501,13 +503,11 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-consensus-key", name); println!("Generating validator {} consensus key...", name); - let password = read_and_confirm_pwd(unsafe_dont_encrypt); - let (_alias, keypair) = wallet.gen_key( - SchemeType::Ed25519, - Some(alias), - password, - true, - ); + let password = + read_and_confirm_encryption_password(unsafe_dont_encrypt); + let (_alias, keypair) = wallet + .gen_key(SchemeType::Ed25519, Some(alias), true, password, None) + .expect("Key generation should not fail."); // Write consensus key for Tendermint tendermint_node::write_validator_key(&tm_home_dir, &keypair); @@ -522,13 +522,11 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-account-key", name); println!("Generating validator {} account key...", name); - let password = read_and_confirm_pwd(unsafe_dont_encrypt); - let (_alias, keypair) = wallet.gen_key( - SchemeType::Ed25519, - Some(alias), - password, - true, - ); + let password = + read_and_confirm_encryption_password(unsafe_dont_encrypt); + let (_alias, keypair) = wallet + .gen_key(SchemeType::Ed25519, Some(alias), true, password, None) + .expect("Key generation should not fail."); keypair.ref_to() }); @@ -539,13 +537,11 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-protocol-key", name); println!("Generating validator {} protocol signing key...", name); - let password = read_and_confirm_pwd(unsafe_dont_encrypt); - let (_alias, keypair) = wallet.gen_key( - SchemeType::Ed25519, - Some(alias), - password, - true, - ); + let password = + read_and_confirm_encryption_password(unsafe_dont_encrypt); + let (_alias, keypair) = wallet + .gen_key(SchemeType::Ed25519, Some(alias), true, password, None) + .expect("Key generation should not fail."); keypair.ref_to() }); @@ -593,7 +589,8 @@ pub fn init_network( crate::wallet::save(&wallet).unwrap(); }); - // Create a wallet for all accounts other than validators + // Create a wallet for all accounts other than validators. + // Do not use mnemonic code / HD derivation path. let mut wallet = crate::wallet::load_or_new(&accounts_dir.join(NET_OTHER_ACCOUNTS_DIR)); if let Some(established) = &mut config.established { @@ -625,13 +622,17 @@ pub fn init_network( "Generating implicit account {} key and address ...", name ); - let password = read_and_confirm_pwd(unsafe_dont_encrypt); - let (_alias, keypair) = wallet.gen_key( - SchemeType::Ed25519, - Some(name.clone()), - password, - true, - ); + let password = + read_and_confirm_encryption_password(unsafe_dont_encrypt); + let (_alias, keypair) = wallet + .gen_key( + SchemeType::Ed25519, + Some(name.clone()), + true, + password, + None, + ) + .expect("Key generation should not fail."); let public_key = genesis_config::HexString(keypair.ref_to().to_string()); config.public_key = Some(public_key); @@ -877,13 +878,17 @@ fn init_established_account( } if config.public_key.is_none() { println!("Generating established account {} key...", name.as_ref()); - let password = read_and_confirm_pwd(unsafe_dont_encrypt); - let (_alias, keypair) = wallet.gen_key( - SchemeType::Ed25519, - Some(format!("{}-key", name.as_ref())), - password, - true, - ); + let password = + read_and_confirm_encryption_password(unsafe_dont_encrypt); + let (_alias, keypair) = wallet + .gen_key( + SchemeType::Ed25519, + Some(format!("{}-key", name.as_ref())), + true, + password, + None, // do not use mnemonic code / HD derivation path + ) + .expect("Key generation should not fail."); let public_key = genesis_config::HexString(keypair.ref_to().to_string()); config.public_key = Some(public_key); diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 922b0c445f..fb9ae608dc 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -305,6 +305,9 @@ pub mod genesis_config { // light client attack. // XXX: u64 doesn't work with toml-rs! pub light_client_attack_min_slash_rate: Decimal, + /// Number of epochs above and below (separately) the current epoch to + /// consider when doing cubic slashing + pub cubic_slashing_window_length: u64, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -647,6 +650,7 @@ pub mod genesis_config { target_staked_ratio, duplicate_vote_min_slash_rate, light_client_attack_min_slash_rate, + cubic_slashing_window_length, } = pos_params; let pos_params = PosParams { max_validator_slots, @@ -659,6 +663,7 @@ pub mod genesis_config { target_staked_ratio, duplicate_vote_min_slash_rate, light_client_attack_min_slash_rate, + cubic_slashing_window_length, }; let mut genesis = Genesis { diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 84a8bd6efd..f57018a75a 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -103,7 +103,10 @@ where // Invariant: This has to be applied after // `copy_validator_sets_and_positions` if we're starting a new epoch - self.slash(); + self.record_slashes_from_evidence(); + if new_epoch { + self.process_slashes(); + } let wrapper_fees = self.get_wrapper_tx_fees(); let mut stats = InternalStats::default(); @@ -127,26 +130,19 @@ where if ErrorCodes::from_u32(processed_tx.result.code).unwrap() == ErrorCodes::InvalidSig { - let mut tx_event = match process_tx(tx.clone()) { - Ok(tx @ TxType::Wrapper(_)) - | Ok(tx @ TxType::Protocol(_)) => { + let mut tx_event = match tx.header().tx_type { + TxType::Wrapper(_) | TxType::Protocol(_) => { Event::new_tx_event(&tx, height.0) } - _ => match TxType::try_from(tx) { - Ok(tx @ TxType::Wrapper(_)) - | Ok(tx @ TxType::Protocol(_)) => { - Event::new_tx_event(&tx, height.0) - } - _ => { - tracing::error!( - "Internal logic error: FinalizeBlock received \ - a tx with an invalid signature error code \ - that could not be deserialized to a \ - WrapperTx / ProtocolTx type" - ); - continue; - } - }, + _ => { + tracing::error!( + "Internal logic error: FinalizeBlock received a \ + tx with an invalid signature error code that \ + could not be deserialized to a WrapperTx / \ + ProtocolTx type" + ); + continue; + } }; tx_event["code"] = processed_tx.result.code.to_string(); tx_event["info"] = @@ -156,8 +152,8 @@ where continue; } - let tx_type = if let Ok(tx_type) = process_tx(tx) { - tx_type + let tx = if let Ok(()) = tx.validate_header() { + tx } else { tracing::error!( "Internal logic error: FinalizeBlock received tx that \ @@ -165,12 +161,13 @@ where ); continue; }; + let tx_type = tx.header(); // If [`process_proposal`] rejected a Tx, emit an event here and // move on to next tx if ErrorCodes::from_u32(processed_tx.result.code).unwrap() != ErrorCodes::Ok { - let mut tx_event = Event::new_tx_event(&tx_type, height.0); + let mut tx_event = Event::new_tx_event(&tx, height.0); tx_event["code"] = processed_tx.result.code.to_string(); tx_event["info"] = format!("Tx rejected: {}", &processed_tx.result.info); @@ -179,7 +176,7 @@ where // if the rejected tx was decrypted, remove it // from the queue of txs to be processed and remove the hash // from storage - if let TxType::Decrypted(_) = &tx_type { + if let TxType::Decrypted(_) = &tx_type.tx_type { let tx_hash = self .wl_storage .storage @@ -187,7 +184,9 @@ where .pop() .expect("Missing wrapper tx in queue") .tx - .tx_hash; + .clone() + .update_header(TxType::Raw) + .header_hash(); let tx_hash_key = replay_protection::get_tx_hash_key(&tx_hash); self.wl_storage @@ -198,24 +197,26 @@ where continue; } - let (mut tx_event, tx_unsigned_hash) = match &tx_type { + let (mut tx_event, tx_unsigned_hash) = match &tx_type.tx_type { TxType::Wrapper(wrapper) => { stats.increment_wrapper_txs(); - let mut tx_event = Event::new_tx_event(&tx_type, height.0); + let mut tx_event = Event::new_tx_event(&tx, height.0); // Writes both txs hash to storage - let tx = Tx::try_from(processed_tx.tx.as_ref()).unwrap(); + let processed_tx = + Tx::try_from(processed_tx.tx.as_ref()).unwrap(); let wrapper_tx_hash_key = replay_protection::get_tx_hash_key(&hash::Hash( - tx.unsigned_hash(), + processed_tx.header_hash().0, )); self.wl_storage .storage .write(&wrapper_tx_hash_key, vec![]) .expect("Error while writing tx hash to storage"); - let inner_tx_hash_key = - replay_protection::get_tx_hash_key(&wrapper.tx_hash); + let inner_tx_hash_key = replay_protection::get_tx_hash_key( + &tx.clone().update_header(TxType::Raw).header_hash(), + ); self.wl_storage .storage .write(&inner_tx_hash_key, vec![]) @@ -276,8 +277,8 @@ where } } - self.wl_storage.storage.tx_queue.push(WrapperTxInQueue { - tx: wrapper.clone(), + self.wl_storage.storage.tx_queue.push(TxInQueue { + tx: processed_tx.clone(), #[cfg(not(feature = "mainnet"))] has_valid_pow, }); @@ -292,20 +293,23 @@ where .pop() .expect("Missing wrapper tx in queue") .tx - .tx_hash; - let mut event = Event::new_tx_event(&tx_type, height.0); + .clone() + .update_header(TxType::Raw) + .header_hash(); + let mut event = Event::new_tx_event(&tx, height.0); match inner { - DecryptedTx::Decrypted { - tx, - has_valid_pow: _, - } => { - stats.increment_tx_type( - namada::core::types::hash::Hash(tx.code_hash()) - .to_string(), - ); + DecryptedTx::Decrypted { has_valid_pow: _ } => { + if let Some(code_sec) = tx + .get_section(tx.code_sechash()) + .and_then(Section::code_sec) + { + stats.increment_tx_type( + code_sec.code.hash().to_string(), + ); + } } - DecryptedTx::Undecryptable(_) => { + DecryptedTx::Undecryptable => { event["log"] = "Transaction could not be decrypted.".into(); event["code"] = ErrorCodes::Undecryptable.into(); @@ -313,7 +317,7 @@ where } (event, Some(wrapper_hash)) } - TxType::Raw(_) => { + TxType::Raw => { tracing::error!( "Internal logic error: FinalizeBlock received a \ TxType::Raw transaction" @@ -330,7 +334,7 @@ where }; match protocol::apply_tx( - tx_type, + tx.clone(), tx_length, TxIndex( tx_index @@ -418,8 +422,8 @@ where .storage .delete(&tx_hash_key) .expect( - "Error while deleting tx hash key from storage", - ); + "Error while deleting tx hash key from storage", + ); } } @@ -601,7 +605,7 @@ where /// executed while finalizing the first block of a new epoch and is applied /// with respect to the previous epoch. fn apply_inflation(&mut self, current_epoch: Epoch) -> Result<()> { - let last_epoch = current_epoch - 1; + let last_epoch = current_epoch.prev(); // Get input values needed for the PD controller for PoS and MASP. // Run the PD controllers to calculate new rates. // @@ -892,26 +896,39 @@ mod test_finalize_block { use namada::ledger::parameters::EpochDuration; use namada::ledger::storage_api; use namada::proof_of_stake::btree_set::BTreeSetShims; - use namada::proof_of_stake::types::WeightedValidator; + use namada::proof_of_stake::parameters::PosParams; + use namada::proof_of_stake::storage::{ + is_validator_slashes_key, slashes_prefix, + }; + use namada::proof_of_stake::types::{ + decimal_mult_amount, BondId, SlashType, ValidatorState, + WeightedValidator, + }; use namada::proof_of_stake::{ + enqueued_slashes_handle, get_num_consensus_validators, read_consensus_validator_set_addresses_with_stake, - rewards_accumulator_handle, validator_consensus_key_handle, - validator_rewards_products_handle, + rewards_accumulator_handle, unjail_validator, + validator_consensus_key_handle, validator_rewards_products_handle, + validator_slashes_handle, validator_state_handle, write_pos_params, }; + use namada::proto::{Code, Data, Section, Signature}; use namada::types::governance::ProposalVote; use namada::types::key::tm_consensus_key_raw_hash; use namada::types::storage::Epoch; use namada::types::time::DurationSecs; + use namada::types::token::Amount; use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, }; - use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; + use namada::types::transaction::{Fee, WrapperTx, MIN_FEE}; use namada_test_utils::TestWasms; use rust_decimal_macros::dec; use test_log::test; use super::*; - use crate::facade::tendermint_proto::abci::{Validator, VoteInfo}; + use crate::facade::tendermint_proto::abci::{ + Misbehavior, Validator, VoteInfo, + }; use crate::node::ledger::shell::test_utils::*; use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ FinalizeBlock, ProcessedTx, @@ -940,31 +957,31 @@ mod test_finalize_block { // create some wrapper txs for i in 1u64..5 { - let raw_tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some(format!("transaction data: {}", i).as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( - Fee { - amount: MIN_FEE.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, + let mut wrapper = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: MIN_FEE.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_data(Data::new("wasm_code".as_bytes().to_owned())); + wrapper.set_code(Code::new( + format!("transaction data: {}", i).as_bytes().to_owned(), + )); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), &keypair, - Epoch(0), - 0.into(), - raw_tx.clone(), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ); - let tx = wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + ))); + wrapper.encrypt(&Default::default()); if i > 1 { processed_txs.push(ProcessedTx { - tx: tx.to_bytes(), + tx: wrapper.to_bytes(), result: TxResult { code: u32::try_from(i.rem_euclid(2)) .expect("Test failed"), @@ -1000,10 +1017,9 @@ mod test_finalize_block { for wrapper in shell.iter_tx_queue() { // we cannot easily implement the PartialEq trait for WrapperTx // so we check the hashes of the inner txs for equality - assert_eq!( - wrapper.tx.tx_hash, - valid_tx.next().expect("Test failed").tx_hash - ); + let valid_tx = valid_tx.next().expect("Test failed"); + assert_eq!(wrapper.tx.header.code_hash, *valid_tx.code_sechash()); + assert_eq!(wrapper.tx.header.data_hash, *valid_tx.data_sechash()); counter += 1; } assert_eq!(counter, 3); @@ -1017,13 +1033,7 @@ mod test_finalize_block { fn test_process_proposal_rejected_decrypted_tx() { let (mut shell, _) = setup(1); let keypair = gen_keypair(); - let raw_tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some(String::from("transaction data").as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1031,25 +1041,28 @@ mod test_finalize_block { &keypair, Epoch(0), 0.into(), - raw_tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + outer_tx.set_data(Data::new( + String::from("transaction data").as_bytes().to_owned(), + )); + outer_tx.encrypt(&Default::default()); + shell.enqueue_tx(outer_tx.clone()); + + outer_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + })); let processed_tx = ProcessedTx { - tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { - tx: raw_tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - })) - .to_bytes(), + tx: outer_tx.to_bytes(), result: TxResult { code: ErrorCodes::InvalidTx.into(), info: "".into(), }, }; - shell.enqueue_tx(wrapper); // check that the decrypted tx was not applied for event in shell @@ -1074,13 +1087,7 @@ mod test_finalize_block { let (mut shell, _) = setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - let pubkey = EncryptionKey::default(); // not valid tx bytes - let tx = "garbage data".as_bytes().to_owned(); - let inner_tx = - namada::types::transaction::encrypted::EncryptedTx::encrypt( - &tx, pubkey, - ); let wrapper = WrapperTx { fee: Fee { amount: 0.into(), @@ -1089,23 +1096,20 @@ mod test_finalize_block { pk: keypair.ref_to(), epoch: Epoch(0), gas_limit: 0.into(), - inner_tx, - tx_hash: hash_tx(&tx), #[cfg(not(feature = "mainnet"))] pow_solution: None, }; let processed_tx = ProcessedTx { - tx: Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( - wrapper.clone(), - ))) - .to_bytes(), + tx: Tx::new(TxType::Decrypted(DecryptedTx::Undecryptable)) + .to_bytes(), result: TxResult { code: ErrorCodes::Ok.into(), info: "".into(), }, }; - shell.enqueue_tx(wrapper); + let tx = Tx::new(TxType::Wrapper(Box::new(wrapper))); + shell.enqueue_tx(tx); // check that correct error message is returned for event in shell @@ -1149,37 +1153,35 @@ mod test_finalize_block { // create two decrypted txs let tx_code = TestWasms::TxNoOp.read_bytes(); for i in 0..2 { - let raw_tx = Tx::new( - tx_code.clone(), - Some( - format!("Decrypted transaction data: {}", i) - .as_bytes() - .to_owned(), - ), - shell.chain_id.clone(), - None, - ); - let wrapper_tx = WrapperTx::new( - Fee { - amount: MIN_FEE.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - &keypair, - Epoch(0), - 0.into(), - raw_tx.clone(), - Default::default(), + let mut outer_tx = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: MIN_FEE.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new(tx_code.clone())); + outer_tx.set_data(Data::new( + format!("Decrypted transaction data: {}", i) + .as_bytes() + .to_owned(), + )); + outer_tx.encrypt(&Default::default()); + shell.enqueue_tx(outer_tx.clone()); + outer_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] - None, - ); - shell.enqueue_tx(wrapper_tx); + has_valid_pow: false, + })); + outer_tx.decrypt(::G2Affine::prime_subgroup_generator()) + .expect("Test failed"); processed_txs.push(ProcessedTx { - tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { - tx: raw_tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - })) - .to_bytes(), + tx: outer_tx.to_bytes(), result: TxResult { code: ErrorCodes::Ok.into(), info: "".into(), @@ -1188,35 +1190,33 @@ mod test_finalize_block { } // create two wrapper txs for i in 0..2 { - let raw_tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some( - format!("Encrypted transaction data: {}", i) - .as_bytes() - .to_owned(), - ), - shell.chain_id.clone(), - None, - ); - let wrapper_tx = WrapperTx::new( - Fee { - amount: MIN_FEE.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, + let mut wrapper_tx = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: MIN_FEE.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + wrapper_tx.header.chain_id = shell.chain_id.clone(); + wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper_tx.set_data(Data::new( + format!("Encrypted transaction data: {}", i) + .as_bytes() + .to_owned(), + )); + wrapper_tx.add_section(Section::Signature(Signature::new( + &wrapper_tx.header_hash(), &keypair, - Epoch(0), - 0.into(), - raw_tx.clone(), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ); - let wrapper = wrapper_tx - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); - valid_txs.push(wrapper_tx); + ))); + wrapper_tx.encrypt(&Default::default()); + valid_txs.push(wrapper_tx.clone()); processed_txs.push(ProcessedTx { - tx: wrapper.to_bytes(), + tx: wrapper_tx.to_bytes(), result: TxResult { code: ErrorCodes::Ok.into(), info: "".into(), @@ -1262,10 +1262,9 @@ mod test_finalize_block { let mut counter = 0; for wrapper in shell.iter_tx_queue() { - assert_eq!( - wrapper.tx.tx_hash, - txs.next().expect("Test failed").tx_hash - ); + let next = txs.next().expect("Test failed"); + assert_eq!(wrapper.tx.header.code_hash, *next.code_sechash()); + assert_eq!(wrapper.tx.header.data_hash, *next.data_sechash()); counter += 1; } assert_eq!(counter, 2); @@ -1507,7 +1506,7 @@ mod test_finalize_block { // FINALIZE BLOCK 1. Tell Namada that val1 is the block proposer. We // won't receive votes from TM since we receive votes at a 1-block // delay, so votes will be empty here - next_block_for_inflation(&mut shell, pkh1.clone(), vec![]); + next_block_for_inflation(&mut shell, pkh1.clone(), vec![], None); assert!( rewards_accumulator_handle() .is_empty(&shell.wl_storage) @@ -1517,7 +1516,7 @@ mod test_finalize_block { // FINALIZE BLOCK 2. Tell Namada that val1 is the block proposer. // Include votes that correspond to block 1. Make val2 the next block's // proposer. - next_block_for_inflation(&mut shell, pkh2.clone(), votes.clone()); + next_block_for_inflation(&mut shell, pkh2.clone(), votes.clone(), None); assert!(rewards_prod_1.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_2.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); @@ -1540,7 +1539,7 @@ mod test_finalize_block { ); // FINALIZE BLOCK 3, with val1 as proposer for the next block. - next_block_for_inflation(&mut shell, pkh1.clone(), votes); + next_block_for_inflation(&mut shell, pkh1.clone(), votes, None); assert!(rewards_prod_1.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_2.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); @@ -1592,7 +1591,7 @@ mod test_finalize_block { // FINALIZE BLOCK 4. The next block proposer will be val1. Only val1, // val2, and val3 vote on this block. - next_block_for_inflation(&mut shell, pkh1.clone(), votes.clone()); + next_block_for_inflation(&mut shell, pkh1.clone(), votes.clone(), None); assert!(rewards_prod_1.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_2.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); @@ -1625,7 +1624,12 @@ mod test_finalize_block { get_rewards_acc(&shell.wl_storage), get_rewards_sum(&shell.wl_storage), ); - next_block_for_inflation(&mut shell, pkh1.clone(), votes.clone()); + next_block_for_inflation( + &mut shell, + pkh1.clone(), + votes.clone(), + None, + ); } assert!( rewards_accumulator_handle() @@ -1680,12 +1684,26 @@ mod test_finalize_block { shell: &mut TestShell, proposer_address: Vec, votes: Vec, + byzantine_validators: Option>, ) { - let req = FinalizeBlock { + // Let the header time be always ahead of the next epoch min start time + let header = Header { + time: shell + .wl_storage + .storage + .next_epoch_min_start_time + .next_second(), + ..Default::default() + }; + let mut req = FinalizeBlock { + header, proposer_address, votes, ..Default::default() }; + if let Some(byz_vals) = byzantine_validators { + req.byzantine_validators = byz_vals; + } shell.finalize_block(req).unwrap(); shell.commit(); } @@ -1701,29 +1719,35 @@ mod test_finalize_block { wasm_path.push("wasm_for_tests/tx_no_op.wasm"); let tx_code = std::fs::read(wasm_path) .expect("Expected a file at given code path"); - let raw_tx = Tx::new( - tx_code, - Some("Encrypted transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper_tx = WrapperTx::new( - Fee { - amount: 0.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - &keypair, - Epoch(0), - 0.into(), - raw_tx.clone(), - Default::default(), + let mut wrapper_tx = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + wrapper_tx.header.chain_id = shell.chain_id.clone(); + wrapper_tx.set_code(Code::new(tx_code)); + wrapper_tx.set_data(Data::new( + "Encrypted transaction data".as_bytes().to_owned(), + )); + let mut decrypted_tx = wrapper_tx.clone(); + wrapper_tx.encrypt(&Default::default()); + + decrypted_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] - None, - ); + has_valid_pow: false, + })); // Write inner hash in storage - let inner_hash_key = - replay_protection::get_tx_hash_key(&wrapper_tx.tx_hash); + let inner_hash_key = replay_protection::get_tx_hash_key( + &wrapper_tx.clone().update_header(TxType::Raw).header_hash(), + ); shell .wl_storage .storage @@ -1731,12 +1755,7 @@ mod test_finalize_block { .expect("Test failed"); let processed_tx = ProcessedTx { - tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { - tx: raw_tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - })) - .to_bytes(), + tx: decrypted_tx.to_bytes(), result: TxResult { code: ErrorCodes::Ok.into(), info: "".into(), @@ -1766,4 +1785,1226 @@ mod test_finalize_block { // .0 // ) } + + #[test] + fn test_ledger_slashing() -> storage_api::Result<()> { + let num_validators = 7_u64; + let (mut shell, _) = setup(num_validators); + let mut params = read_pos_params(&shell.wl_storage).unwrap(); + params.unbonding_len = 4; + write_pos_params(&mut shell.wl_storage, params.clone())?; + + let validator_set: Vec = + read_consensus_validator_set_addresses_with_stake( + &shell.wl_storage, + Epoch::default(), + ) + .unwrap() + .into_iter() + .collect(); + + let val1 = validator_set[0].clone(); + let val2 = validator_set[1].clone(); + + let initial_stake = val1.bonded_stake; + let total_initial_stake = num_validators * initial_stake; + + let get_pkh = |address, epoch| { + let ck = validator_consensus_key_handle(&address) + .get(&shell.wl_storage, epoch, ¶ms) + .unwrap() + .unwrap(); + let hash_string = tm_consensus_key_raw_hash(&ck); + HEXUPPER.decode(hash_string.as_bytes()).unwrap() + }; + + let mut all_pkhs: Vec> = Vec::new(); + let mut behaving_pkhs: Vec> = Vec::new(); + for (idx, validator) in validator_set.iter().enumerate() { + // Every validator should be in the consensus set + assert_eq!( + validator_state_handle(&validator.address) + .get(&shell.wl_storage, Epoch::default(), ¶ms) + .unwrap(), + Some(ValidatorState::Consensus) + ); + all_pkhs.push(get_pkh(validator.address.clone(), Epoch::default())); + if idx > 1_usize { + behaving_pkhs + .push(get_pkh(validator.address.clone(), Epoch::default())); + } + } + + let pkh1 = all_pkhs[0].clone(); + let pkh2 = all_pkhs[1].clone(); + + // Finalize block 1 (no votes since this is the first block) + next_block_for_inflation(&mut shell, pkh1.clone(), vec![], None); + + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + assert!(!votes.is_empty()); + assert_eq!(votes.len(), 7_usize); + + // For block 2, include the evidences found for block 1. + // NOTE: Only the type, height, and validator address fields from the + // Misbehavior struct are used in Namada + let byzantine_validators = vec![ + Misbehavior { + r#type: 1, + validator: Some(Validator { + address: pkh1.clone(), + power: Default::default(), + }), + height: 1, + time: Default::default(), + total_voting_power: Default::default(), + }, + Misbehavior { + r#type: 2, + validator: Some(Validator { + address: pkh2, + power: Default::default(), + }), + height: 1, + time: Default::default(), + total_voting_power: Default::default(), + }, + ]; + next_block_for_inflation( + &mut shell, + pkh1.clone(), + votes, + Some(byzantine_validators), + ); + + let processing_epoch = shell.wl_storage.storage.block.epoch + + params.unbonding_len + + 1_u64 + + params.cubic_slashing_window_length; + + // Check that the ValidatorState, enqueued slashes, and validator sets + // are properly updated + assert_eq!( + validator_state_handle(&val1.address) + .get(&shell.wl_storage, Epoch::default(), ¶ms) + .unwrap(), + Some(ValidatorState::Consensus) + ); + assert_eq!( + validator_state_handle(&val2.address) + .get(&shell.wl_storage, Epoch::default(), ¶ms) + .unwrap(), + Some(ValidatorState::Consensus) + ); + assert!( + enqueued_slashes_handle() + .at(&Epoch::default()) + .is_empty(&shell.wl_storage)? + ); + assert_eq!( + get_num_consensus_validators(&shell.wl_storage, Epoch::default()) + .unwrap(), + 7_u64 + ); + for epoch in Epoch::default().next().iter_range(params.pipeline_len) { + assert_eq!( + validator_state_handle(&val1.address) + .get(&shell.wl_storage, epoch, ¶ms) + .unwrap(), + Some(ValidatorState::Jailed) + ); + assert_eq!( + validator_state_handle(&val2.address) + .get(&shell.wl_storage, epoch, ¶ms) + .unwrap(), + Some(ValidatorState::Jailed) + ); + assert!( + enqueued_slashes_handle() + .at(&epoch) + .is_empty(&shell.wl_storage)? + ); + assert_eq!( + get_num_consensus_validators(&shell.wl_storage, epoch).unwrap(), + 5_u64 + ); + } + assert!( + !enqueued_slashes_handle() + .at(&processing_epoch) + .is_empty(&shell.wl_storage)? + ); + + // Advance to the processing epoch + let votes = get_default_true_votes(&shell.wl_storage, Epoch::default()); + loop { + next_block_for_inflation( + &mut shell, + pkh1.clone(), + votes.clone(), + None, + ); + // println!( + // "Block {} epoch {}", + // shell.wl_storage.storage.block.height, + // shell.wl_storage.storage.block.epoch + // ); + if shell.wl_storage.storage.block.epoch == processing_epoch { + // println!("Reached processing epoch"); + break; + } else { + assert!( + enqueued_slashes_handle() + .at(&shell.wl_storage.storage.block.epoch) + .is_empty(&shell.wl_storage)? + ); + let stake1 = read_validator_stake( + &shell.wl_storage, + ¶ms, + &val1.address, + shell.wl_storage.storage.block.epoch, + )? + .unwrap(); + let stake2 = read_validator_stake( + &shell.wl_storage, + ¶ms, + &val2.address, + shell.wl_storage.storage.block.epoch, + )? + .unwrap(); + let total_stake = read_total_stake( + &shell.wl_storage, + ¶ms, + shell.wl_storage.storage.block.epoch, + )?; + assert_eq!(stake1, initial_stake); + assert_eq!(stake2, initial_stake); + assert_eq!(total_stake, total_initial_stake); + } + } + + let num_slashes = storage_api::iter_prefix_bytes( + &shell.wl_storage, + &slashes_prefix(), + )? + .filter(|kv_res| { + let (k, _v) = kv_res.as_ref().unwrap(); + is_validator_slashes_key(k).is_some() + }) + .count(); + + assert_eq!(num_slashes, 2); + assert_eq!( + validator_slashes_handle(&val1.address) + .len(&shell.wl_storage) + .unwrap(), + 1_u64 + ); + assert_eq!( + validator_slashes_handle(&val2.address) + .len(&shell.wl_storage) + .unwrap(), + 1_u64 + ); + + let slash1 = validator_slashes_handle(&val1.address) + .get(&shell.wl_storage, 0)? + .unwrap(); + let slash2 = validator_slashes_handle(&val2.address) + .get(&shell.wl_storage, 0)? + .unwrap(); + + assert_eq!(slash1.r#type, SlashType::DuplicateVote); + assert_eq!(slash2.r#type, SlashType::LightClientAttack); + assert_eq!(slash1.epoch, Epoch::default()); + assert_eq!(slash2.epoch, Epoch::default()); + + // Each validator has equal weight in this test, and two have been + // slashed + let frac = dec!(2) / dec!(7); + let cubic_rate = dec!(9) * frac * frac; + + assert_eq!(slash1.rate, cubic_rate); + assert_eq!(slash2.rate, cubic_rate); + + // Check that there are still 5 consensus validators and the 2 + // misbehaving ones are still jailed + for epoch in shell + .wl_storage + .storage + .block + .epoch + .iter_range(params.pipeline_len + 1) + { + assert_eq!( + validator_state_handle(&val1.address) + .get(&shell.wl_storage, epoch, ¶ms) + .unwrap(), + Some(ValidatorState::Jailed) + ); + assert_eq!( + validator_state_handle(&val2.address) + .get(&shell.wl_storage, epoch, ¶ms) + .unwrap(), + Some(ValidatorState::Jailed) + ); + assert_eq!( + get_num_consensus_validators(&shell.wl_storage, epoch).unwrap(), + 5_u64 + ); + } + + // Check that the deltas at the pipeline epoch are slashed + let pipeline_epoch = + shell.wl_storage.storage.block.epoch + params.pipeline_len; + let stake1 = read_validator_stake( + &shell.wl_storage, + ¶ms, + &val1.address, + pipeline_epoch, + )? + .unwrap(); + let stake2 = read_validator_stake( + &shell.wl_storage, + ¶ms, + &val2.address, + pipeline_epoch, + )? + .unwrap(); + let total_stake = + read_total_stake(&shell.wl_storage, ¶ms, pipeline_epoch)?; + + let expected_slashed = decimal_mult_amount(cubic_rate, initial_stake); + assert_eq!(stake1, initial_stake - expected_slashed); + assert_eq!(stake2, initial_stake - expected_slashed); + assert_eq!(total_stake, total_initial_stake - 2 * expected_slashed); + + // Unjail one of the validators + let current_epoch = shell.wl_storage.storage.block.epoch; + unjail_validator(&mut shell.wl_storage, &val1.address, current_epoch)?; + let pipeline_epoch = current_epoch + params.pipeline_len; + + // Check that the state is the same until the pipeline epoch, at which + // point one validator is unjailed + for epoch in shell + .wl_storage + .storage + .block + .epoch + .iter_range(params.pipeline_len) + { + assert_eq!( + validator_state_handle(&val1.address) + .get(&shell.wl_storage, epoch, ¶ms) + .unwrap(), + Some(ValidatorState::Jailed) + ); + assert_eq!( + validator_state_handle(&val2.address) + .get(&shell.wl_storage, epoch, ¶ms) + .unwrap(), + Some(ValidatorState::Jailed) + ); + assert_eq!( + get_num_consensus_validators(&shell.wl_storage, epoch).unwrap(), + 5_u64 + ); + } + assert_eq!( + validator_state_handle(&val1.address) + .get(&shell.wl_storage, pipeline_epoch, ¶ms) + .unwrap(), + Some(ValidatorState::Consensus) + ); + assert_eq!( + validator_state_handle(&val2.address) + .get(&shell.wl_storage, pipeline_epoch, ¶ms) + .unwrap(), + Some(ValidatorState::Jailed) + ); + assert_eq!( + get_num_consensus_validators(&shell.wl_storage, pipeline_epoch) + .unwrap(), + 6_u64 + ); + + Ok(()) + } + + /// NOTE: must call `get_default_true_votes` before every call to + /// `next_block_for_inflation` + #[test] + fn test_multiple_misbehaviors() -> storage_api::Result<()> { + for num_validators in 4u64..10u64 { + println!("NUM VALIDATORS = {}", num_validators); + test_multiple_misbehaviors_by_num_vals(num_validators)?; + } + Ok(()) + } + + /// Current test procedure (prefixed by epoch in which the event occurs): + /// 0) Validator initial stake of 200_000 + /// 1) Delegate 67_231 to validator + /// 1) Self-unbond 154_654 + /// 2) Unbond delegation of 18_000 + /// 3) Self-bond 9_123 + /// 4) Self-unbond 15_000 + /// 5) Delegate 8_144 to validator + /// 6) Discover misbehavior in epoch 3 + /// 7) Discover misbehavior in epoch 3 + /// 7) Discover misbehavior in epoch 4 + fn test_multiple_misbehaviors_by_num_vals( + num_validators: u64, + ) -> storage_api::Result<()> { + // Setup the network with pipeline_len = 2, unbonding_len = 4 + // let num_validators = 8_u64; + let (mut shell, _) = setup(num_validators); + let mut params = read_pos_params(&shell.wl_storage).unwrap(); + params.unbonding_len = 4; + params.max_validator_slots = 4; + write_pos_params(&mut shell.wl_storage, params.clone())?; + + // Slash pool balance + let nam_address = shell.wl_storage.storage.native_token.clone(); + let slash_balance_key = token::balance_key( + &nam_address, + &namada_proof_of_stake::SLASH_POOL_ADDRESS, + ); + let slash_pool_balance_init: token::Amount = shell + .wl_storage + .read(&slash_balance_key) + .expect("must be able to read") + .unwrap_or_default(); + debug_assert_eq!(slash_pool_balance_init, token::Amount::default()); + + let consensus_set: Vec = + read_consensus_validator_set_addresses_with_stake( + &shell.wl_storage, + Epoch::default(), + ) + .unwrap() + .into_iter() + .collect(); + + let val1 = consensus_set[0].clone(); + let pkh1 = get_pkh_from_address( + &shell.wl_storage, + ¶ms, + val1.address.clone(), + Epoch::default(), + ); + + let initial_stake = val1.bonded_stake; + let total_initial_stake = num_validators * initial_stake; + + // Finalize block 1 + next_block_for_inflation(&mut shell, pkh1.clone(), vec![], None); + + let votes = get_default_true_votes(&shell.wl_storage, Epoch::default()); + assert!(!votes.is_empty()); + + // Advance to epoch 1 and + // 1. Delegate 67231 NAM to validator + // 2. Validator self-unbond 154654 NAM + let current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None); + assert_eq!(shell.wl_storage.storage.block.epoch.0, 1_u64); + + // Make an account with balance and delegate some tokens + let delegator = address::testing::gen_implicit_address(); + let del_1_amount = token::Amount::whole(67_231); + let staking_token = shell.wl_storage.storage.native_token.clone(); + credit_tokens( + &mut shell.wl_storage, + &staking_token, + &delegator, + token::Amount::whole(200_000), + ) + .unwrap(); + namada_proof_of_stake::bond_tokens( + &mut shell.wl_storage, + Some(&delegator), + &val1.address, + del_1_amount, + current_epoch, + ) + .unwrap(); + + // Self-unbond + let self_unbond_1_amount = token::Amount::whole(154_654); + namada_proof_of_stake::unbond_tokens( + &mut shell.wl_storage, + None, + &val1.address, + self_unbond_1_amount, + current_epoch, + ) + .unwrap(); + + let val_stake = namada_proof_of_stake::read_validator_stake( + &shell.wl_storage, + ¶ms, + &val1.address, + current_epoch + params.pipeline_len, + ) + .unwrap() + .unwrap_or_default(); + + let total_stake = namada_proof_of_stake::read_total_stake( + &shell.wl_storage, + ¶ms, + current_epoch + params.pipeline_len, + ) + .unwrap(); + + assert_eq!( + val_stake, + initial_stake + del_1_amount - self_unbond_1_amount + ); + assert_eq!( + total_stake, + total_initial_stake + del_1_amount - self_unbond_1_amount + ); + + // Advance to epoch 2 and + // 1. Unbond 18000 NAM from delegation + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + let current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None); + println!("\nUnbonding in epoch 2"); + let del_unbond_1_amount = token::Amount::whole(18_000); + namada_proof_of_stake::unbond_tokens( + &mut shell.wl_storage, + Some(&delegator), + &val1.address, + del_unbond_1_amount, + current_epoch, + ) + .unwrap(); + + let val_stake = namada_proof_of_stake::read_validator_stake( + &shell.wl_storage, + ¶ms, + &val1.address, + current_epoch + params.pipeline_len, + ) + .unwrap() + .unwrap_or_default(); + let total_stake = namada_proof_of_stake::read_total_stake( + &shell.wl_storage, + ¶ms, + current_epoch + params.pipeline_len, + ) + .unwrap(); + assert_eq!( + val_stake, + initial_stake + del_1_amount + - self_unbond_1_amount + - del_unbond_1_amount + ); + assert_eq!( + total_stake, + total_initial_stake + del_1_amount + - self_unbond_1_amount + - del_unbond_1_amount + ); + + // Advance to epoch 3 and + // 1. Validator self-bond 9123 NAM + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + let current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None); + println!("\nBonding in epoch 3"); + + let self_bond_1_amount = token::Amount::whole(9_123); + namada_proof_of_stake::bond_tokens( + &mut shell.wl_storage, + None, + &val1.address, + self_bond_1_amount, + current_epoch, + ) + .unwrap(); + + // Advance to epoch 4 + // 1. Validator self-unbond 15000 NAM + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + let current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None); + assert_eq!(current_epoch.0, 4_u64); + + let self_unbond_2_amount = token::Amount::whole(15_000); + namada_proof_of_stake::unbond_tokens( + &mut shell.wl_storage, + None, + &val1.address, + self_unbond_2_amount, + current_epoch, + ) + .unwrap(); + + // Advance to epoch 5 and + // Delegate 8144 NAM to validator + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + let current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None); + assert_eq!(current_epoch.0, 5_u64); + println!("Delegating in epoch 5"); + + // Delegate + let del_2_amount = token::Amount::whole(8_144); + namada_proof_of_stake::bond_tokens( + &mut shell.wl_storage, + Some(&delegator), + &val1.address, + del_2_amount, + current_epoch, + ) + .unwrap(); + + println!("Advancing to epoch 6"); + + // Advance to epoch 6 + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + let current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None); + assert_eq!(current_epoch.0, 6_u64); + + // Discover a misbehavior committed in epoch 3 + // NOTE: Only the type, height, and validator address fields from the + // Misbehavior struct are used in Namada + let misbehavior_epoch = Epoch(3_u64); + let height = shell + .wl_storage + .storage + .block + .pred_epochs + .first_block_heights[misbehavior_epoch.0 as usize]; + let misbehaviors = vec![Misbehavior { + r#type: 1, + validator: Some(Validator { + address: pkh1.clone(), + power: Default::default(), + }), + height: height.0 as i64, + time: Default::default(), + total_voting_power: Default::default(), + }]; + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + next_block_for_inflation( + &mut shell, + pkh1.clone(), + votes.clone(), + Some(misbehaviors), + ); + + // Assertions + assert_eq!(current_epoch.0, 6_u64); + let processing_epoch = misbehavior_epoch + + params.unbonding_len + + 1_u64 + + params.cubic_slashing_window_length; + let enqueued_slash = enqueued_slashes_handle() + .at(&processing_epoch) + .at(&val1.address) + .front(&shell.wl_storage) + .unwrap() + .unwrap(); + assert_eq!(enqueued_slash.epoch, misbehavior_epoch); + assert_eq!(enqueued_slash.r#type, SlashType::DuplicateVote); + assert_eq!(enqueued_slash.rate, Decimal::ZERO); + let last_slash = + namada_proof_of_stake::read_validator_last_slash_epoch( + &shell.wl_storage, + &val1.address, + ) + .unwrap(); + assert_eq!(last_slash, Some(misbehavior_epoch)); + assert!( + namada_proof_of_stake::validator_slashes_handle(&val1.address) + .is_empty(&shell.wl_storage) + .unwrap() + ); + + println!("Advancing to epoch 7"); + + // Advance to epoch 7 + let current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None); + + // Discover two more misbehaviors, one committed in epoch 3, one in + // epoch 4 + let height4 = shell + .wl_storage + .storage + .block + .pred_epochs + .first_block_heights[4]; + let misbehaviors = vec![ + Misbehavior { + r#type: 1, + validator: Some(Validator { + address: pkh1.clone(), + power: Default::default(), + }), + height: height.0 as i64, + time: Default::default(), + total_voting_power: Default::default(), + }, + Misbehavior { + r#type: 2, + validator: Some(Validator { + address: pkh1.clone(), + power: Default::default(), + }), + height: height4.0 as i64, + time: Default::default(), + total_voting_power: Default::default(), + }, + ]; + next_block_for_inflation( + &mut shell, + pkh1.clone(), + votes.clone(), + Some(misbehaviors), + ); + assert_eq!(current_epoch.0, 7_u64); + let enqueued_slashes_8 = enqueued_slashes_handle() + .at(&processing_epoch) + .at(&val1.address); + let enqueued_slashes_9 = enqueued_slashes_handle() + .at(&processing_epoch.next()) + .at(&val1.address); + + assert_eq!(enqueued_slashes_8.len(&shell.wl_storage).unwrap(), 2_u64); + assert_eq!(enqueued_slashes_9.len(&shell.wl_storage).unwrap(), 1_u64); + let last_slash = + namada_proof_of_stake::read_validator_last_slash_epoch( + &shell.wl_storage, + &val1.address, + ) + .unwrap(); + assert_eq!(last_slash, Some(Epoch(4))); + assert!( + namada_proof_of_stake::is_validator_frozen( + &shell.wl_storage, + &val1.address, + current_epoch, + ¶ms + ) + .unwrap() + ); + assert!( + namada_proof_of_stake::validator_slashes_handle(&val1.address) + .is_empty(&shell.wl_storage) + .unwrap() + ); + + let pre_stake_10 = namada_proof_of_stake::read_validator_stake( + &shell.wl_storage, + ¶ms, + &val1.address, + Epoch(10), + ) + .unwrap() + .unwrap_or_default(); + assert_eq!( + pre_stake_10, + initial_stake + del_1_amount + - self_unbond_1_amount + - del_unbond_1_amount + + self_bond_1_amount + - self_unbond_2_amount + + del_2_amount + ); + + println!("\nNow processing the infractions\n"); + + // Advance to epoch 9, where the infractions committed in epoch 3 will + // be processed + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + let _ = advance_epoch(&mut shell, &pkh1, &votes, None); + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + let current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None); + assert_eq!(current_epoch.0, 9_u64); + + let val_stake_3 = namada_proof_of_stake::read_validator_stake( + &shell.wl_storage, + ¶ms, + &val1.address, + Epoch(3), + ) + .unwrap() + .unwrap_or_default(); + let val_stake_4 = namada_proof_of_stake::read_validator_stake( + &shell.wl_storage, + ¶ms, + &val1.address, + Epoch(4), + ) + .unwrap() + .unwrap_or_default(); + + let tot_stake_3 = namada_proof_of_stake::read_total_stake( + &shell.wl_storage, + ¶ms, + Epoch(3), + ) + .unwrap(); + let tot_stake_4 = namada_proof_of_stake::read_total_stake( + &shell.wl_storage, + ¶ms, + Epoch(4), + ) + .unwrap(); + + let vp_frac_3 = Decimal::from(val_stake_3) / Decimal::from(tot_stake_3); + let vp_frac_4 = Decimal::from(val_stake_4) / Decimal::from(tot_stake_4); + let tot_frac = dec!(2) * vp_frac_3 + vp_frac_4; + let cubic_rate = + std::cmp::min(Decimal::ONE, dec!(9) * tot_frac * tot_frac); + dbg!(&cubic_rate); + + let equal_enough = |rate1: Decimal, rate2: Decimal| -> bool { + let tolerance = dec!(0.000000001); + (rate1 - rate2).abs() < tolerance + }; + + // There should be 2 slashes processed for the validator, each with rate + // equal to the cubic slashing rate + let val_slashes = + namada_proof_of_stake::validator_slashes_handle(&val1.address); + assert_eq!(val_slashes.len(&shell.wl_storage).unwrap(), 2u64); + let is_rate_good = val_slashes + .iter(&shell.wl_storage) + .unwrap() + .all(|s| equal_enough(s.unwrap().rate, cubic_rate)); + assert!(is_rate_good); + + // Check the amount of stake deducted from the futuremost epoch while + // processing the slashes + let post_stake_10 = namada_proof_of_stake::read_validator_stake( + &shell.wl_storage, + ¶ms, + &val1.address, + Epoch(10), + ) + .unwrap() + .unwrap_or_default(); + // The amount unbonded after the infraction that affected the deltas + // before processing is `del_unbond_1_amount + self_bond_1_amount - + // self_unbond_2_amount` (since this self-bond was enacted then unbonded + // all after the infraction). Thus, the additional deltas to be + // deducted is the (infraction stake - this) * rate + let slash_rate_3 = std::cmp::min(Decimal::ONE, dec!(2) * cubic_rate); + let exp_slashed_during_processing_9 = decimal_mult_amount( + slash_rate_3, + initial_stake + del_1_amount + - self_unbond_1_amount + - del_unbond_1_amount + + self_bond_1_amount + - self_unbond_2_amount, + ); + assert_eq!( + pre_stake_10 - post_stake_10, + exp_slashed_during_processing_9 + ); + + // Check that we can compute the stake at the pipeline epoch + // NOTE: may be off. by 1 namnam due to rounding; + let exp_pipeline_stake = decimal_mult_amount( + Decimal::ONE - slash_rate_3, + initial_stake + del_1_amount + - self_unbond_1_amount + - del_unbond_1_amount + + self_bond_1_amount + - self_unbond_2_amount, + ) + del_2_amount; + assert!( + (exp_pipeline_stake.change() - post_stake_10.change()).abs() <= 1 + ); + + // Check the balance of the Slash Pool + // TODO: finish once implemented + // let slash_pool_balance: token::Amount = shell + // .wl_storage + // .read(&slash_balance_key) + // .expect("must be able to read") + // .unwrap_or_default(); + // let exp_slashed_3 = decimal_mult_amount( + // std::cmp::min(Decimal::TWO * cubic_rate, Decimal::ONE), + // val_stake_3 - del_unbond_1_amount + self_bond_1_amount + // - self_unbond_2_amount, + // ); + // assert_eq!(slash_pool_balance, exp_slashed_3); + + let _pre_stake_11 = namada_proof_of_stake::read_validator_stake( + &shell.wl_storage, + ¶ms, + &val1.address, + Epoch(10), + ) + .unwrap() + .unwrap_or_default(); + + // Advance to epoch 10, where the infraction committed in epoch 4 will + // be processed + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + let current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None); + assert_eq!(current_epoch.0, 10_u64); + + // Check the balance of the Slash Pool + // TODO: finish once implemented + // let slash_pool_balance: token::Amount = shell + // .wl_storage + // .read(&slash_balance_key) + // .expect("must be able to read") + // .unwrap_or_default(); + + // let exp_slashed_4 = if dec!(2) * cubic_rate >= Decimal::ONE { + // token::Amount::default() + // } else if dec!(3) * cubic_rate >= Decimal::ONE { + // decimal_mult_amount( + // Decimal::ONE - dec!(2) * cubic_rate, + // val_stake_4 + self_bond_1_amount - self_unbond_2_amount, + // ) + // } else { + // decimal_mult_amount( + // std::cmp::min(cubic_rate, Decimal::ONE), + // val_stake_4 + self_bond_1_amount - self_unbond_2_amount, + // ) + // }; + // dbg!(slash_pool_balance, exp_slashed_3 + exp_slashed_4); + // assert!( + // (slash_pool_balance.change() + // - (exp_slashed_3 + exp_slashed_4).change()) + // .abs() + // <= 1 + // ); + + let val_stake = read_validator_stake( + &shell.wl_storage, + ¶ms, + &val1.address, + current_epoch + params.pipeline_len, + )? + .unwrap_or_default(); + + let post_stake_11 = namada_proof_of_stake::read_validator_stake( + &shell.wl_storage, + ¶ms, + &val1.address, + Epoch(10), + ) + .unwrap() + .unwrap_or_default(); + + assert_eq!(post_stake_11, val_stake); + // dbg!(&val_stake); + // dbg!(pre_stake_10 - post_stake_10); + + // dbg!(&exp_slashed_during_processing_9); + // TODO: finish once implemented + // assert!( + // ((pre_stake_11 - post_stake_11).change() - + // exp_slashed_4.change()) .abs() + // <= 1 + // ); + + // dbg!(&val_stake, &exp_stake); + // dbg!(exp_slashed_during_processing_8 + + // exp_slashed_during_processing_9); dbg!( + // val_stake_3 + // - (exp_slashed_during_processing_8 + + // exp_slashed_during_processing_9) + // ); + + // let exp_stake = val_stake_3 - del_unbond_1_amount + + // self_bond_1_amount + // - self_unbond_2_amount + // + del_2_amount + // - exp_slashed_3 + // - exp_slashed_4; + + // assert!((exp_stake.change() - post_stake_11.change()).abs() <= 1); + + for _ in 0..2 { + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + let _ = advance_epoch(&mut shell, &pkh1, &votes, None); + } + let current_epoch = shell.wl_storage.storage.block.epoch; + assert_eq!(current_epoch.0, 12_u64); + + println!("\nCHECK BOND AND UNBOND DETAILS"); + let details = namada_proof_of_stake::bonds_and_unbonds( + &shell.wl_storage, + None, + None, + ) + .unwrap(); + + let del_id = BondId { + source: delegator.clone(), + validator: val1.address.clone(), + }; + let self_id = BondId { + source: val1.address.clone(), + validator: val1.address.clone(), + }; + + let del_details = details.get(&del_id).unwrap(); + let self_details = details.get(&self_id).unwrap(); + // dbg!(del_details, self_details); + + // Check slashes + assert_eq!(del_details.slashes, self_details.slashes); + assert_eq!(del_details.slashes.len(), 3); + assert_eq!(del_details.slashes[0].epoch, Epoch(3)); + assert!(equal_enough(del_details.slashes[0].rate, cubic_rate)); + assert_eq!(del_details.slashes[1].epoch, Epoch(3)); + assert!(equal_enough(del_details.slashes[1].rate, cubic_rate)); + assert_eq!(del_details.slashes[2].epoch, Epoch(4)); + assert!(equal_enough(del_details.slashes[2].rate, cubic_rate)); + + // Check delegations + assert_eq!(del_details.bonds.len(), 2); + assert_eq!(del_details.bonds[0].start, Epoch(3)); + assert_eq!( + del_details.bonds[0].amount, + del_1_amount - del_unbond_1_amount + ); + // TODO: decimal mult issues should be resolved with PR 1282 + assert!( + (del_details.bonds[0].slashed_amount.unwrap().change() + - decimal_mult_amount( + std::cmp::min(Decimal::ONE, dec!(3) * cubic_rate), + del_1_amount - del_unbond_1_amount + ) + .change()) + .abs() + <= 2 + ); + assert_eq!(del_details.bonds[1].start, Epoch(7)); + assert_eq!(del_details.bonds[1].amount, del_2_amount); + assert_eq!(del_details.bonds[1].slashed_amount, None); + + // Check self-bonds + assert_eq!(self_details.bonds.len(), 1); + assert_eq!(self_details.bonds[0].start, Epoch(0)); + assert_eq!( + self_details.bonds[0].amount, + initial_stake - self_unbond_1_amount + self_bond_1_amount + - self_unbond_2_amount + ); + // TODO: not sure why this is correct??? (with + self_bond_1_amount - + // self_unbond_2_amount) + // TODO: Make sure this is sound and what we expect + assert_eq!( + self_details.bonds[0].slashed_amount, + Some(decimal_mult_amount( + std::cmp::min(Decimal::ONE, dec!(3) * cubic_rate), + initial_stake - self_unbond_1_amount + self_bond_1_amount + - self_unbond_2_amount + )) + ); + + // Check delegation unbonds + assert_eq!(del_details.unbonds.len(), 1); + assert_eq!(del_details.unbonds[0].start, Epoch(3)); + assert_eq!(del_details.unbonds[0].withdraw, Epoch(9)); + assert_eq!(del_details.unbonds[0].amount, del_unbond_1_amount); + assert!( + (del_details.unbonds[0].slashed_amount.unwrap().change() + - decimal_mult_amount( + std::cmp::min(Decimal::ONE, dec!(2) * cubic_rate), + del_unbond_1_amount + ) + .change()) + .abs() + <= 1 + ); + + // Check self-unbonds + assert_eq!(self_details.unbonds.len(), 3); + assert_eq!(self_details.unbonds[0].start, Epoch(0)); + assert_eq!(self_details.unbonds[0].withdraw, Epoch(8)); + assert_eq!(self_details.unbonds[1].start, Epoch(0)); + assert_eq!(self_details.unbonds[1].withdraw, Epoch(11)); + assert_eq!(self_details.unbonds[2].start, Epoch(5)); + assert_eq!(self_details.unbonds[2].withdraw, Epoch(11)); + assert_eq!(self_details.unbonds[0].amount, self_unbond_1_amount); + assert_eq!(self_details.unbonds[0].slashed_amount, None); + assert_eq!( + self_details.unbonds[1].amount, + self_unbond_2_amount - self_bond_1_amount + ); + assert_eq!( + self_details.unbonds[1].slashed_amount, + Some(decimal_mult_amount( + std::cmp::min(Decimal::ONE, dec!(3) * cubic_rate), + self_unbond_2_amount - self_bond_1_amount + )) + ); + assert_eq!(self_details.unbonds[2].amount, self_bond_1_amount); + assert_eq!(self_details.unbonds[2].slashed_amount, None); + + println!("\nWITHDRAWING DELEGATION UNBOND"); + // let slash_pool_balance_pre_withdraw = slash_pool_balance; + // Withdraw the delegation unbonds, which total to 18_000. This should + // only be affected by the slashes in epoch 3 + let del_withdraw = namada_proof_of_stake::withdraw_tokens( + &mut shell.wl_storage, + Some(&delegator), + &val1.address, + current_epoch, + ) + .unwrap(); + + let exp_del_withdraw_slashed_amount = + decimal_mult_amount(slash_rate_3, del_unbond_1_amount); + assert_eq!( + del_withdraw, + del_unbond_1_amount - exp_del_withdraw_slashed_amount + ); + + // TODO: finish once implemented + // Check the balance of the Slash Pool + // let slash_pool_balance: token::Amount = shell + // .wl_storage + // .read(&slash_balance_key) + // .expect("must be able to read") + // .unwrap_or_default(); + // dbg!(del_withdraw, slash_pool_balance); + // assert_eq!( + // slash_pool_balance - slash_pool_balance_pre_withdraw, + // exp_del_withdraw_slashed_amount + // ); + + // println!("\nWITHDRAWING SELF UNBOND"); + // Withdraw the self unbonds, which total 154_654 + 15_000 - 9_123. Only + // the (15_000 - 9_123) tokens are slashable. + // let self_withdraw = namada_proof_of_stake::withdraw_tokens( + // &mut shell.wl_storage, + // None, + // &val1.address, + // current_epoch, + // ) + // .unwrap(); + + // let exp_self_withdraw_slashed_amount = decimal_mult_amount( + // std::cmp::min(dec!(3) * cubic_rate, Decimal::ONE), + // self_unbond_2_amount - self_bond_1_amount, + // ); + // Check the balance of the Slash Pool + // let slash_pool_balance: token::Amount = shell + // .wl_storage + // .read(&slash_balance_key) + // .expect("must be able to read") + // .unwrap_or_default(); + + // dbg!(self_withdraw, slash_pool_balance); + // dbg!( + // decimal_mult_amount(dec!(2) * cubic_rate, val_stake_3) + // + decimal_mult_amount(cubic_rate, val_stake_4) + // ); + + // assert_eq!( + // exp_self_withdraw_slashed_amount, + // slash_pool_balance + // - slash_pool_balance_pre_withdraw + // - exp_del_withdraw_slashed_amount + // ); + + Ok(()) + } + + fn get_default_true_votes(storage: &S, epoch: Epoch) -> Vec + where + S: StorageRead, + { + let params = read_pos_params(storage).unwrap(); + read_consensus_validator_set_addresses_with_stake(storage, epoch) + .unwrap() + .into_iter() + .map(|val| { + let pkh = get_pkh_from_address( + storage, + ¶ms, + val.address.clone(), + epoch, + ); + VoteInfo { + validator: Some(Validator { + address: pkh, + power: u64::from(val.bonded_stake) as i64, + }), + signed_last_block: true, + } + }) + .collect::>() + } + + fn advance_epoch( + shell: &mut TestShell, + proposer_address: &[u8], + consensus_votes: &[VoteInfo], + misbehaviors: Option>, + ) -> Epoch { + let current_epoch = shell.wl_storage.storage.block.epoch; + loop { + next_block_for_inflation( + shell, + proposer_address.to_owned(), + consensus_votes.to_owned(), + misbehaviors.clone(), + ); + if shell.wl_storage.storage.block.epoch == current_epoch.next() { + break; + } + } + shell.wl_storage.storage.block.epoch + } + + fn get_pkh_from_address( + storage: &S, + params: &PosParams, + address: Address, + epoch: Epoch, + ) -> Vec + where + S: StorageRead, + { + let ck = validator_consensus_key_handle(&address) + .get(storage, epoch, params) + .unwrap() + .unwrap(); + let hash_string = tm_consensus_key_raw_hash(&ck); + HEXUPPER.decode(hash_string.as_bytes()).unwrap() + } } diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index dfdae4d04e..9ff5ebc279 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -12,6 +12,7 @@ use namada::ledger::storage::types::encode; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::ledger::storage_api::{token, StorageWrite}; use namada::proof_of_stake::read_total_stake; +use namada::proto::{Code, Data}; use namada::types::address::Address; use namada::types::governance::{Council, Tally, TallyResult, VotePower}; use namada::types::storage::Epoch; @@ -147,17 +148,13 @@ where 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)), - shell.chain_id.clone(), - None, - ); - let tx_type = TxType::Decrypted(DecryptedTx::Decrypted { - tx, + let mut tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] has_valid_pow: false, - }); + })); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_data(Data::new(encode(&id))); + tx.set_code(Code::new(proposal_code)); let pending_execution_key = gov_storage::get_proposal_execution_key(id); shell @@ -165,7 +162,7 @@ where .write(&pending_execution_key, ()) .expect("Should be able to write to storage."); let tx_result = protocol::apply_tx( - tx_type, + tx, 0, /* this is used to compute the fee * based on the code size. We dont * need it here. */ diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index acf2e103a0..6b9dec5a76 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -22,7 +22,6 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use borsh::{BorshDeserialize, BorshSerialize}; -use namada::core::types::hash::Hash; use namada::ledger::events::log::EventLog; use namada::ledger::events::Event; use namada::ledger::gas::BlockGasMeter; @@ -35,11 +34,11 @@ use namada::ledger::storage::{ }; use namada::ledger::storage_api::{self, StorageRead, StorageWrite}; use namada::ledger::{ibc, pos, protocol, replay_protection}; -use namada::proof_of_stake::{self, read_pos_params, slash}; -use namada::proto::{self, Tx}; +use namada::proof_of_stake::{self, process_slashes, read_pos_params, slash}; +use namada::proto::{self, Section, Tx}; use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::chain::ChainId; -use namada::types::internal::WrapperTxInQueue; +use namada::types::internal::TxInQueue; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; @@ -47,8 +46,8 @@ use namada::types::token::{self}; #[cfg(not(feature = "mainnet"))] use namada::types::transaction::MIN_FEE; use namada::types::transaction::{ - hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, - EllipticCurve, PairingEngine, TxType, WrapperTx, + hash_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, + EllipticCurve, PairingEngine, TxType, }; use namada::types::{address, hash}; use namada::vm::wasm::{TxCache, VpCache}; @@ -414,7 +413,7 @@ where /// Iterate over the wrapper txs in order #[allow(dead_code)] - fn iter_tx_queue(&mut self) -> impl Iterator { + fn iter_tx_queue(&mut self) -> impl Iterator { self.wl_storage.storage.tx_queue.iter() } @@ -494,7 +493,7 @@ where } /// Apply PoS slashes from the evidence - fn slash(&mut self) { + fn record_slashes_from_evidence(&mut self) { if !self.byzantine_validators.is_empty() { let byzantine_validators = mem::take(&mut self.byzantine_validators); @@ -502,6 +501,7 @@ where let pos_params = read_pos_params(&self.wl_storage).unwrap(); let current_epoch = self.wl_storage.storage.block.epoch; for evidence in byzantine_validators { + // dbg!(&evidence); tracing::info!("Processing evidence {evidence:?}."); let evidence_height = match u64::try_from(evidence.height) { Ok(height) => height, @@ -529,7 +529,12 @@ where continue; } }; - if evidence_epoch + pos_params.unbonding_len <= current_epoch { + // Disregard evidences that should have already been processed + // at this time + if evidence_epoch + pos_params.slash_processing_epoch_offset() + - pos_params.cubic_slashing_window_length + <= current_epoch + { tracing::info!( "Skipping outdated evidence from epoch \ {evidence_epoch}" @@ -609,6 +614,19 @@ where } } + /// Process and apply slashes that have already been recorded for the + /// current epoch + fn process_slashes(&mut self) { + let current_epoch = self.wl_storage.storage.block.epoch; + if let Err(err) = process_slashes(&mut self.wl_storage, current_epoch) { + tracing::error!( + "Error while processing slashes queued for epoch {}: {}", + current_epoch, + err + ); + } + } + /// INVARIANT: This method must be stateless. #[cfg(feature = "abcipp")] pub fn extend_vote( @@ -656,19 +674,20 @@ where /// block construction and validation pub fn replay_protection_checks( &self, - wrapper: &WrapperTx, + wrapper: &Tx, tx_bytes: &[u8], temp_wl_storage: &mut TempWlStorage, ) -> Result<()> { - let inner_hash_key = - replay_protection::get_tx_hash_key(&wrapper.tx_hash); + let inner_tx_hash = + wrapper.clone().update_header(TxType::Raw).header_hash(); + let inner_hash_key = replay_protection::get_tx_hash_key(&inner_tx_hash); if temp_wl_storage .has_key(&inner_hash_key) .expect("Error while checking inner tx hash key in storage") { return Err(Error::ReplayAttempt(format!( "Inner transaction hash {} already in storage", - &wrapper.tx_hash + &inner_tx_hash, ))); } @@ -679,7 +698,7 @@ where let tx = Tx::try_from(tx_bytes).expect("Deserialization shouldn't fail"); - let wrapper_hash = Hash(tx.unsigned_hash()); + let wrapper_hash = tx.header_hash(); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); if temp_wl_storage @@ -728,17 +747,17 @@ where }; // Tx chain id - if tx.chain_id != self.chain_id { + if tx.header.chain_id != self.chain_id { response.code = ErrorCodes::InvalidChainId.into(); response.log = format!( "Tx carries a wrong chain id: expected {}, found {}", - self.chain_id, tx.chain_id + self.chain_id, tx.header.chain_id ); return response; } // Tx expiration - if let Some(exp) = tx.expiration { + if let Some(exp) = tx.header.expiration { let last_block_timestamp = self.get_block_timestamp(None); if last_block_timestamp > exp { @@ -752,8 +771,8 @@ where } // Tx signature check - let tx_type = match process_tx(tx) { - Ok(ty) => ty, + let tx_type = match tx.validate_header() { + Ok(()) => tx.header(), Err(msg) => { response.code = ErrorCodes::InvalidSig.into(); response.log = msg.to_string(); @@ -762,10 +781,13 @@ where }; // Tx type check - if let TxType::Wrapper(wrapper) = tx_type { + if let TxType::Wrapper(wrapper) = tx_type.tx_type { // Replay protection check + let mut inner_tx = tx; + inner_tx.update_header(TxType::Raw); + let inner_tx_hash = &inner_tx.header_hash(); let inner_hash_key = - replay_protection::get_tx_hash_key(&wrapper.tx_hash); + replay_protection::get_tx_hash_key(inner_tx_hash); if self .wl_storage .storage @@ -777,14 +799,14 @@ where response.log = format!( "Inner transaction hash {} already in storage, replay \ attempt", - wrapper.tx_hash + inner_tx_hash ); return response; } let tx = Tx::try_from(tx_bytes).expect("Deserialization shouldn't fail"); - let wrapper_hash = hash::Hash(tx.unsigned_hash()); + let wrapper_hash = hash::Hash(tx.header_hash().0); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); if self @@ -848,13 +870,6 @@ where let mut tx_wasm_cache = self.tx_wasm_cache.read_only(); match Tx::try_from(tx_bytes) { Ok(tx) => { - let tx = TxType::Decrypted(DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - // To be able to dry-run testnet faucet withdrawal, pretend - // that we got a valid PoW - has_valid_pow: true, - }); match protocol::apply_tx( tx, tx_bytes.len(), @@ -985,6 +1000,7 @@ mod test_utils { use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::{update_allowed_conversions, Sha256Hasher}; + use namada::proto::{Code, Data}; use namada::types::chain::ChainId; use namada::types::hash::Hash; use namada::types::key::*; @@ -1141,16 +1157,12 @@ mod test_utils { /// Add a wrapper tx to the queue of txs to be decrypted /// in the current block proposal #[cfg(test)] - pub fn enqueue_tx(&mut self, wrapper: WrapperTx) { - self.shell - .wl_storage - .storage - .tx_queue - .push(WrapperTxInQueue { - tx: wrapper, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - }); + pub fn enqueue_tx(&mut self, tx: Tx) { + self.shell.wl_storage.storage.tx_queue.push(TxInQueue { + tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + }); } } @@ -1224,13 +1236,7 @@ mod test_utils { .expect("begin_block failed"); let keypair = gen_keypair(); // enqueue a wrapper tx - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: native_token, @@ -1238,12 +1244,15 @@ mod test_utils { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - shell.wl_storage.storage.tx_queue.push(WrapperTxInQueue { + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.encrypt(&Default::default()); + + shell.wl_storage.storage.tx_queue.push(TxInQueue { tx: wrapper, #[cfg(not(feature = "mainnet"))] has_valid_pow: false, @@ -1281,7 +1290,7 @@ mod test_utils { #[cfg(test)] mod test_mempool_validate { use namada::proof_of_stake::Epoch; - use namada::proto::SignedTxData; + use namada::proto::{Code, Data, Section, Signature, Tx}; use namada::types::transaction::{Fee, WrapperTx}; use super::test_utils::TestShell; @@ -1294,41 +1303,23 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - - let mut wrapper = WrapperTx::new( - Fee { - amount: 100.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - &keypair, - Epoch(0), - 0.into(), - tx, - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ) - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Wrapper signing failed"); - - let unsigned_wrapper = if let Some(Ok(SignedTxData { - data: Some(data), - sig: _, - })) = wrapper - .data - .take() - .map(|data| SignedTxData::try_from_slice(&data[..])) - { - Tx::new(vec![], Some(data), shell.chain_id.clone(), None) - } else { - panic!("Test failed") - }; + let mut unsigned_wrapper = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + unsigned_wrapper.header.chain_id = shell.chain_id.clone(); + unsigned_wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + unsigned_wrapper + .set_data(Data::new("transaction data".as_bytes().to_owned())); + unsigned_wrapper.encrypt(&Default::default()); let mut result = shell.mempool_validate( unsigned_wrapper.to_bytes().as_ref(), @@ -1349,67 +1340,33 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - - let mut wrapper = WrapperTx::new( - Fee { - amount: 100.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, + let mut invalid_wrapper = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + invalid_wrapper.header.chain_id = shell.chain_id.clone(); + invalid_wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + invalid_wrapper + .set_data(Data::new("transaction data".as_bytes().to_owned())); + invalid_wrapper.add_section(Section::Signature(Signature::new( + &invalid_wrapper.header_hash(), &keypair, - Epoch(0), - 0.into(), - tx, - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ) - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Wrapper signing failed"); - - let invalid_wrapper = if let Some(Ok(SignedTxData { - data: Some(data), - sig, - })) = wrapper - .data - .take() - .map(|data| SignedTxData::try_from_slice(&data[..])) - { - let mut new_wrapper = if let TxType::Wrapper(wrapper) = - ::deserialize(&mut data.as_ref()) - .expect("Test failed") - { - wrapper - } else { - panic!("Test failed") - }; + ))); + invalid_wrapper.encrypt(&Default::default()); - // we mount a malleability attack to try and remove the fee - new_wrapper.fee.amount = 0.into(); - let new_data = TxType::Wrapper(new_wrapper) - .try_to_vec() - .expect("Test failed"); - Tx::new( - vec![], - Some( - SignedTxData { - sig, - data: Some(new_data), - } - .try_to_vec() - .expect("Test failed"), - ), - shell.chain_id.clone(), - None, - ) - } else { - panic!("Test failed"); - }; + // we mount a malleability attack to try and remove the fee + let mut new_wrapper = + invalid_wrapper.header().wrapper().expect("Test failed"); + new_wrapper.fee.amount = 0.into(); + invalid_wrapper.update_header(TxType::Wrapper(Box::new(new_wrapper))); let mut result = shell.mempool_validate( invalid_wrapper.to_bytes().as_ref(), @@ -1429,12 +1386,9 @@ mod test_mempool_validate { let (shell, _) = TestShell::new(); // Test Raw TxType - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - None, - shell.chain_id.clone(), - None, - ); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); let result = shell.mempool_validate( tx.to_bytes().as_ref(), @@ -1452,14 +1406,7 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 100.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1467,27 +1414,26 @@ mod test_mempool_validate { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ) - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Wrapper signing failed"); - - let tx_type = match process_tx(wrapper.clone()).expect("Test failed") { - TxType::Wrapper(t) => t, - _ => panic!("Test failed"), - }; + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); // Write wrapper hash to storage - let wrapper_hash = hash::Hash(wrapper.unsigned_hash()); + let wrapper_hash = wrapper.header_hash(); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); shell .wl_storage .storage - .write(&wrapper_hash_key, &wrapper_hash) + .write(&wrapper_hash_key, wrapper_hash) .expect("Test failed"); // Try wrapper tx replay attack @@ -1519,13 +1465,14 @@ mod test_mempool_validate { ) ); + let inner_tx_hash = + wrapper.clone().update_header(TxType::Raw).header_hash(); // Write inner hash in storage - let inner_hash_key = - replay_protection::get_tx_hash_key(&tx_type.tx_hash); + let inner_hash_key = replay_protection::get_tx_hash_key(&inner_tx_hash); shell .wl_storage .storage - .write(&inner_hash_key, &tx_type.tx_hash) + .write(&inner_hash_key, inner_tx_hash) .expect("Test failed"); // Try inner tx replay attack @@ -1538,7 +1485,7 @@ mod test_mempool_validate { result.log, format!( "Inner transaction hash {} already in storage, replay attempt", - tx_type.tx_hash + inner_tx_hash ) ); @@ -1551,7 +1498,7 @@ mod test_mempool_validate { result.log, format!( "Inner transaction hash {} already in storage, replay attempt", - tx_type.tx_hash + inner_tx_hash ) ) } @@ -1564,13 +1511,14 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); let wrong_chain_id = ChainId("Wrong chain id".to_string()); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - wrong_chain_id.clone(), - None, - ) - .sign(&keypair); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wrong_chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + tx.add_section(Section::Signature(Signature::new( + &tx.header_hash(), + &keypair, + ))); let result = shell.mempool_validate( tx.to_bytes().as_ref(), @@ -1593,13 +1541,15 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - Some(DateTimeUtc::now()), - ) - .sign(&keypair); + let mut tx = Tx::new(TxType::Raw); + tx.header.expiration = Some(DateTimeUtc::now()); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + tx.add_section(Section::Signature(Signature::new( + &tx.header_hash(), + &keypair, + ))); let result = shell.mempool_validate( tx.to_bytes().as_ref(), diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 41d05fea5d..7d126a5218 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -4,11 +4,12 @@ use namada::core::hints; use namada::ledger::storage::{DBIter, StorageHasher, TempWlStorage, DB}; use namada::proof_of_stake::pos_queries::PosQueries; use namada::proto::Tx; -use namada::types::internal::WrapperTxInQueue; +use namada::types::internal::TxInQueue; use namada::types::time::DateTimeUtc; -use namada::types::transaction::tx_types::TxType; use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; -use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; +use namada::types::transaction::{ + AffineCurve, DecryptedTx, EllipticCurve, TxType, +}; use super::super::*; #[allow(unused_imports)] @@ -21,8 +22,10 @@ use super::block_space_alloc::{AllocFailure, BlockSpaceAllocator}; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::ExtendedCommitInfo; 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::google::protobuf::Timestamp; -use crate::node::ledger::shell::{process_tx, ShellMode}; +use crate::node::ledger::shell::ShellMode; use crate::node::ledger::shims::abcipp_shim_types::shim::{response, TxBytes}; impl Shell @@ -45,7 +48,6 @@ where let txs = if let ShellMode::Validator { .. } = self.mode { // start counting allotted space for txs let alloc = self.get_encrypted_txs_allocator(); - // add encrypted txs let (encrypted_txs, alloc) = self.build_encrypted_txs( alloc, @@ -140,13 +142,11 @@ where // If tx doesn't have an expiration it is valid. If time cannot be // retrieved from block default to last block datetime which has // already been checked by mempool_validate, so it's valid - if let (Some(block_time), Some(exp)) = (block_time.as_ref(), &tx.expiration) { + if let (Some(block_time), Some(exp)) = (block_time.as_ref(), &tx.header.expiration) { if block_time > exp { return None } } - if let Ok(TxType::Wrapper(ref wrapper)) = process_tx(tx) { - if self.replay_protection_checks(wrapper, tx_bytes.as_slice(), &mut temp_wl_storage).is_ok() { - return Some(tx_bytes.clone()) - } + if tx.validate_header().is_ok() && tx.header().wrapper().is_some() && self.replay_protection_checks(&tx, tx_bytes.as_slice(), &mut temp_wl_storage).is_ok() { + return Some(tx_bytes.clone()); } } None @@ -202,7 +202,6 @@ where // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - let pos_queries = self.wl_storage.pos_queries(); let txs = self .wl_storage @@ -210,20 +209,30 @@ where .tx_queue .iter() .map( - |WrapperTxInQueue { + |TxInQueue { tx, #[cfg(not(feature = "mainnet"))] has_valid_pow, - }| { - Tx::from(match tx.decrypt(privkey) { - Ok(tx) => DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: *has_valid_pow, + }| { + let mut tx = tx.clone(); + match tx.decrypt(privkey).ok() + { + Some(()) => { + tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + has_valid_pow: *has_valid_pow, + })); + tx + }, + // An absent or undecryptable inner_tx are both + // treated as undecryptable + None => { + tx.update_header(TxType::Decrypted( + DecryptedTx::Undecryptable + )); + tx }, - _ => DecryptedTx::Undecryptable(tx.clone()), - }) - .to_bytes() + }.to_bytes() }, ) // TODO: make sure all decrypted txs are accepted @@ -280,7 +289,7 @@ mod test_prepare_proposal { use borsh::BorshSerialize; use namada::ledger::replay_protection; use namada::proof_of_stake::Epoch; - use namada::types::hash::Hash; + use namada::proto::{Code, Data, Header, Section, Signature}; use namada::types::transaction::{Fee, WrapperTx}; use super::*; @@ -292,12 +301,10 @@ mod test_prepare_proposal { #[test] fn test_prepare_proposal_rejects_non_wrapper_tx() { let (shell, _) = test_utils::setup(1); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction_data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); + let mut tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { + has_valid_pow: true, + })); + tx.header.chain_id = shell.chain_id.clone(); let req = RequestPrepareProposal { txs: vec![tx.to_bytes()], ..Default::default() @@ -312,36 +319,23 @@ mod test_prepare_proposal { fn test_error_in_processing_tx() { let (shell, _) = test_utils::setup(1); let keypair = gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction_data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); // an unsigned wrapper will cause an error in processing - let wrapper = Tx::new( - "".as_bytes().to_owned(), - Some( - WrapperTx::new( - Fee { - amount: 0.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - &keypair, - Epoch(0), - 0.into(), - tx, - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ) - .try_to_vec() - .expect("Test failed"), - ), - shell.chain_id.clone(), + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] None, - ) - .to_bytes(); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction_data".as_bytes().to_owned())); + wrapper.encrypt(&Default::default()); + let wrapper = wrapper.to_bytes(); #[allow(clippy::redundant_clone)] let req = RequestPrepareProposal { txs: vec![wrapper.clone()], @@ -367,18 +361,7 @@ mod test_prepare_proposal { // create a request with two new wrappers from mempool and // two wrappers from the previous block to be decrypted for i in 0..2 { - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some(format!("transaction data: {}", i).as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - expected_decrypted.push(Tx::from(DecryptedTx::Decrypted { - tx: tx.clone(), - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - })); - let wrapper_tx = WrapperTx::new( + let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -386,39 +369,58 @@ mod test_prepare_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let wrapper = wrapper_tx - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); - shell.enqueue_tx(wrapper_tx); - expected_wrapper.push(wrapper.clone()); - req.txs.push(wrapper.to_bytes()); + )))); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + tx.set_data(Data::new( + format!("transaction data: {}", i).as_bytes().to_owned(), + )); + tx.add_section(Section::Signature(Signature::new( + &tx.header_hash(), + &keypair, + ))); + tx.encrypt(&Default::default()); + + shell.enqueue_tx(tx.clone()); + expected_wrapper.push(tx.clone()); + req.txs.push(tx.to_bytes()); + tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + })); + expected_decrypted.push(tx.clone()); } - let expected_txs: Vec = expected_wrapper + // we extract the inner data from the txs for testing + // equality since otherwise changes in timestamps would + // fail the test + let expected_txs: Vec
= expected_wrapper .into_iter() .chain(expected_decrypted.into_iter()) - // we extract the inner data from the txs for testing - // equality since otherwise changes in timestamps would - // fail the test - .map(|tx| tx.data.expect("Test failed")) + .map(|tx| tx.header) .collect(); - let received: Vec = shell + 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") + .header }) .collect(); // check that the order of the txs is correct - assert_eq!(received, expected_txs); + assert_eq!( + received + .iter() + .map(|x| x.try_to_vec().unwrap()) + .collect::>(), + expected_txs + .iter() + .map(|x| x.try_to_vec().unwrap()) + .collect::>(), + ); } /// Test that if the unsigned wrapper tx hash is known (replay attack), the @@ -428,14 +430,7 @@ mod test_prepare_proposal { let (mut shell, _) = test_utils::setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -443,17 +438,20 @@ mod test_prepare_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let signed = wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); // Write wrapper hash to storage - let wrapper_unsigned_hash = Hash(signed.unsigned_hash()); + let wrapper_unsigned_hash = wrapper.header_hash(); let hash_key = replay_protection::get_tx_hash_key(&wrapper_unsigned_hash); shell @@ -463,7 +461,7 @@ mod test_prepare_proposal { .expect("Test failed"); let req = RequestPrepareProposal { - txs: vec![signed.to_bytes()], + txs: vec![wrapper.to_bytes()], ..Default::default() }; @@ -471,7 +469,7 @@ mod test_prepare_proposal { shell.prepare_proposal(req).txs.into_iter().map(|tx_bytes| { Tx::try_from(tx_bytes.as_slice()) .expect("Test failed") - .data + .data() .expect("Test failed") }); assert_eq!(received.len(), 0); @@ -484,14 +482,7 @@ mod test_prepare_proposal { let (shell, _) = test_utils::setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -499,23 +490,27 @@ mod test_prepare_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let signed = wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); + let req = RequestPrepareProposal { - txs: vec![signed.to_bytes(); 2], + txs: vec![wrapper.to_bytes(); 2], ..Default::default() }; let received = shell.prepare_proposal(req).txs.into_iter().map(|tx_bytes| { Tx::try_from(tx_bytes.as_slice()) .expect("Test failed") - .data + .data() .expect("Test failed") }); assert_eq!(received.len(), 1); @@ -528,14 +523,7 @@ mod test_prepare_proposal { let (mut shell, _) = test_utils::setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -543,15 +531,19 @@ mod test_prepare_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let inner_unsigned_hash = wrapper.tx_hash.clone(); - let signed = wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); + let inner_unsigned_hash = + wrapper.clone().update_header(TxType::Raw).header_hash(); // Write inner hash to storage let hash_key = replay_protection::get_tx_hash_key(&inner_unsigned_hash); @@ -562,7 +554,7 @@ mod test_prepare_proposal { .expect("Test failed"); let req = RequestPrepareProposal { - txs: vec![signed.to_bytes()], + txs: vec![wrapper.to_bytes()], ..Default::default() }; @@ -570,7 +562,7 @@ mod test_prepare_proposal { shell.prepare_proposal(req).txs.into_iter().map(|tx_bytes| { Tx::try_from(tx_bytes.as_slice()) .expect("Test failed") - .data + .data() .expect("Test failed") }); assert_eq!(received.len(), 0); @@ -584,14 +576,7 @@ mod test_prepare_proposal { let keypair = crate::wallet::defaults::daewon_keypair(); let keypair_2 = crate::wallet::defaults::daewon_keypair(); - - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -599,41 +584,51 @@ mod test_prepare_proposal { &keypair, Epoch(0), 0.into(), - tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let signed = wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + let tx_code = Code::new("wasm_code".as_bytes().to_owned()); + wrapper.set_code(tx_code.clone()); + let tx_data = Data::new("transaction data".as_bytes().to_owned()); + wrapper.set_data(tx_data.clone()); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); - let new_wrapper = WrapperTx::new( - Fee { - amount: 0.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - &keypair_2, - Epoch(0), - 0.into(), - tx, - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ); - let new_signed = new_wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + let mut new_wrapper = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair_2, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + new_wrapper.header.chain_id = shell.chain_id.clone(); + new_wrapper.header.timestamp = wrapper.header.timestamp; + new_wrapper.set_code(tx_code); + new_wrapper.set_data(tx_data); + new_wrapper.add_section(Section::Signature(Signature::new( + &new_wrapper.header_hash(), + &keypair, + ))); + new_wrapper.encrypt(&Default::default()); let req = RequestPrepareProposal { - txs: vec![signed.to_bytes(), new_signed.to_bytes()], + txs: vec![wrapper.to_bytes(), new_wrapper.to_bytes()], ..Default::default() }; let received = shell.prepare_proposal(req).txs.into_iter().map(|tx_bytes| { Tx::try_from(tx_bytes.as_slice()) .expect("Test failed") - .data + .data() .expect("Test failed") }); assert_eq!(received.len(), 1); @@ -645,28 +640,28 @@ mod test_prepare_proposal { let (shell, _) = test_utils::setup(1); let keypair = gen_keypair(); let tx_time = DateTimeUtc::now(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper_tx = WrapperTx::new( - Fee { - amount: 0.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, + let mut wrapper_tx = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + wrapper_tx.header.chain_id = shell.chain_id.clone(); + wrapper_tx.header.expiration = Some(tx_time); + wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper_tx + .set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper_tx.add_section(Section::Signature(Signature::new( + &wrapper_tx.header_hash(), &keypair, - Epoch(0), - 0.into(), - tx, - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ); - let wrapper = wrapper_tx - .sign(&keypair, shell.chain_id.clone(), Some(tx_time)) - .expect("Test failed"); + ))); + wrapper_tx.encrypt(&Default::default()); let time = DateTimeUtc::now(); let block_time = @@ -675,7 +670,7 @@ mod test_prepare_proposal { nanos: time.0.timestamp_subsec_nanos() as i32, }; let req = RequestPrepareProposal { - txs: vec![wrapper.to_bytes()], + txs: vec![wrapper_tx.to_bytes()], max_tx_bytes: 0, time: Some(block_time), ..Default::default() diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 6ad5a71048..2837ac6600 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -6,7 +6,7 @@ use namada::core::hints; use namada::core::ledger::storage::WlStorage; use namada::ledger::storage::TempWlStorage; use namada::proof_of_stake::pos_queries::PosQueries; -use namada::types::internal::WrapperTxInQueue; +use namada::types::internal::TxInQueue; use super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; @@ -193,7 +193,7 @@ where pub(crate) fn process_single_tx<'a>( &self, tx_bytes: &[u8], - tx_queue_iter: &mut impl Iterator, + tx_queue_iter: &mut impl Iterator, metadata: &mut ValidationMeta, temp_wl_storage: &mut TempWlStorage, block_time: DateTimeUtc, @@ -229,17 +229,17 @@ where }) }, |tx| { - let tx_chain_id = tx.chain_id.clone(); - let tx_expiration = tx.expiration; - let tx_type = process_tx(tx).map_err(|err| { + let tx_chain_id = tx.header.chain_id.clone(); + let tx_expiration = tx.header.expiration; + if let Err(err) = tx.validate_header() { // This occurs if the wrapper / protocol tx signature is // invalid - TxResult { + return Err(TxResult { code: ErrorCodes::InvalidSig.into(), info: err.to_string(), - } - })?; - Ok((tx_chain_id, tx_expiration, tx_type)) + }); + } + Ok((tx_chain_id, tx_expiration, tx)) }, ); let (tx_chain_id, tx_expiration, tx) = match maybe_tx { @@ -250,9 +250,15 @@ where // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - match tx { + if let Err(err) = tx.validate_header() { + return TxResult { + code: ErrorCodes::InvalidSig.into(), + info: err.to_string(), + }; + } + match tx.header().tx_type { // If it is a raw transaction, we do no further validation - TxType::Raw(_) => TxResult { + TxType::Raw => TxResult { code: ErrorCodes::InvalidTx.into(), info: "Transaction rejected: Non-encrypted transactions are \ not supported" @@ -290,11 +296,19 @@ where .into(), } } - TxType::Decrypted(tx) => { + TxType::Decrypted(tx_header) => { metadata.has_decrypted_txs = true; match tx_queue_iter.next() { Some(wrapper) => { - if wrapper.tx.tx_hash != tx.hash_commitment() { + let mut inner_tx = tx; + inner_tx.update_header(TxType::Raw); + if wrapper + .tx + .clone() + .update_header(TxType::Raw) + .header_hash() + != inner_tx.header_hash() + { TxResult { code: ErrorCodes::InvalidOrder.into(), info: "Process proposal rejected a decrypted \ @@ -302,41 +316,38 @@ where determined in the previous block" .into(), } - } else if verify_decrypted_correctly(&tx, privkey) { - if let DecryptedTx::Decrypted { - tx, - has_valid_pow: _, - } = tx - { - // Tx chain id - if tx.chain_id != self.chain_id { + } else if verify_decrypted_correctly( + &tx_header, + wrapper.tx.clone(), + privkey, + ) { + // Tx chain id + if wrapper.tx.header.chain_id != self.chain_id { + return TxResult { + code: ErrorCodes::InvalidDecryptedChainId + .into(), + info: format!( + "Decrypted tx carries a wrong chain \ + id: expected {}, found {}", + self.chain_id, + wrapper.tx.header.chain_id + ), + }; + } + + // Tx expiration + if let Some(exp) = wrapper.tx.header.expiration { + if block_time > exp { return TxResult { - code: - ErrorCodes::InvalidDecryptedChainId - .into(), + code: ErrorCodes::ExpiredDecryptedTx + .into(), info: format!( - "Decrypted tx carries a wrong \ - chain id: expected {}, found {}", - self.chain_id, tx.chain_id + "Decrypted tx expired at {:#?}, \ + block time: {:#?}", + exp, block_time ), }; } - - // Tx expiration - if let Some(exp) = tx.expiration { - if block_time > exp { - return TxResult { - code: - ErrorCodes::ExpiredDecryptedTx - .into(), - info: format!( - "Decrypted tx expired at \ - {:#?}, block time: {:#?}", - exp, block_time - ), - }; - } - } } TxResult { code: ErrorCodes::Ok.into(), @@ -423,7 +434,7 @@ where } // validate the ciphertext via Ferveo - if !wrapper.validate_ciphertext() { + if !tx.validate_ciphertext() { TxResult { code: ErrorCodes::InvalidTx.into(), info: format!( @@ -434,7 +445,7 @@ where } else { // Replay protection checks if let Err(e) = self.replay_protection_checks( - &wrapper, + &tx, tx_bytes, temp_wl_storage, ) { @@ -504,16 +515,14 @@ where /// are covered by the e2e tests. #[cfg(test)] mod test_process_proposal { - use borsh::BorshDeserialize; use namada::ledger::parameters::storage::get_wrapper_tx_fees_key; - use namada::proto::SignedTxData; + use namada::proto::{Code, Data, Section, Signature}; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::Epoch; use namada::types::token::Amount; - use namada::types::transaction::encrypted::EncryptedTx; - use namada::types::transaction::protocol::ProtocolTxType; - use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; + use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; + use namada::types::transaction::{Fee, WrapperTx, MIN_FEE}; use super::*; use crate::node::ledger::shell::test_utils::{ @@ -526,13 +535,7 @@ mod test_process_proposal { fn test_unsigned_wrapper_rejected() { let (mut shell, _) = test_utils::setup(1); let keypair = gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -540,18 +543,14 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let tx = Tx::new( - vec![], - Some(TxType::Wrapper(wrapper).try_to_vec().expect("Test failed")), - shell.chain_id.clone(), - None, - ) - .to_bytes(); + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + outer_tx.encrypt(&Default::default()); + let tx = outer_tx.to_bytes(); #[allow(clippy::redundant_clone)] let request = ProcessProposal { txs: vec![tx.clone()], @@ -566,7 +565,10 @@ mod test_process_proposal { ); assert_eq!( response[0].result.info, - String::from("Wrapper transactions must be signed") + String::from( + "WrapperTx signature verification failed: Transaction \ + doesn't have any data with a signature." + ) ); } } @@ -578,14 +580,7 @@ mod test_process_proposal { fn test_wrapper_bad_signature_rejected() { let (mut shell, _) = test_utils::setup(1); let keypair = gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let timestamp = tx.timestamp; - let mut wrapper = WrapperTx::new( + let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 100.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -593,51 +588,23 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ) - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); - let new_tx = if let Some(Ok(SignedTxData { - data: Some(data), - sig, - })) = wrapper - .data - .take() - .map(|data| SignedTxData::try_from_slice(&data[..])) - { - let mut new_wrapper = if let TxType::Wrapper(wrapper) = - ::deserialize(&mut data.as_ref()) - .expect("Test failed") - { - wrapper - } else { - panic!("Test failed") - }; - + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + outer_tx.add_section(Section::Signature(Signature::new( + &outer_tx.header_hash(), + &keypair, + ))); + outer_tx.encrypt(&Default::default()); + let mut new_tx = outer_tx.clone(); + if let TxType::Wrapper(wrapper) = &mut new_tx.header.tx_type { // we mount a malleability attack to try and remove the fee - new_wrapper.fee.amount = 0.into(); - let new_data = TxType::Wrapper(new_wrapper) - .try_to_vec() - .expect("Test failed"); - Tx { - code_or_hash: vec![], - data: Some( - SignedTxData { - sig, - data: Some(new_data), - } - .try_to_vec() - .expect("Test failed"), - ), - timestamp, - chain_id: shell.chain_id.clone(), - expiration: None, - } + wrapper.fee.amount = 0.into(); } else { - panic!("Test failed"); + panic!("Test failed") }; let request = ProcessProposal { txs: vec![new_tx.to_bytes()], @@ -646,8 +613,9 @@ mod test_process_proposal { match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), Err(TestError::RejectProposal(response)) => { - let expected_error = - "Signature verification failed: Invalid signature"; + let expected_error = "WrapperTx signature verification \ + failed: Transaction doesn't have any \ + data with a signature."; assert_eq!( response[0].result.code, u32::from(ErrorCodes::InvalidSig) @@ -676,13 +644,7 @@ mod test_process_proposal { ) .unwrap(); let keypair = gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 1.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -690,15 +652,20 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ) - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + outer_tx.add_section(Section::Signature(Signature::new( + &outer_tx.header_hash(), + &keypair, + ))); + outer_tx.encrypt(&Default::default()); + let request = ProcessProposal { - txs: vec![wrapper.to_bytes()], + txs: vec![outer_tx.to_bytes()], }; match shell.process_proposal(request) { @@ -744,13 +711,7 @@ mod test_process_proposal { ) .unwrap(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: Amount::whole(1_000_100), token: shell.wl_storage.storage.native_token.clone(), @@ -758,16 +719,20 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ) - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + outer_tx.add_section(Section::Signature(Signature::new( + &outer_tx.header_hash(), + &keypair, + ))); + outer_tx.encrypt(&Default::default()); let request = ProcessProposal { - txs: vec![wrapper.to_bytes()], + txs: vec![outer_tx.to_bytes()], }; match shell.process_proposal(request) { @@ -796,34 +761,31 @@ mod test_process_proposal { let keypair = gen_keypair(); let mut txs = vec![]; for i in 0..3 { - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some(format!("transaction data: {}", i).as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( - Fee { - amount: i.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - &keypair, - Epoch(0), - 0.into(), - tx.clone(), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ); - shell.enqueue_tx(wrapper); - let mut decrypted_tx = - Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { - tx, + let mut outer_tx = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: i.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - })); - decrypted_tx.chain_id = shell.chain_id.clone(); - txs.push(decrypted_tx); + None, + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + outer_tx.set_data(Data::new( + format!("transaction data: {}", i).as_bytes().to_owned(), + )); + outer_tx.encrypt(&Default::default()); + shell.enqueue_tx(outer_tx.clone()); + + outer_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + })); + txs.push(outer_tx); } let response = { let request = ProcessProposal { @@ -859,13 +821,7 @@ mod test_process_proposal { let (mut shell, _) = test_utils::setup(1); let keypair = gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -873,17 +829,16 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - shell.enqueue_tx(wrapper.clone()); - - let mut tx = - Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable(wrapper))); - tx.chain_id = shell.chain_id.clone(); - + )))); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + tx.encrypt(&Default::default()); + shell.enqueue_tx(tx.clone()); + + tx.header.tx_type = TxType::Decrypted(DecryptedTx::Undecryptable); let request = ProcessProposal { txs: vec![tx.to_bytes()], }; @@ -914,13 +869,7 @@ mod test_process_proposal { let (mut shell, _) = test_utils::setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let mut wrapper = WrapperTx::new( + let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -928,20 +877,19 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - wrapper.tx_hash = Hash([0; 32]); + )))); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + tx.set_code_sechash(Hash([0u8; 32])); + tx.set_data_sechash(Hash([0u8; 32])); + tx.encrypt(&Default::default()); - shell.enqueue_tx(wrapper.clone()); - let mut tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( - #[allow(clippy::redundant_clone)] - wrapper.clone(), - ))); - tx.chain_id = shell.chain_id.clone(); + shell.enqueue_tx(tx.clone()); + tx.header.tx_type = TxType::Decrypted(DecryptedTx::Undecryptable); let request = ProcessProposal { txs: vec![tx.to_bytes()], }; @@ -964,10 +912,7 @@ mod test_process_proposal { fn test_undecryptable() { let (mut shell, _) = test_utils::setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - let pubkey = EncryptionKey::default(); // not valid tx bytes - let tx = "garbage data".as_bytes().to_owned(); - let inner_tx = EncryptedTx::encrypt(&tx, pubkey); let wrapper = WrapperTx { fee: Fee { amount: 0.into(), @@ -976,21 +921,18 @@ mod test_process_proposal { pk: keypair.ref_to(), epoch: Epoch(0), gas_limit: 0.into(), - inner_tx, - tx_hash: hash_tx(&tx), #[cfg(not(feature = "mainnet"))] pow_solution: None, }; - shell.enqueue_tx(wrapper.clone()); - let mut signed = - Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( - #[allow(clippy::redundant_clone)] - wrapper.clone(), - ))); - signed.chain_id = shell.chain_id.clone(); + let tx = Tx::new(TxType::Wrapper(Box::new(wrapper))); + let mut decrypted = tx.clone(); + decrypted.update_header(TxType::Decrypted(DecryptedTx::Undecryptable)); + + shell.enqueue_tx(tx); + let request = ProcessProposal { - txs: vec![signed.to_bytes()], + txs: vec![decrypted.to_bytes()], }; let response = if let [resp] = shell .process_proposal(request) @@ -1009,20 +951,13 @@ mod test_process_proposal { #[test] fn test_too_many_decrypted_txs() { let (mut shell, _) = test_utils::setup(1); - - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - - let mut tx = Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { - tx, + let mut tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] has_valid_pow: false, })); - tx.chain_id = shell.chain_id.clone(); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); let request = ProcessProposal { txs: vec![tx.to_bytes()], @@ -1050,14 +985,11 @@ mod test_process_proposal { fn test_raw_tx_rejected() { let (mut shell, _) = test_utils::setup(1); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let mut tx = Tx::from(TxType::Raw(tx)); - tx.chain_id = shell.chain_id.clone(); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + let request = ProcessProposal { txs: vec![tx.to_bytes()], }; @@ -1088,13 +1020,7 @@ mod test_process_proposal { let keypair = crate::wallet::defaults::daewon_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1102,17 +1028,20 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let signed = wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); // Write wrapper hash to storage - let wrapper_unsigned_hash = Hash(signed.unsigned_hash()); + let wrapper_unsigned_hash = wrapper.header_hash(); let hash_key = replay_protection::get_tx_hash_key(&wrapper_unsigned_hash); shell @@ -1123,8 +1052,9 @@ mod test_process_proposal { // Run validation let request = ProcessProposal { - txs: vec![signed.to_bytes()], + txs: vec![wrapper.to_bytes()], }; + match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), Err(TestError::RejectProposal(response)) => { @@ -1162,13 +1092,7 @@ mod test_process_proposal { .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) .unwrap(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1176,18 +1100,21 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let signed = wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); // Run validation let request = ProcessProposal { - txs: vec![signed.to_bytes(); 2], + txs: vec![wrapper.to_bytes(); 2], }; match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), @@ -1205,7 +1132,10 @@ mod test_process_proposal { format!( "Transaction replay attempt: Inner transaction hash \ {} already in storage", - wrapper.tx_hash + wrapper + .clone() + .update_header(TxType::Raw) + .header_hash(), ) ); } @@ -1220,13 +1150,7 @@ mod test_process_proposal { let keypair = crate::wallet::defaults::daewon_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1234,15 +1158,19 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let inner_unsigned_hash = wrapper.tx_hash.clone(); - let signed = wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); + let inner_unsigned_hash = + wrapper.clone().update_header(TxType::Raw).header_hash(); // Write inner hash to storage let hash_key = replay_protection::get_tx_hash_key(&inner_unsigned_hash); @@ -1254,7 +1182,7 @@ mod test_process_proposal { // Run validation let request = ProcessProposal { - txs: vec![signed.to_bytes()], + txs: vec![wrapper.to_bytes()], }; match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), @@ -1306,13 +1234,7 @@ mod test_process_proposal { .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) .unwrap(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1320,17 +1242,22 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let inner_unsigned_hash = wrapper.tx_hash.clone(); - let signed = wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + let mut new_wrapper = wrapper.clone(); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); + let inner_unsigned_hash = + wrapper.clone().update_header(TxType::Raw).header_hash(); - let new_wrapper = WrapperTx::new( + new_wrapper.update_header(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1338,18 +1265,18 @@ mod test_process_proposal { &keypair_2, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let new_signed = new_wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + new_wrapper.add_section(Section::Signature(Signature::new( + &new_wrapper.header_hash(), + &keypair, + ))); + new_wrapper.encrypt(&Default::default()); // Run validation let request = ProcessProposal { - txs: vec![signed.to_bytes(), new_signed.to_bytes()], + txs: vec![wrapper.to_bytes(), new_wrapper.to_bytes()], }; match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), @@ -1378,13 +1305,7 @@ mod test_process_proposal { let (mut shell, _) = test_utils::setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1392,25 +1313,32 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); + )))); let wrong_chain_id = ChainId("Wrong chain id".to_string()); - let signed = wrapper - .sign(&keypair, wrong_chain_id.clone(), None) - .expect("Test failed"); + wrapper.header.chain_id = wrong_chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + let mut protocol_tx = wrapper.clone(); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); - let protocol_tx = ProtocolTxType::EthereumStateUpdate(tx).sign( - &keypair.ref_to(), + protocol_tx.update_header(TxType::Protocol(Box::new(ProtocolTx { + pk: keypair.ref_to(), + tx: ProtocolTxType::EthereumStateUpdate, + }))); + protocol_tx.add_section(Section::Signature(Signature::new( + &protocol_tx.header_hash(), &keypair, - wrong_chain_id.clone(), - ); + ))); // Run validation let request = ProcessProposal { - txs: vec![signed.to_bytes(), protocol_tx.to_bytes()], + txs: vec![wrapper.to_bytes(), protocol_tx.to_bytes()], }; match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), @@ -1441,19 +1369,7 @@ mod test_process_proposal { let keypair = crate::wallet::defaults::daewon_keypair(); let wrong_chain_id = ChainId("Wrong chain id".to_string()); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("new transaction data".as_bytes().to_owned()), - wrong_chain_id.clone(), - None, - ); - let decrypted: Tx = DecryptedTx::Decrypted { - tx: tx.clone(), - has_valid_pow: false, - } - .into(); - let signed_decrypted = decrypted.sign(&keypair); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1461,12 +1377,24 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let wrapper_in_queue = WrapperTxInQueue { + )))); + wrapper.header.chain_id = wrong_chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper + .set_data(Data::new("new transaction data".as_bytes().to_owned())); + let mut decrypted = wrapper.clone(); + wrapper.encrypt(&Default::default()); + + decrypted.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + has_valid_pow: false, + })); + decrypted.add_section(Section::Signature(Signature::new( + &decrypted.header_hash(), + &keypair, + ))); + let wrapper_in_queue = TxInQueue { tx: wrapper, has_valid_pow: false, }; @@ -1474,7 +1402,7 @@ mod test_process_proposal { // Run validation let request = ProcessProposal { - txs: vec![signed_decrypted.to_bytes()], + txs: vec![decrypted.to_bytes()], }; match shell.process_proposal(request) { @@ -1502,13 +1430,7 @@ mod test_process_proposal { let (mut shell, _) = test_utils::setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1516,18 +1438,22 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let signed = wrapper - .sign(&keypair, shell.chain_id.clone(), Some(DateTimeUtc::now())) - .expect("Test failed"); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.header.expiration = Some(DateTimeUtc::now()); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); // Run validation let request = ProcessProposal { - txs: vec![signed.to_bytes()], + txs: vec![wrapper.to_bytes()], }; match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), @@ -1547,19 +1473,7 @@ mod test_process_proposal { let (mut shell, _) = test_utils::setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("new transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - Some(DateTimeUtc::now()), - ); - let decrypted: Tx = DecryptedTx::Decrypted { - tx: tx.clone(), - has_valid_pow: false, - } - .into(); - let signed_decrypted = decrypted.sign(&keypair); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1567,12 +1481,25 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let wrapper_in_queue = WrapperTxInQueue { + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.header.expiration = Some(DateTimeUtc::now()); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper + .set_data(Data::new("new transaction data".as_bytes().to_owned())); + let mut decrypted = wrapper.clone(); + wrapper.encrypt(&Default::default()); + + decrypted.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + has_valid_pow: false, + })); + decrypted.add_section(Section::Signature(Signature::new( + &decrypted.header_hash(), + &keypair, + ))); + let wrapper_in_queue = TxInQueue { tx: wrapper, has_valid_pow: false, }; @@ -1580,7 +1507,7 @@ mod test_process_proposal { // Run validation let request = ProcessProposal { - txs: vec![signed_decrypted.to_bytes()], + txs: vec![decrypted.to_bytes()], }; match shell.process_proposal(request) { Ok(response) => { diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 05ffbf8de0..7bb02a6aef 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -186,7 +186,7 @@ impl AbcippShim { let mut end_block_request: FinalizeBlock = begin_block_request.into(); let hash = self.get_hash(); - end_block_request.hash = BlockHash::from(hash.clone()); + end_block_request.hash = BlockHash::from(hash); end_block_request.txs = txs; self.service .call(Request::FinalizeBlock(end_block_request)) diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 73c21ba6ca..e9222ab213 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -246,7 +246,7 @@ mod tests { // insert let vp1 = Hash::sha256("vp1".as_bytes()); - storage.write(&key, vp1.clone()).expect("write failed"); + storage.write(&key, vp1).expect("write failed"); // check let (vp_code_hash, gas) = diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 9d54bc6de3..695946e651 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -537,6 +537,19 @@ impl DB for RocksDB { return Ok(None); } }; + let update_epoch_blocks_delay: Option = match self + .0 + .get_cf(state_cf, "update_epoch_blocks_delay") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load epoch update block delay from the DB" + ); + return Ok(None); + } + }; let tx_queue: TxQueue = match self .0 .get_cf(state_cf, "tx_queue") @@ -636,6 +649,7 @@ impl DB for RocksDB { results, next_epoch_min_start_height, next_epoch_min_start_time, + update_epoch_blocks_delay, address_gen, tx_queue, })) @@ -660,10 +674,11 @@ impl DB for RocksDB { height, epoch, pred_epochs, - results, next_epoch_min_start_height, next_epoch_min_start_time, + update_epoch_blocks_delay, address_gen, + results, tx_queue, }: BlockStateWrite = state; @@ -704,6 +719,24 @@ impl DB for RocksDB { "next_epoch_min_start_time", types::encode(&next_epoch_min_start_time), ); + if let Some(current_value) = self + .0 + .get_cf(state_cf, "update_epoch_blocks_delay") + .map_err(|e| Error::DBError(e.into_string()))? + { + // Write the predecessor value for rollback + batch.0.put_cf( + state_cf, + "pred/update_epoch_blocks_delay", + current_value, + ); + } + batch.0.put_cf( + state_cf, + "update_epoch_blocks_delay", + types::encode(&update_epoch_blocks_delay), + ); + // Tx queue if let Some(pred_tx_queue) = self .0 @@ -1412,6 +1445,7 @@ mod test { let height = BlockHeight::default(); let next_epoch_min_start_height = BlockHeight::default(); let next_epoch_min_start_time = DateTimeUtc::now(); + let update_epoch_blocks_delay = None; let address_gen = EstablishedAddressGen::new("whatever"); let tx_queue = TxQueue::default(); let results = BlockResults::default(); @@ -1425,6 +1459,7 @@ mod test { pred_epochs: &pred_epochs, next_epoch_min_start_height, next_epoch_min_start_time, + update_epoch_blocks_delay, address_gen: &address_gen, tx_queue: &tx_queue, }; diff --git a/apps/src/lib/wallet/keys.rs b/apps/src/lib/wallet/keys.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/apps/src/lib/wallet/keys.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 04aae73dc6..5e97d1bd68 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -1,5 +1,4 @@ pub mod defaults; -mod keys; pub mod pre_genesis; mod store; @@ -7,15 +6,16 @@ use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::{env, fs}; +use namada::bip39::{Language, Mnemonic}; pub use namada::ledger::wallet::alias::Alias; use namada::ledger::wallet::{ - ConfirmationResponse, FindKeyError, Wallet, WalletUtils, -}; -pub use namada::ledger::wallet::{ - DecryptionError, StoredKeypair, ValidatorData, ValidatorKeys, + ConfirmationResponse, FindKeyError, GenRestoreKeyError, Wallet, WalletUtils, }; +pub use namada::ledger::wallet::{ValidatorData, ValidatorKeys}; use namada::types::key::*; +use rand_core::OsRng; pub use store::wallet_file; +use zeroize::Zeroizing; use crate::cli; use crate::config::genesis::genesis_config::GenesisConfig; @@ -24,28 +24,57 @@ use crate::config::genesis::genesis_config::GenesisConfig; pub struct CliWalletUtils; impl WalletUtils for CliWalletUtils { + type Rng = OsRng; type Storage = PathBuf; - /// Read the password for encryption/decryption from the file/env/stdin. - /// Panics if all options are empty/invalid. - fn read_password(prompt_msg: &str) -> String { + fn read_decryption_password() -> Zeroizing { + match env::var("NAMADA_WALLET_PASSWORD_FILE") { + Ok(path) => Zeroizing::new( + fs::read_to_string(path) + .expect("Something went wrong reading the file"), + ), + Err(_) => match env::var("NAMADA_WALLET_PASSWORD") { + Ok(password) => Zeroizing::new(password), + Err(_) => { + let prompt = "Enter your decryption password: "; + rpassword::read_password_from_tty(Some(prompt)) + .map(Zeroizing::new) + .expect("Failed reading password from tty.") + } + }, + } + } + + fn read_encryption_password() -> Zeroizing { let pwd = match env::var("NAMADA_WALLET_PASSWORD_FILE") { - Ok(path) => fs::read_to_string(path) - .expect("Something went wrong reading the file"), + Ok(path) => Zeroizing::new( + fs::read_to_string(path) + .expect("Something went wrong reading the file"), + ), Err(_) => match env::var("NAMADA_WALLET_PASSWORD") { - Ok(password) => password, - Err(_) => rpassword::read_password_from_tty(Some(prompt_msg)) - .unwrap_or_default(), + Ok(password) => Zeroizing::new(password), + Err(_) => { + let prompt = "Enter your encryption password: "; + read_and_confirm_passphrase_tty(prompt).unwrap_or_else( + |e| { + eprintln!("{e}"); + eprintln!( + "Action cancelled, no changes persisted." + ); + cli::safe_exit(1) + }, + ) + } }, }; - if pwd.is_empty() { + if pwd.as_str().is_empty() { eprintln!("Password cannot be empty"); + eprintln!("Action cancelled, no changes persisted."); cli::safe_exit(1) } pwd } - /// Read an alias from the file/env/stdin. fn read_alias(prompt_msg: &str) -> String { print!("Choose an alias for {}: ", prompt_msg); io::stdout().flush().unwrap(); @@ -54,6 +83,26 @@ impl WalletUtils for CliWalletUtils { alias.trim().to_owned() } + fn read_mnemonic_code() -> Result { + let phrase = get_secure_user_input("Input mnemonic code: ") + .map_err(|_| GenRestoreKeyError::MnemonicInputError)?; + Mnemonic::from_phrase(phrase.as_ref(), Language::English) + .map_err(|_| GenRestoreKeyError::MnemonicInputError) + } + + fn read_mnemonic_passphrase(confirm: bool) -> Zeroizing { + let prompt = "Enter BIP39 passphrase (empty for none): "; + let result = if confirm { + read_and_confirm_passphrase_tty(prompt) + } else { + rpassword::read_password_from_tty(Some(prompt)).map(Zeroizing::new) + }; + result.unwrap_or_else(|e| { + eprintln!("{}", e); + cli::safe_exit(1); + }) + } + // The given alias has been selected but conflicts with another alias in // the store. Offer the user to either replace existing mapping, alter the // chosen alias to a name of their chosing, or cancel the aliasing. @@ -101,6 +150,38 @@ impl WalletUtils for CliWalletUtils { } } +fn get_secure_user_input(request: S) -> std::io::Result> +where + S: std::fmt::Display, +{ + print!("{} ", request); + std::io::stdout().flush()?; + + let mut response = Zeroizing::default(); + std::io::stdin().read_line(&mut response)?; + Ok(response) +} + +pub fn read_and_confirm_passphrase_tty( + prompt: &str, +) -> Result, std::io::Error> { + let passphrase = + rpassword::read_password_from_tty(Some(prompt)).map(Zeroizing::new)?; + if !passphrase.is_empty() { + let confirmed = rpassword::read_password_from_tty(Some( + "Enter same passphrase again: ", + )) + .map(Zeroizing::new)?; + if confirmed != passphrase { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Passphrases did not match", + )); + } + } + Ok(passphrase) +} + /// Generate keypair /// for signing protocol txs and for the DKG (which will also be stored) /// A protocol keypair may be optionally provided, indicating that @@ -183,28 +264,38 @@ pub fn load_or_new_from_genesis( Wallet::::new(store_dir.to_path_buf(), store) } -/// Read the password for encryption from the file/env/stdin with -/// confirmation. -pub fn read_and_confirm_pwd(unsafe_dont_encrypt: bool) -> Option { - let password = if unsafe_dont_encrypt { +/// Read the password for encryption from the file/env/stdin, with +/// confirmation if read from stdin. +pub fn read_and_confirm_encryption_password( + unsafe_dont_encrypt: bool, +) -> Option> { + if unsafe_dont_encrypt { println!("Warning: The keypair will NOT be encrypted."); None } else { - Some(CliWalletUtils::read_password( - "Enter your encryption password: ", - )) - }; - // Bis repetita for confirmation. - let to_confirm = if unsafe_dont_encrypt { - None - } else { - Some(CliWalletUtils::read_password( - "To confirm, please enter the same encryption password once more: ", - )) - }; - if to_confirm != password { - eprintln!("Your two inputs do not match!"); - cli::safe_exit(1) + Some(CliWalletUtils::read_encryption_password()) + } +} + +#[cfg(test)] +mod tests { + use namada::bip39::MnemonicType; + use namada::ledger::wallet::WalletUtils; + use rand_core; + + use super::CliWalletUtils; + + #[test] + fn test_generate_mnemonic() { + const MNEMONIC_TYPE: MnemonicType = MnemonicType::Words12; + + let mut rng = rand_core::OsRng; + let mnemonic1 = + CliWalletUtils::generate_mnemonic_code(MNEMONIC_TYPE, &mut rng) + .unwrap(); + let mnemonic2 = + CliWalletUtils::generate_mnemonic_code(MNEMONIC_TYPE, &mut rng) + .unwrap(); + assert_ne!(mnemonic1.into_phrase(), mnemonic2.into_phrase()); } - password } diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 12209d5674..55638ab31e 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -8,9 +8,10 @@ use namada::ledger::wallet::pre_genesis::{ }; use namada::ledger::wallet::{gen_key_to_store, WalletUtils}; use namada::types::key::SchemeType; +use zeroize::Zeroizing; use crate::wallet::store::gen_validator_keys; -use crate::wallet::{read_and_confirm_pwd, CliWalletUtils}; +use crate::wallet::{read_and_confirm_encryption_password, CliWalletUtils}; /// Validator pre-genesis wallet file name const VALIDATOR_FILE_NAME: &str = "wallet.toml"; @@ -27,7 +28,7 @@ pub fn gen_and_store( unsafe_dont_encrypt: bool, store_dir: &Path, ) -> std::io::Result { - let password = read_and_confirm_pwd(unsafe_dont_encrypt); + let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let validator = gen(scheme, password); let data = validator.store.encode(); let wallet_path = validator_file_name(store_dir); @@ -66,9 +67,7 @@ pub fn load(store_dir: &Path) -> Result { || store.consensus_key.is_encrypted() || store.account_key.is_encrypted() { - Some(CliWalletUtils::read_password( - "Enter decryption password: ", - )) + Some(CliWalletUtils::read_decryption_password()) } else { None }; @@ -99,17 +98,20 @@ pub fn load(store_dir: &Path) -> Result { /// Generate a new [`ValidatorWallet`] with required pre-genesis keys. Will /// prompt for password when `!unsafe_dont_encrypt`. -fn gen(scheme: SchemeType, password: Option) -> ValidatorWallet { - let (account_key, account_sk) = gen_key_to_store(scheme, &password); +fn gen( + scheme: SchemeType, + password: Option>, +) -> ValidatorWallet { + let (account_key, account_sk) = gen_key_to_store(scheme, password.clone()); let (consensus_key, consensus_sk) = gen_key_to_store( // Note that TM only allows ed25519 for consensus key SchemeType::Ed25519, - &password, + password.clone(), ); let (tendermint_node_key, tendermint_node_sk) = gen_key_to_store( // Note that TM only allows ed25519 for node IDs SchemeType::Ed25519, - &password, + password, ); let validator_keys = gen_validator_keys(None, scheme); let store = ValidatorStore { diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index fcdcfb24d9..4b2fcfa9ed 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -12,7 +12,7 @@ use file_lock::{FileLock, FileOptions}; use namada::ledger::wallet::store::AddressVpType; #[cfg(feature = "dev")] use namada::ledger::wallet::StoredKeypair; -use namada::ledger::wallet::{gen_sk, Store, ValidatorKeys}; +use namada::ledger::wallet::{gen_sk_rng, Store, ValidatorKeys}; #[cfg(not(feature = "dev"))] use namada::types::address::Address; use namada::types::key::*; @@ -169,7 +169,8 @@ pub fn gen_validator_keys( protocol_keypair: Option, scheme: SchemeType, ) -> ValidatorKeys { - let protocol_keypair = protocol_keypair.unwrap_or_else(|| gen_sk(scheme)); + let protocol_keypair = + protocol_keypair.unwrap_or_else(|| gen_sk_rng(scheme)); let dkg_keypair = ferveo_common::Keypair::::new( &mut StdRng::from_entropy(), ); diff --git a/core/Cargo.toml b/core/Cargo.toml index a2d8358a23..8f155db2d5 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -7,7 +7,7 @@ resolver = "2" version = "0.16.0" [features] -default = [] +default = ["multicore"] mainnet = [] ferveo-tpke = [ "ferveo", @@ -25,12 +25,6 @@ secp256k1-sign-verify = [ "libsecp256k1/hmac", ] -abcipp = [ - "ibc-proto-abcipp", - "ibc-abcipp", - "tendermint-abcipp", - "tendermint-proto-abcipp", -] abciplus = [ "ibc", "ibc-proto", @@ -42,9 +36,9 @@ ibc-mocks = [ "ibc/mocks", "ibc/std", ] -ibc-mocks-abcipp = [ - "ibc-abcipp/mocks", - "ibc-abcipp/std", + +multicore = [ + "bellman/multicore" ] # for integration tests and test utilies @@ -63,7 +57,7 @@ ark-serialize = {version = "0.3"} # branch = "bat/arse-merkle-tree" arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/heliaxdev/sparse-merkle-tree", rev = "e086b235ed6e68929bf73f617dd61cd17b000a56", default-features = false, features = ["std", "borsh"]} bech32 = "0.8.0" -bellman = "0.11.2" +bellman = { version = "0.11.2", default-features = false, features = ["groth16"] } borsh = "0.9.0" chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} data-encoding = "2.3.2" @@ -75,13 +69,12 @@ tpke = {package = "group-threshold-cryptography", optional = true, git = "https: # TODO using the same version of tendermint-rs as we do here. ibc = {version = "0.36.0", default-features = false, features = ["serde"], optional = true} ibc-proto = {version = "0.26.0", default-features = false, optional = true} -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "db14744bfba6239cc5f58345ff90f8b7d42637d6", default-features = false, features = ["serde"], optional = true} -ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "dd8ba23110a144ffe2074a0b889676468266435a", default-features = false, optional = true} ics23 = "0.9.0" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} itertools = "0.10.0" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +# branch = "murisi/namada-integration" +masp_primitives = { git = "https://github.com/anoma/masp", rev = "cfea8c95d3f73077ca3e25380fd27e5b46e828fd" } proptest = {git = "https://github.com/heliaxdev/proptest", rev = "8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1", optional = true} prost = "0.11.6" prost-types = "0.11.6" @@ -95,8 +88,6 @@ serde_json = "1.0.62" sha2 = "0.9.3" tendermint = {version = "0.23.6", optional = true} tendermint-proto = {version = "0.23.6", optional = true} -tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "02b256829e80f8cfecf3fa0d625c2a76c79cd043", optional = true} -tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "02b256829e80f8cfecf3fa0d625c2a76c79cd043", optional = true} thiserror = "1.0.38" tracing = "0.1.30" zeroize = {version = "1.5.5", features = ["zeroize_derive"]} diff --git a/core/src/ledger/storage/merkle_tree.rs b/core/src/ledger/storage/merkle_tree.rs index 31fb40b1eb..046ebca4e6 100644 --- a/core/src/ledger/storage/merkle_tree.rs +++ b/core/src/ledger/storage/merkle_tree.rs @@ -723,7 +723,7 @@ mod test { let stores_write = tree.stores(); let mut stores_read = MerkleTreeStoresRead::default(); for st in StoreType::iter() { - stores_read.set_root(st, stores_write.root(st).clone()); + stores_read.set_root(st, *stores_write.root(st)); stores_read.set_store(stores_write.store(st).to_owned()); } let restored_tree = diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index 16e28d2759..c8cfc3361b 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -83,6 +83,13 @@ impl DB for MockDB { } None => return Ok(None), }; + let update_epoch_blocks_delay: Option = + match self.0.borrow().get("update_epoch_blocks_delay") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; #[cfg(feature = "ferveo-tpke")] let tx_queue: TxQueue = match self.0.borrow().get("tx_queue") { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, @@ -160,6 +167,7 @@ impl DB for MockDB { pred_epochs, next_epoch_min_start_height, next_epoch_min_start_time, + update_epoch_blocks_delay, address_gen, results, #[cfg(feature = "ferveo-tpke")] @@ -188,6 +196,7 @@ impl DB for MockDB { pred_epochs, next_epoch_min_start_height, next_epoch_min_start_time, + update_epoch_blocks_delay, address_gen, results, #[cfg(feature = "ferveo-tpke")] @@ -203,6 +212,10 @@ impl DB for MockDB { "next_epoch_min_start_time".into(), types::encode(&next_epoch_min_start_time), ); + self.0.borrow_mut().insert( + "update_epoch_blocks_delay".into(), + types::encode(&update_epoch_blocks_delay), + ); #[cfg(feature = "ferveo-tpke")] { self.0 diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index cb3f9299df..fac8c9892d 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -168,6 +168,8 @@ pub struct BlockStateRead { pub next_epoch_min_start_height: BlockHeight, /// Minimum block time at which the next epoch may start pub next_epoch_min_start_time: DateTimeUtc, + /// Update epoch delay + pub update_epoch_blocks_delay: Option, /// Established address generator pub address_gen: EstablishedAddressGen, /// Results of applying transactions @@ -195,6 +197,8 @@ pub struct BlockStateWrite<'a> { pub next_epoch_min_start_height: BlockHeight, /// Minimum block time at which the next epoch may start pub next_epoch_min_start_time: DateTimeUtc, + /// Update epoch delay + pub update_epoch_blocks_delay: Option, /// Established address generator pub address_gen: &'a EstablishedAddressGen, /// Results of applying transactions @@ -390,6 +394,7 @@ where pred_epochs, next_epoch_min_start_height, next_epoch_min_start_time, + update_epoch_blocks_delay, results, address_gen, #[cfg(feature = "ferveo-tpke")] @@ -405,6 +410,7 @@ where self.last_epoch = epoch; self.next_epoch_min_start_height = next_epoch_min_start_height; self.next_epoch_min_start_time = next_epoch_min_start_time; + self.update_epoch_blocks_delay = update_epoch_blocks_delay; self.address_gen = address_gen; // Rebuild Merkle tree self.block.tree = MerkleTree::new(merkle_tree_stores) @@ -462,6 +468,7 @@ where pred_epochs: &self.block.pred_epochs, next_epoch_min_start_height: self.next_epoch_min_start_height, next_epoch_min_start_time: self.next_epoch_min_start_time, + update_epoch_blocks_delay: self.update_epoch_blocks_delay, address_gen: &self.address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue: &self.tx_queue, @@ -919,7 +926,6 @@ where } } } - Ok(()) } } diff --git a/core/src/ledger/storage/write_log.rs b/core/src/ledger/storage/write_log.rs index bf4881aab0..938e4e8948 100644 --- a/core/src/ledger/storage/write_log.rs +++ b/core/src/ledger/storage/write_log.rs @@ -439,11 +439,7 @@ impl WriteLog { } StorageModification::InitAccount { vp_code_hash } => { storage - .batch_write_subspace_val( - batch, - key, - vp_code_hash.clone(), - ) + .batch_write_subspace_val(batch, key, *vp_code_hash) .map_err(Error::StorageError)?; } // temporary value isn't persisted @@ -609,7 +605,7 @@ mod tests { // init let init_vp = "initialized".as_bytes().to_vec(); let vp_hash = Hash::sha256(init_vp); - let (addr, gas) = write_log.init_account(&address_gen, vp_hash.clone()); + let (addr, gas) = write_log.init_account(&address_gen, vp_hash); let vp_key = storage::Key::validity_predicate(&addr); assert_eq!(gas, (vp_key.len() + vp_hash.len()) as u64); @@ -693,7 +689,7 @@ mod tests { // initialize an account let vp1 = Hash::sha256("vp1".as_bytes()); - let (addr1, _) = write_log.init_account(&address_gen, vp1.clone()); + let (addr1, _) = write_log.init_account(&address_gen, vp1); write_log.commit_tx(); // write values diff --git a/core/src/ledger/storage_api/collections/lazy_map.rs b/core/src/ledger/storage_api/collections/lazy_map.rs index c1e8ae6dbf..5870fd7b3f 100644 --- a/core/src/ledger/storage_api/collections/lazy_map.rs +++ b/core/src/ledger/storage_api/collections/lazy_map.rs @@ -740,4 +740,119 @@ mod test { Ok(()) } + + #[test] + fn test_nested_map_basics() -> storage_api::Result<()> { + let mut storage = TestWlStorage::default(); + let key = storage::Key::parse("testing").unwrap(); + + // A nested map from u32 -> String -> u32 + let nested_map = NestedMap::>::open(key); + + assert!(nested_map.is_empty(&storage)?); + assert!(nested_map.iter(&storage)?.next().is_none()); + + // Insert a value + nested_map + .at(&0) + .insert(&mut storage, "string1".to_string(), 100)?; + + assert!(!nested_map.is_empty(&storage)?); + assert!(nested_map.iter(&storage)?.next().is_some()); + assert_eq!( + nested_map.at(&0).get(&storage, &"string1".to_string())?, + Some(100) + ); + assert_eq!( + nested_map.at(&0).get(&storage, &"string2".to_string())?, + None + ); + + // Insert more values + nested_map + .at(&1) + .insert(&mut storage, "string1".to_string(), 200)?; + nested_map + .at(&0) + .insert(&mut storage, "string2".to_string(), 300)?; + + let mut it = nested_map.iter(&storage)?; + let ( + NestedSubKey::Data { + key, + nested_sub_key: SubKey::Data(inner_key), + }, + inner_val, + ) = it.next().unwrap()?; + assert_eq!(key, 0); + assert_eq!(inner_key, "string1".to_string()); + assert_eq!(inner_val, 100); + + let ( + NestedSubKey::Data { + key, + nested_sub_key: SubKey::Data(inner_key), + }, + inner_val, + ) = it.next().unwrap()?; + assert_eq!(key, 0); + assert_eq!(inner_key, "string2".to_string()); + assert_eq!(inner_val, 300); + + let ( + NestedSubKey::Data { + key, + nested_sub_key: SubKey::Data(inner_key), + }, + inner_val, + ) = it.next().unwrap()?; + assert_eq!(key, 1); + assert_eq!(inner_key, "string1".to_string()); + assert_eq!(inner_val, 200); + + // Next element should be None + assert!(it.next().is_none()); + drop(it); + + // Start removing elements + let rem = nested_map + .at(&0) + .remove(&mut storage, &"string2".to_string())?; + assert_eq!(rem, Some(300)); + assert_eq!( + nested_map.at(&0).get(&storage, &"string2".to_string())?, + None + ); + assert_eq!(nested_map.at(&0).len(&storage)?, 1_u64); + assert_eq!(nested_map.at(&1).len(&storage)?, 1_u64); + assert_eq!(nested_map.iter(&storage)?.count(), 2); + + // Start removing elements + let rem = nested_map + .at(&0) + .remove(&mut storage, &"string1".to_string())?; + assert_eq!(rem, Some(100)); + assert_eq!( + nested_map.at(&0).get(&storage, &"string1".to_string())?, + None + ); + assert_eq!(nested_map.at(&0).len(&storage)?, 0_u64); + assert_eq!(nested_map.at(&1).len(&storage)?, 1_u64); + assert_eq!(nested_map.iter(&storage)?.count(), 1); + + // Start removing elements + let rem = nested_map + .at(&1) + .remove(&mut storage, &"string1".to_string())?; + assert_eq!(rem, Some(200)); + assert_eq!( + nested_map.at(&1).get(&storage, &"string1".to_string())?, + None + ); + assert_eq!(nested_map.at(&0).len(&storage)?, 0_u64); + assert_eq!(nested_map.at(&1).len(&storage)?, 0_u64); + assert!(nested_map.is_empty(&storage)?); + + Ok(()) + } } diff --git a/core/src/ledger/storage_api/collections/lazy_vec.rs b/core/src/ledger/storage_api/collections/lazy_vec.rs index 1e83456814..d21ca1c515 100644 --- a/core/src/ledger/storage_api/collections/lazy_vec.rs +++ b/core/src/ledger/storage_api/collections/lazy_vec.rs @@ -407,7 +407,7 @@ impl LazyVec { // `LazyVec` methods with borsh encoded values `T` impl LazyVec where - T: BorshSerialize + BorshDeserialize + 'static, + T: BorshSerialize + BorshDeserialize + 'static + Debug, { /// Appends an element to the back of a collection. pub fn push(&self, storage: &mut S, val: T) -> Result<()> @@ -470,6 +470,23 @@ where storage.read(&self.get_data_key(index)) } + /// Read the first element + pub fn front(&self, storage: &S) -> Result> + where + S: StorageRead, + { + self.get(storage, 0) + } + + /// Read the last element + pub fn back(&self, storage: &S) -> Result> + where + S: StorageRead, + { + let len = self.len(storage)?; + self.get(storage, len - 1) + } + /// 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. diff --git a/core/src/ledger/storage_api/collections/mod.rs b/core/src/ledger/storage_api/collections/mod.rs index 6301d151be..ff1136acd5 100644 --- a/core/src/ledger/storage_api/collections/mod.rs +++ b/core/src/ledger/storage_api/collections/mod.rs @@ -60,7 +60,7 @@ pub trait LazyCollection { type SubKeyWithData: Debug; /// A type of a value in the inner-most collection - type Value: BorshDeserialize; + type Value: BorshDeserialize + Debug; /// Create or use an existing vector with the given storage `key`. fn open(key: storage::Key) -> Self; diff --git a/core/src/ledger/vp_env.rs b/core/src/ledger/vp_env.rs index f1d1210e28..b9ce0caced 100644 --- a/core/src/ledger/vp_env.rs +++ b/core/src/ledger/vp_env.rs @@ -4,9 +4,9 @@ use borsh::BorshDeserialize; use super::storage_api::{self, StorageRead}; +use crate::proto::Tx; use crate::types::address::Address; use crate::types::hash::Hash; -use crate::types::key::common; use crate::types::storage::{ BlockHash, BlockHeight, Epoch, Header, Key, TxIndex, }; @@ -91,20 +91,11 @@ where fn eval( &self, vp_code: Hash, - input_data: Vec, - ) -> Result; - - /// Verify a transaction signature. The signature is expected to have been - /// produced on the encoded transaction [`crate::proto::Tx`] - /// using [`crate::proto::Tx::sign`]. - fn verify_tx_signature( - &self, - pk: &common::PublicKey, - sig: &common::Signature, + input_data: Tx, ) -> Result; /// Get a tx hash - fn get_tx_code_hash(&self) -> Result; + fn get_tx_code_hash(&self) -> Result, storage_api::Error>; /// Verify a MASP transaction fn verify_masp(&self, tx: Vec) -> Result; diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index daeb0e9a49..37886a3b7c 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -3,27 +3,23 @@ pub mod generated; mod types; -pub use types::{Dkg, Error, Signed, SignedTxData, Tx}; +pub use types::{ + Code, Commitment, Data, Dkg, Error, Header, MaspBuilder, Section, + Signature, Tx, TxError, +}; #[cfg(test)] mod tests { - use std::time::SystemTime; - use data_encoding::HEXLOWER; use generated::types::Tx; use prost::Message; use super::*; - use crate::types::chain::ChainId; #[test] fn encoding_round_trip() { let tx = Tx { - code_or_hash: "wasm code".as_bytes().to_owned(), - data: Some("arbitrary data".as_bytes().to_owned()), - timestamp: Some(SystemTime::now().into()), - chain_id: ChainId::default().0, - expiration: Some(SystemTime::now().into()), + data: "arbitrary data".as_bytes().to_owned(), }; let mut tx_bytes = vec![]; tx.encode(&mut tx_bytes).unwrap(); diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index f82fb7bd8b..e350ed4ee7 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -1,31 +1,47 @@ -use std::convert::{TryFrom, TryInto}; -use std::hash::{Hash, Hasher}; +use std::collections::HashSet; +use std::convert::TryFrom; +#[cfg(feature = "ferveo-tpke")] +use ark_ec::AffineCurve; +#[cfg(feature = "ferveo-tpke")] +use ark_ec::PairingEngine; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use masp_primitives::transaction::builder::Builder; +use masp_primitives::transaction::components::sapling::builder::SaplingMetadata; +use masp_primitives::transaction::Transaction; +use masp_primitives::zip32::ExtendedFullViewingKey; use prost::Message; +use serde::de::Error as SerdeError; use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; use thiserror::Error; use super::generated::types; #[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] use crate::tendermint_proto::abci::ResponseDeliverTx; +use crate::types::address::Address; use crate::types::chain::ChainId; use crate::types::key::*; +use crate::types::storage::Epoch; use crate::types::time::DateTimeUtc; #[cfg(feature = "ferveo-tpke")] use crate::types::token::Transfer; -use crate::types::transaction::hash_tx; #[cfg(feature = "ferveo-tpke")] -use crate::types::transaction::process_tx; +use crate::types::transaction::protocol::ProtocolTx; +#[cfg(feature = "ferveo-tpke")] +use crate::types::transaction::EllipticCurve; #[cfg(feature = "ferveo-tpke")] -use crate::types::transaction::DecryptedTx; +use crate::types::transaction::EncryptionKey; #[cfg(feature = "ferveo-tpke")] -use crate::types::transaction::TxType; +use crate::types::transaction::WrapperTxErr; +use crate::types::transaction::{hash_tx, DecryptedTx, TxType, WrapperTx}; #[derive(Error, Debug)] pub enum Error { #[error("Error decoding a transaction from bytes: {0}")] TxDecodingError(prost::DecodeError), + #[error("Error deserializing transaction field bytes: {0}")] + TxDeserializingError(std::io::Error), #[error("Error decoding an DkgGossipMessage from bytes: {0}")] DkgDecodingError(prost::DecodeError), #[error("Dkg is empty")] @@ -38,416 +54,1077 @@ pub enum Error { pub type Result = std::result::Result; -/// This can be used to sign an arbitrary tx. The signature is produced and -/// verified on the tx data concatenated with the tx code, however the tx code -/// itself is not part of this structure. -/// -/// Because the signature is not checked by the ledger, we don't inline it into -/// the `Tx` type directly. Instead, the signature is attached to the `tx.data`, -/// which can then be checked by a validity predicate wasm. -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] -pub struct SignedTxData { - /// The original tx data bytes, if any - pub data: Option>, - /// The signature is produced on the tx data concatenated with the tx code - /// and the timestamp. - pub sig: common::Signature, -} - -/// A generic signed data wrapper for Borsh encode-able data. +/// A section representing transaction data #[derive( - Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, )] -pub struct Signed { - /// Arbitrary data to be signed - pub data: T, - /// The signature of the data - pub sig: common::Signature, +pub struct Data { + pub salt: [u8; 8], + pub data: Vec, } -impl PartialEq for Signed -where - T: BorshSerialize + BorshDeserialize + PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.data == other.data && self.sig == other.sig +impl Data { + /// Make a new data section with the given bytes + pub fn new(data: Vec) -> Self { + Self { + salt: DateTimeUtc::now().0.timestamp_millis().to_le_bytes(), + data, + } + } + + /// Hash this data section + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec().expect("unable to serialize data section"), + ); + hasher } } -impl Eq for Signed where - T: BorshSerialize + BorshDeserialize + Eq + PartialEq -{ +/// Error representing the case where the supplied code has incorrect hash +pub struct CommitmentError; + +/// Represents either some code bytes or their SHA-256 hash +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub enum Commitment { + /// Result of applying hash function to bytes + Hash(crate::types::hash::Hash), + /// Result of applying identity function to bytes + Id(Vec), } -impl Hash for Signed -where - T: BorshSerialize + BorshDeserialize + Hash, -{ - fn hash(&self, state: &mut H) { - self.data.hash(state); - self.sig.hash(state); +impl Commitment { + /// Substitute bytes with their SHA-256 hash + pub fn contract(&mut self) { + if let Self::Id(code) = self { + *self = Self::Hash(hash_tx(code)); + } + } + + /// Substitute a code hash with the supplied bytes if the hashes are + /// consistent, otherwise return an error + pub fn expand( + &mut self, + code: Vec, + ) -> std::result::Result<(), CommitmentError> { + match self { + Self::Id(c) if *c == code => Ok(()), + Self::Hash(hash) if *hash == hash_tx(&code) => { + *self = Self::Id(code); + Ok(()) + } + _ => Err(CommitmentError), + } + } + + /// Return the contained hash commitment + pub fn hash(&self) -> crate::types::hash::Hash { + match self { + Self::Id(code) => hash_tx(code), + Self::Hash(hash) => *hash, + } + } + + /// Return the result of applying identity function if there is any + pub fn id(&self) -> Option> { + if let Self::Id(code) = self { + Some(code.clone()) + } else { + None + } + } +} + +/// A section representing transaction code +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct Code { + /// Additional random data + pub salt: [u8; 8], + /// Actual transaction code + pub code: Commitment, +} + +impl Code { + /// Make a new code section with the given bytes + pub fn new(code: Vec) -> Self { + Self { + salt: DateTimeUtc::now().0.timestamp_millis().to_le_bytes(), + code: Commitment::Id(code), + } + } + + /// Make a new code section with the given hash + pub fn from_hash(hash: crate::types::hash::Hash) -> Self { + Self { + salt: DateTimeUtc::now().0.timestamp_millis().to_le_bytes(), + code: Commitment::Hash(hash), + } + } + + /// Hash this code section + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update(self.salt); + hasher.update(self.code.hash()); + hasher + } +} + +/// A section representing the signature over another section +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct Signature { + /// Additional random data + salt: [u8; 8], + /// The hash of the section being signed + target: crate::types::hash::Hash, + /// The signature over the above has + pub signature: common::Signature, + /// The public key to verrify the above siggnature + pub_key: common::PublicKey, +} + +impl Signature { + /// Sign the given section hash with the given key and return a section + pub fn new( + target: &crate::types::hash::Hash, + sec_key: &common::SecretKey, + ) -> Self { + Self { + salt: DateTimeUtc::now().0.timestamp_millis().to_le_bytes(), + target: *target, + signature: common::SigScheme::sign(sec_key, target), + pub_key: sec_key.ref_to(), + } + } + + /// Hash this signature section + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec() + .expect("unable to serialize signature section"), + ); + hasher + } +} + +/// Represents a section obtained by encrypting another section +#[derive(Clone, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "ferveo-tpke", serde(from = "SerializedCiphertext"))] +#[cfg_attr(feature = "ferveo-tpke", serde(into = "SerializedCiphertext"))] +#[cfg_attr( + not(feature = "ferveo-tpke"), + derive(BorshSerialize, BorshDeserialize, BorshSchema) +)] +pub struct Ciphertext { + /// The ciphertext corresponding to the original section serialization + #[cfg(feature = "ferveo-tpke")] + pub ciphertext: tpke::Ciphertext, + /// Ciphertext representation when ferveo not available + #[cfg(not(feature = "ferveo-tpke"))] + pub opaque: Vec, +} + +impl Ciphertext { + /// Make a ciphertext section based on the given sections. Note that this + /// encryption is not idempotent + #[cfg(feature = "ferveo-tpke")] + pub fn new(sections: Vec
, pubkey: &EncryptionKey) -> Self { + let mut rng = rand::thread_rng(); + let bytes = + sections.try_to_vec().expect("unable to serialize sections"); + Self { + ciphertext: tpke::encrypt(&bytes, pubkey.0, &mut rng), + } + } + + /// Decrypt this ciphertext back to the original plaintext sections. + #[cfg(feature = "ferveo-tpke")] + pub fn decrypt( + &self, + privkey: ::G2Affine, + ) -> std::io::Result> { + let bytes = tpke::decrypt(&self.ciphertext, privkey); + Vec::
::try_from_slice(&bytes) + } + + /// Get the hash of this ciphertext section. This operation is done in such + /// a way it matches the hash of the type pun + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec().expect("unable to serialize decrypted tx"), + ); + hasher + } +} + +#[cfg(feature = "ferveo-tpke")] +impl borsh::ser::BorshSerialize for Ciphertext { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + use ark_serialize::CanonicalSerialize; + let tpke::Ciphertext { + nonce, + ciphertext, + auth_tag, + } = &self.ciphertext; + // Serialize the nonce into bytes + let mut nonce_buffer = Vec::::new(); + nonce.serialize(&mut nonce_buffer).map_err(|err| { + std::io::Error::new(std::io::ErrorKind::InvalidData, err) + })?; + // serialize the auth_tag to bytes + let mut tag_buffer = Vec::::new(); + auth_tag.serialize(&mut tag_buffer).map_err(|err| { + std::io::Error::new(std::io::ErrorKind::InvalidData, err) + })?; + let mut payload = Vec::new(); + // serialize the three byte arrays + BorshSerialize::serialize( + &(nonce_buffer, ciphertext, tag_buffer), + &mut payload, + )?; + // now serialize the ciphertext payload with length + BorshSerialize::serialize(&payload, writer) + } +} + +#[cfg(feature = "ferveo-tpke")] +impl borsh::BorshDeserialize for Ciphertext { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + type VecTuple = (u32, Vec, Vec, Vec); + let (_length, nonce, ciphertext, auth_tag): VecTuple = + BorshDeserialize::deserialize(buf)?; + Ok(Self { + ciphertext: tpke::Ciphertext { + nonce: ark_serialize::CanonicalDeserialize::deserialize( + &*nonce, + ) + .map_err(|err| { + std::io::Error::new(std::io::ErrorKind::InvalidData, err) + })?, + ciphertext, + auth_tag: ark_serialize::CanonicalDeserialize::deserialize( + &*auth_tag, + ) + .map_err(|err| { + std::io::Error::new(std::io::ErrorKind::InvalidData, err) + })?, + }, + }) + } +} + +#[cfg(feature = "ferveo-tpke")] +impl borsh::BorshSchema for Ciphertext { + fn add_definitions_recursively( + definitions: &mut std::collections::HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + // Encoded as `(Vec, Vec, Vec)` + let elements = "u8".into(); + let definition = borsh::schema::Definition::Sequence { elements }; + definitions.insert("Vec".into(), definition); + let elements = + vec!["Vec".into(), "Vec".into(), "Vec".into()]; + let definition = borsh::schema::Definition::Tuple { elements }; + definitions.insert(Self::declaration(), definition); + } + + fn declaration() -> borsh::schema::Declaration { + "Ciphertext".into() + } +} + +/// A helper struct for serializing EncryptedTx structs +/// as an opaque blob +#[cfg(feature = "ferveo-tpke")] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(transparent)] +struct SerializedCiphertext { + payload: Vec, +} + +#[cfg(feature = "ferveo-tpke")] +impl From for SerializedCiphertext { + fn from(tx: Ciphertext) -> Self { + SerializedCiphertext { + payload: tx + .try_to_vec() + .expect("Unable to serialize encrypted transaction"), + } + } +} + +#[cfg(feature = "ferveo-tpke")] +impl From for Ciphertext { + fn from(ser: SerializedCiphertext) -> Self { + BorshDeserialize::deserialize(&mut ser.payload.as_ref()) + .expect("Unable to deserialize encrypted transactions") + } +} + +#[derive(serde::Serialize, serde::Deserialize)] +struct TransactionSerde(Vec); + +impl From> for TransactionSerde { + fn from(tx: Vec) -> Self { + Self(tx) + } +} + +impl From for Vec { + fn from(tx: TransactionSerde) -> Vec { + tx.0 } } -impl PartialOrd for Signed +fn borsh_serde( + obj: &impl BorshSerialize, + ser: S, +) -> std::result::Result where - T: BorshSerialize + BorshDeserialize + PartialOrd, + S: serde::Serializer, + T: From>, + T: serde::Serialize, { - fn partial_cmp(&self, other: &Self) -> Option { - self.data.partial_cmp(&other.data) - } + Into::::into(obj.try_to_vec().unwrap()).serialize(ser) } -impl Signed +fn serde_borsh<'de, T, S, U>(ser: S) -> std::result::Result where - T: BorshSerialize + BorshDeserialize, + S: serde::Deserializer<'de>, + T: Into>, + T: serde::Deserialize<'de>, + U: BorshDeserialize, { - /// Initialize a new signed data. - pub fn new(keypair: &common::SecretKey, data: T) -> Self { - let to_sign = data - .try_to_vec() - .expect("Encoding data for signing shouldn't fail"); - let sig = common::SigScheme::sign(keypair, to_sign); - Self { data, sig } - } - - /// Verify that the data has been signed by the secret key - /// counterpart of the given public key. - pub fn verify( - &self, - pk: &common::PublicKey, - ) -> std::result::Result<(), VerifySigError> { - let bytes = self - .data - .try_to_vec() - .expect("Encoding data for verifying signature shouldn't fail"); - common::SigScheme::verify_signature_raw(pk, &bytes, &self.sig) + BorshDeserialize::try_from_slice(&Into::>::into(T::deserialize( + ser, + )?)) + .map_err(S::Error::custom) +} + +/// A structure to facilitate Serde (de)serializations of Builders +#[derive(serde::Serialize, serde::Deserialize)] +struct BuilderSerde(Vec); + +impl From> for BuilderSerde { + fn from(tx: Vec) -> Self { + Self(tx) } } -/// A Tx with its code replaced by a hash salted with the Borsh -/// serialized timestamp of the transaction. This structure will almost -/// certainly be smaller than a Tx, yet in the usual cases it contains -/// enough information to confirm that the Tx is as intended and make a -/// non-malleable signature. +impl From for Vec { + fn from(tx: BuilderSerde) -> Vec { + tx.0 + } +} + +/// A structure to facilitate Serde (de)serializations of SaplingMetadata +#[derive(serde::Serialize, serde::Deserialize)] +pub struct SaplingMetadataSerde(Vec); + +impl From> for SaplingMetadataSerde { + fn from(tx: Vec) -> Self { + Self(tx) + } +} + +impl From for Vec { + fn from(tx: SaplingMetadataSerde) -> Vec { + tx.0 + } +} + +/// A section providing the auxiliary inputs used to construct a MASP +/// transaction #[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, + Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, )] -pub struct SigningTx { - pub code_hash: [u8; 32], - pub data: Option>, - pub timestamp: DateTimeUtc, +pub struct MaspBuilder { + /// The MASP transaction that this section witnesses + pub target: crate::types::hash::Hash, + /// The decoded set of asset types used by the transaction. Useful for + /// offline wallets trying to display AssetTypes. + pub asset_types: HashSet<(Address, Epoch)>, + /// Track how Info objects map to descriptors and outputs + #[serde( + serialize_with = "borsh_serde::", + deserialize_with = "serde_borsh::" + )] + pub metadata: SaplingMetadata, + /// The data that was used to construct the target transaction + #[serde( + serialize_with = "borsh_serde::", + deserialize_with = "serde_borsh::" + )] + pub builder: Builder<(), (), ExtendedFullViewingKey, ()>, +} + +impl MaspBuilder { + /// Get the hash of this ciphertext section. This operation is done in such + /// a way it matches the hash of the type pun + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec().expect("unable to serialize MASP builder"), + ); + hasher + } +} + +impl borsh::BorshSchema for MaspBuilder { + fn add_definitions_recursively( + _definitions: &mut std::collections::HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + } + + fn declaration() -> borsh::schema::Declaration { + "Builder".into() + } +} + +/// A section of a transaction. Carries an independent piece of information +/// necessary for the processing of a transaction. +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub enum Section { + /// Transaction data that needs to be sent to hardware wallets + Data(Data), + /// Transaction data that does not need to be sent to hardware wallets + ExtraData(Code), + /// Transaction code. Sending to hardware wallets optional + Code(Code), + /// A transaction signature. Often produced by hardware wallets + Signature(Signature), + /// Ciphertext obtained by encrypting arbitrary transaction sections + Ciphertext(Ciphertext), + /// Embedded MASP transaction section + #[serde( + serialize_with = "borsh_serde::", + deserialize_with = "serde_borsh::" + )] + MaspTx(Transaction), + /// A section providing the auxiliary inputs used to construct a MASP + /// transaction. Only send to wallet, never send to protocol. + MaspBuilder(MaspBuilder), +} + +impl Section { + /// Hash this section. Section hashes are useful for signatures and also for + /// allowing transaction sections to cross reference. + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + // Get the index corresponding to this variant + let discriminant = + self.try_to_vec().expect("sections should serialize")[0]; + // Use Borsh's discriminant in the Section's hash + hasher.update([discriminant]); + match self { + Self::Data(data) => data.hash(hasher), + Self::ExtraData(extra) => extra.hash(hasher), + Self::Code(code) => code.hash(hasher), + Self::Signature(sig) => sig.hash(hasher), + Self::Ciphertext(ct) => ct.hash(hasher), + Self::MaspBuilder(mb) => mb.hash(hasher), + Self::MaspTx(tx) => { + hasher.update(tx.txid().as_ref()); + hasher + } + } + } + + /// Sign over the hash of this section and return a signature section that + /// can be added to the container transaction + pub fn sign(&self, sec_key: &common::SecretKey) -> Signature { + let mut hasher = Sha256::new(); + self.hash(&mut hasher); + Signature::new( + &crate::types::hash::Hash(hasher.finalize().into()), + sec_key, + ) + } + + /// Extract the data from this section if possible + pub fn data(&self) -> Option { + if let Self::Data(data) = self { + Some(data.clone()) + } else { + None + } + } + + /// Extract the extra data from this section if possible + pub fn extra_data_sec(&self) -> Option { + if let Self::ExtraData(data) = self { + Some(data.clone()) + } else { + None + } + } + + /// Extract the extra data from this section if possible + pub fn extra_data(&self) -> Option> { + if let Self::ExtraData(data) = self { + data.code.id() + } else { + None + } + } + + /// Extract the code from this section is possible + pub fn code_sec(&self) -> Option { + if let Self::Code(data) = self { + Some(data.clone()) + } else { + None + } + } + + /// Extract the code from this section is possible + pub fn code(&self) -> Option> { + if let Self::Code(data) = self { + data.code.id() + } else { + None + } + } + + /// Extract the signature from this section if possible + pub fn signature(&self) -> Option { + if let Self::Signature(data) = self { + Some(data.clone()) + } else { + None + } + } + + /// Extract the ciphertext from this section if possible + pub fn ciphertext(&self) -> Option { + if let Self::Ciphertext(data) = self { + Some(data.clone()) + } else { + None + } + } + + /// Extract the MASP transaction from this section if possible + pub fn masp_tx(&self) -> Option { + if let Self::MaspTx(data) = self { + Some(data.clone()) + } else { + None + } + } + + /// Extract the MASP builder from this section if possible + pub fn masp_builder(&self) -> Option { + if let Self::MaspBuilder(data) = self { + Some(data.clone()) + } else { + None + } + } +} + +/// A Namada transaction header indicating where transaction subcomponents can +/// be found +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct Header { + /// The chain which this transaction is being submitted to pub chain_id: ChainId, + /// The time at which this transaction expires pub expiration: Option, + /// A transaction timestamp + pub timestamp: DateTimeUtc, + /// The SHA-256 hash of the transaction's code section + pub code_hash: crate::types::hash::Hash, + /// The SHA-256 hash of the transaction's data section + pub data_hash: crate::types::hash::Hash, + /// The type of this transaction + pub tx_type: TxType, } -impl SigningTx { - pub fn hash(&self) -> [u8; 32] { - let timestamp = Some(self.timestamp.into()); - let expiration = self.expiration.map(|e| e.into()); - let mut bytes = vec![]; - types::Tx { - code_or_hash: self.code_hash.to_vec(), - data: self.data.clone(), - timestamp, - chain_id: self.chain_id.as_str().to_owned(), - expiration, - } - .encode(&mut bytes) - .expect("encoding a transaction failed"); - hash_tx(&bytes).0 - } - - /// Sign a transaction using [`SignedTxData`]. - pub fn sign(self, keypair: &common::SecretKey) -> Self { - let to_sign = self.hash(); - let sig = common::SigScheme::sign(keypair, to_sign); - let signed = SignedTxData { - data: self.data, - sig, - } - .try_to_vec() - .expect("Encoding transaction data shouldn't fail"); - SigningTx { - code_hash: self.code_hash, - data: Some(signed), - timestamp: self.timestamp, - chain_id: self.chain_id, - expiration: self.expiration, - } - } - - /// Verify that the transaction has been signed by the secret key - /// counterpart of the given public key. - pub fn verify_sig( - &self, - pk: &common::PublicKey, - sig: &common::Signature, - ) -> std::result::Result<(), VerifySigError> { - // Try to get the transaction data from decoded `SignedTxData` - let tx_data = self.data.clone().ok_or(VerifySigError::MissingData)?; - let signed_tx_data = SignedTxData::try_from_slice(&tx_data[..]) - .expect("Decoding transaction data shouldn't fail"); - let data = signed_tx_data.data; - let tx = SigningTx { - code_hash: self.code_hash, - data, - timestamp: self.timestamp, - chain_id: self.chain_id.clone(), - expiration: self.expiration, - }; - let signed_data = tx.hash(); - common::SigScheme::verify_signature_raw(pk, &signed_data, sig) - } - - /// Expand this reduced Tx using the supplied code only if the the code - /// hashes to the stored code hash - pub fn expand(self, code: Vec) -> Option { - if hash_tx(&code).0 == self.code_hash { - Some(Tx { - code_or_hash: code, - data: self.data, - timestamp: self.timestamp, - chain_id: self.chain_id, - expiration: self.expiration, - }) +impl Header { + /// Make a new header of the given transaction type + pub fn new(tx_type: TxType) -> Self { + Self { + tx_type, + chain_id: ChainId::default(), + expiration: None, + timestamp: DateTimeUtc::now(), + code_hash: crate::types::hash::Hash::default(), + data_hash: crate::types::hash::Hash::default(), + } + } + + /// Get the hash of this transaction header. + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec() + .expect("unable to serialize transaction header"), + ); + hasher + } + + /// Get the wrapper header if it is present + pub fn wrapper(&self) -> Option { + if let TxType::Wrapper(wrapper) = &self.tx_type { + Some(*wrapper.clone()) + } else { + None + } + } + + /// Get the decrypted header if it is present + pub fn decrypted(&self) -> Option { + if let TxType::Decrypted(decrypted) = &self.tx_type { + Some(decrypted.clone()) } else { None } } -} -impl From for SigningTx { - fn from(tx: Tx) -> SigningTx { - SigningTx { - code_hash: hash_tx(&tx.code_or_hash).0, - data: tx.data, - timestamp: tx.timestamp, - chain_id: tx.chain_id, - expiration: tx.expiration, + #[cfg(feature = "ferveo-tpke")] + /// Get the protocol header if it is present + pub fn protocol(&self) -> Option { + if let TxType::Protocol(protocol) = &self.tx_type { + Some(*protocol.clone()) + } else { + None } } } -/// A SigningTx but with the full code embedded. This structure will almost -/// certainly be bigger than SigningTxs and contains enough information to -/// execute the transaction. +/// Errors relating to decrypting a wrapper tx and its +/// encrypted payload from a Tx type +#[allow(missing_docs)] +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum TxError { + #[error("{0}")] + Unsigned(String), + #[error("{0}")] + SigError(String), + #[error("Failed to deserialize Tx: {0}")] + Deserialization(String), +} + +/// A Namada transaction is represented as a header followed by a series of +/// seections providing additional details. #[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, )] pub struct Tx { - pub code_or_hash: Vec, - pub data: Option>, - pub timestamp: DateTimeUtc, - pub chain_id: ChainId, - pub expiration: Option, + /// Type indicating how to process transaction + pub header: Header, + /// Additional details necessary to process transaction + pub sections: Vec
, } +/// Deserialize Tx from protobufs impl TryFrom<&[u8]> for Tx { type Error = Error; fn try_from(tx_bytes: &[u8]) -> Result { let tx = types::Tx::decode(tx_bytes).map_err(Error::TxDecodingError)?; - let timestamp = match tx.timestamp { - Some(t) => t.try_into().map_err(Error::InvalidTimestamp)?, - None => return Err(Error::NoTimestampError), - }; - let chain_id = ChainId(tx.chain_id); - let expiration = match tx.expiration { - Some(e) => Some(e.try_into().map_err(Error::InvalidTimestamp)?), - None => None, - }; - - Ok(Tx { - code_or_hash: tx.code_or_hash, - data: tx.data, - timestamp, - chain_id, - expiration, - }) + BorshDeserialize::try_from_slice(&tx.data) + .map_err(Error::TxDeserializingError) } } -impl From for types::Tx { - fn from(tx: Tx) -> Self { - let timestamp = Some(tx.timestamp.into()); - let expiration = tx.expiration.map(|e| e.into()); - - types::Tx { - code_or_hash: tx.code_or_hash, - data: tx.data, - timestamp, - chain_id: tx.chain_id.as_str().to_owned(), - expiration, +impl Tx { + /// Create a transaction of the given type + pub fn new(header: TxType) -> Self { + Tx { + header: Header::new(header), + sections: vec![], } } -} -#[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] -impl From for ResponseDeliverTx { - #[cfg(not(feature = "ferveo-tpke"))] - fn from(_tx: Tx) -> ResponseDeliverTx { - Default::default() + /// Get the transaction header + pub fn header(&self) -> Header { + self.header.clone() } - /// Annotate the Tx with meta-data based on its contents - #[cfg(feature = "ferveo-tpke")] - fn from(tx: Tx) -> ResponseDeliverTx { - use crate::tendermint_proto::abci::{Event, EventAttribute}; + /// Get the transaction header hash + pub fn header_hash(&self) -> crate::types::hash::Hash { + crate::types::hash::Hash( + self.header.hash(&mut Sha256::new()).finalize_reset().into(), + ) + } - #[cfg(feature = "ABCI")] - fn encode_str(x: &str) -> Vec { - x.as_bytes().to_vec() - } - #[cfg(not(feature = "ABCI"))] - fn encode_str(x: &str) -> String { - x.to_string() - } - #[cfg(feature = "ABCI")] - fn encode_string(x: String) -> Vec { - x.into_bytes() - } - #[cfg(not(feature = "ABCI"))] - fn encode_string(x: String) -> String { - x - } - match process_tx(tx) { - Ok(TxType::Decrypted(DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: _, - })) => { - let empty_vec = vec![]; - let tx_data = tx.data.as_ref().unwrap_or(&empty_vec); - let signed = - if let Ok(signed) = SignedTxData::try_from_slice(tx_data) { - signed - } else { - return Default::default(); - }; - if let Ok(transfer) = Transfer::try_from_slice( - signed.data.as_ref().unwrap_or(&empty_vec), - ) { - let events = vec![Event { - r#type: "transfer".to_string(), - attributes: vec![ - EventAttribute { - key: encode_str("source"), - value: encode_string(transfer.source.encode()), - index: true, - }, - EventAttribute { - key: encode_str("target"), - value: encode_string(transfer.target.encode()), - index: true, - }, - EventAttribute { - key: encode_str("token"), - value: encode_string(transfer.token.encode()), - index: true, - }, - EventAttribute { - key: encode_str("amount"), - value: encode_string( - transfer.amount.to_string(), - ), - index: true, - }, - ], - }]; - ResponseDeliverTx { - events, - info: "Transfer tx".to_string(), - ..Default::default() - } - } else { - Default::default() - } + /// Update the header whilst maintaining existing cross-references + pub fn update_header(&mut self, tx_type: TxType) -> &mut Self { + self.header.tx_type = tx_type; + self + } + + /// Get the transaction section with the given hash + pub fn get_section( + &self, + hash: &crate::types::hash::Hash, + ) -> Option<&Section> { + for section in &self.sections { + let sechash = crate::types::hash::Hash( + section.hash(&mut Sha256::new()).finalize_reset().into(), + ); + if sechash == *hash { + return Some(section); } - _ => Default::default(), } + None } -} -impl Tx { - /// Create a new transaction. `code_or_hash` should be set as the wasm code - /// bytes or hash. - pub fn new( - code_or_hash: Vec, - data: Option>, - chain_id: ChainId, - expiration: Option, - ) -> Self { - Tx { - code_or_hash, - data, - timestamp: DateTimeUtc::now(), - chain_id, - expiration, + /// Add a new section to the transaction + pub fn add_section(&mut self, section: Section) -> &mut Section { + self.sections.push(section); + self.sections.last_mut().unwrap() + } + + /// Get the hash of this transaction's code from the heeader + pub fn code_sechash(&self) -> &crate::types::hash::Hash { + &self.header.code_hash + } + + /// Set the transaction code hash stored in the header + pub fn set_code_sechash(&mut self, hash: crate::types::hash::Hash) { + self.header.code_hash = hash + } + + /// Get the code designated by the transaction code hash in the header + pub fn code(&self) -> Option> { + match self.get_section(self.code_sechash()) { + Some(Section::Code(section)) => section.code.id(), + _ => None, } } + /// Add the given code to the transaction and set code hash in the header + pub fn set_code(&mut self, code: Code) -> &mut Section { + let sec = Section::Code(code); + let mut hasher = Sha256::new(); + sec.hash(&mut hasher); + let hash = crate::types::hash::Hash(hasher.finalize().into()); + self.set_code_sechash(hash); + self.sections.push(sec); + self.sections.last_mut().unwrap() + } + + /// Get the transaction data hash stored in the header + pub fn data_sechash(&self) -> &crate::types::hash::Hash { + &self.header.data_hash + } + + /// Set the transaction data hash stored in the header + pub fn set_data_sechash(&mut self, hash: crate::types::hash::Hash) { + self.header.data_hash = hash + } + + /// Add the given code to the transaction and set the hash in the header + pub fn set_data(&mut self, data: Data) -> &mut Section { + let sec = Section::Data(data); + let mut hasher = Sha256::new(); + sec.hash(&mut hasher); + let hash = crate::types::hash::Hash(hasher.finalize().into()); + self.set_data_sechash(hash); + self.sections.push(sec); + self.sections.last_mut().unwrap() + } + + /// Get the data designated by the transaction data hash in the header + pub fn data(&self) -> Option> { + match self.get_section(self.data_sechash()) { + Some(Section::Data(data)) => Some(data.data.clone()), + _ => None, + } + } + + /// Convert this transaction into protobufs pub fn to_bytes(&self) -> Vec { let mut bytes = vec![]; - let tx: types::Tx = self.clone().into(); + let tx: types::Tx = types::Tx { + data: self.try_to_vec().expect("encoding a transaction failed"), + }; tx.encode(&mut bytes) .expect("encoding a transaction failed"); bytes } - pub fn hash(&self) -> [u8; 32] { - SigningTx::from(self.clone()).hash() - } - - pub fn unsigned_hash(&self) -> [u8; 32] { - match self.data { - Some(ref data) => { - match SignedTxData::try_from_slice(data) { - Ok(signed_data) => { - // Reconstruct unsigned tx - let unsigned_tx = Tx { - code_or_hash: self.code_or_hash.clone(), - data: signed_data.data, - timestamp: self.timestamp, - chain_id: self.chain_id.clone(), - expiration: self.expiration, - }; - unsigned_tx.hash() - } - Err(_) => { - // Unsigned tx - self.hash() - } + /// Verify that the section with the given hash has been signed by the given + /// public key + pub fn verify_signature( + &self, + pk: &common::PublicKey, + hash: &crate::types::hash::Hash, + ) -> std::result::Result<(), VerifySigError> { + for section in &self.sections { + if let Section::Signature(sig_sec) = section { + if sig_sec.pub_key == *pk && sig_sec.target == *hash { + return common::SigScheme::verify_signature_raw( + pk, + &hash.0, + &sig_sec.signature, + ); } } - None => { - // Unsigned tx - self.hash() + } + Err(VerifySigError::MissingData) + } + + /// Validate any and all ciphertexts stored in this transaction + #[cfg(feature = "ferveo-tpke")] + pub fn validate_ciphertext(&self) -> bool { + let mut valid = true; + for section in &self.sections { + if let Section::Ciphertext(ct) = section { + valid = valid && ct.ciphertext.check( + &::G1Prepared::from( + -::G1Affine::prime_subgroup_generator(), + ) + ); + } + } + valid + } + + /// Decrypt any and all ciphertexts stored in this transaction use the + /// given decryption key + #[cfg(feature = "ferveo-tpke")] + pub fn decrypt( + &mut self, + privkey: ::G2Affine, + ) -> std::result::Result<(), WrapperTxErr> { + // Iterate backwrds to sidestep the effects of deletion on indexing + for i in (0..self.sections.len()).rev() { + if let Section::Ciphertext(ct) = &self.sections[i] { + // Add all the deecrypted sections + self.sections.extend( + ct.decrypt(privkey).map_err(|_| WrapperTxErr::InvalidTx)?, + ); + // Remove the original ciphertext + self.sections.remove(i); } } + self.data().ok_or(WrapperTxErr::DecryptedHash)?; + self.get_section(self.code_sechash()) + .ok_or(WrapperTxErr::DecryptedHash)?; + Ok(()) } - pub fn code_hash(&self) -> [u8; 32] { - SigningTx::from(self.clone()).code_hash + /// Encrypt all sections in this transaction other than the header and + /// signatures over it + #[cfg(feature = "ferveo-tpke")] + pub fn encrypt(&mut self, pubkey: &EncryptionKey) { + let header_hash = self.header_hash(); + let mut plaintexts = vec![]; + // Iterate backwrds to sidestep the effects of deletion on indexing + for i in (0..self.sections.len()).rev() { + match &self.sections[i] { + Section::Signature(sig) if sig.target == header_hash => {} + // Add eligible section to the list of sections to encrypt + _ => plaintexts.push(self.sections.remove(i)), + } + } + // Encrypt all eligible sections in one go + self.sections + .push(Section::Ciphertext(Ciphertext::new(plaintexts, pubkey))); } - /// Sign a transaction using [`SignedTxData`]. - pub fn sign(self, keypair: &common::SecretKey) -> Self { - let code = self.code_or_hash.clone(); - SigningTx::from(self) - .sign(keypair) - .expand(code) - .expect("code hashes to unexpected value") + /// Determines the type of the input Tx + /// + /// If it is a raw Tx, signed or not, the Tx is + /// returned unchanged inside an enum variant stating its type. + /// + /// If it is a decrypted tx, signing it adds no security so we + /// extract the signed data without checking the signature (if it + /// is signed) or return as is. Either way, it is returned in + /// an enum variant stating its type. + /// + /// If it is a WrapperTx, we extract the signed data of + /// the Tx and verify it is of the appropriate form. This means + /// 1. The wrapper tx is indeed signed + /// 2. The signature is valid + pub fn validate_header(&self) -> std::result::Result<(), TxError> { + match &self.header.tx_type { + // verify signature and extract signed data + TxType::Wrapper(wrapper) => { + self.verify_signature(&wrapper.pk, &self.header_hash()) + .map_err(|err| { + TxError::SigError(format!( + "WrapperTx signature verification failed: {}", + err + )) + })?; + Ok(()) + } + // verify signature and extract signed data + #[cfg(feature = "ferveo-tpke")] + TxType::Protocol(protocol) => { + self.verify_signature(&protocol.pk, &self.header_hash()) + .map_err(|err| { + TxError::SigError(format!( + "ProtocolTx signature verification failed: {}", + err + )) + })?; + Ok(()) + } + // we extract the signed data, but don't check the signature + TxType::Decrypted(_) => Ok(()), + // return as is + TxType::Raw => Ok(()), + } } - /// Verify that the transaction has been signed by the secret key - /// counterpart of the given public key. - pub fn verify_sig( - &self, - pk: &common::PublicKey, - sig: &common::Signature, - ) -> std::result::Result<(), VerifySigError> { - SigningTx::from(self.clone()).verify_sig(pk, sig) + /// Filter out all the sections that must not be submitted to the protocol + /// and return them. + pub fn protocol_filter(&mut self) -> Vec
{ + let mut filtered = Vec::new(); + for i in (0..self.sections.len()).rev() { + if let Section::MaspBuilder(_) = self.sections[i] { + // MASP Builders containin extended full viewing keys amongst + // other private information and must be removed prior to + // submission to protocol + filtered.push(self.sections.remove(i)); + } + } + filtered + } + + /// Filter out all the sections that need not be sent to the hardware wallet + /// and return them + pub fn wallet_filter(&mut self) -> Vec
{ + let mut filtered = Vec::new(); + for i in (0..self.sections.len()).rev() { + match &mut self.sections[i] { + // This section is known to be large and can be contracted + Section::Code(section) => { + filtered.push(Section::Code(section.clone())); + section.code.contract(); + } + // This section is known to be large and can be contracted + Section::ExtraData(section) => { + filtered.push(Section::ExtraData(section.clone())); + section.code.contract(); + } + // Everything else is fine to add + _ => {} + } + } + filtered + } +} + +#[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] +impl From for ResponseDeliverTx { + #[cfg(not(feature = "ferveo-tpke"))] + fn from(_tx: Tx) -> ResponseDeliverTx { + Default::default() + } + + /// Annotate the Tx with meta-data based on its contents + #[cfg(feature = "ferveo-tpke")] + fn from(tx: Tx) -> ResponseDeliverTx { + use crate::tendermint_proto::abci::{Event, EventAttribute}; + + // If data cannot be extracteed, then attach no events + let tx_data = if let Some(data) = tx.data() { + data + } else { + return Default::default(); + }; + // If the data is not a Transfer, then attach no events + let transfer = if let Ok(transfer) = Transfer::try_from_slice(&tx_data) + { + transfer + } else { + return Default::default(); + }; + // Otherwise attach all Transfer events + let events = vec![Event { + r#type: "transfer".to_string(), + attributes: vec![ + EventAttribute { + key: "source".to_string(), + value: transfer.source.encode(), + index: true, + }, + EventAttribute { + key: "target".to_string(), + value: transfer.target.encode(), + index: true, + }, + EventAttribute { + key: "token".to_string(), + value: transfer.token.encode(), + index: true, + }, + EventAttribute { + key: "amount".to_string(), + value: transfer.amount.to_string(), + index: true, + }, + ], + }]; + ResponseDeliverTx { + events, + info: "Transfer tx".to_string(), + ..Default::default() + } } } @@ -529,34 +1206,6 @@ impl Dkg { mod tests { use super::*; - #[test] - fn test_tx() { - let code = "wasm code".as_bytes().to_owned(); - let data = "arbitrary data".as_bytes().to_owned(); - let chain_id = ChainId::default(); - let tx = - Tx::new(code.clone(), Some(data.clone()), chain_id.clone(), None); - - let bytes = tx.to_bytes(); - let tx_from_bytes = - Tx::try_from(bytes.as_ref()).expect("decoding failed"); - assert_eq!(tx_from_bytes, tx); - - let types_tx = types::Tx { - code_or_hash: code, - data: Some(data), - timestamp: None, - chain_id: chain_id.0, - expiration: None, - }; - let mut bytes = vec![]; - types_tx.encode(&mut bytes).expect("encoding failed"); - match Tx::try_from(bytes.as_ref()) { - Err(Error::NoTimestampError) => {} - _ => panic!("unexpected result"), - } - } - #[test] fn test_dkg_gossip_message() { let data = "arbitrary string".to_owned(); @@ -578,4 +1227,91 @@ mod tests { let dkg_from_types = Dkg::from(types_dkg); assert_eq!(dkg_from_types, dkg); } + + /// Test that encryption and decryption are inverses. + #[cfg(feature = "ferveo-tpke")] + #[test] + fn test_encrypt_decrypt() { + // The trivial public - private keypair + let pubkey = EncryptionKey(::G1Affine::prime_subgroup_generator()); + let privkey = ::G2Affine::prime_subgroup_generator(); + // generate encrypted payload + let plaintext = vec![Section::Data(Data::new( + "Super secret stuff".as_bytes().to_vec(), + ))]; + let encrypted = Ciphertext::new(plaintext.clone(), &pubkey); + // check that encryption doesn't do trivial things + assert_ne!( + encrypted.ciphertext.ciphertext, + plaintext.try_to_vec().expect("Test failed") + ); + // decrypt the payload and check we got original data back + let decrypted = encrypted.decrypt(privkey); + assert_eq!( + decrypted + .expect("Test failed") + .try_to_vec() + .expect("Test failed"), + plaintext.try_to_vec().expect("Test failed"), + ); + } + + /// Test that serializing and deserializing again via Borsh produces + /// original payload + #[cfg(feature = "ferveo-tpke")] + #[test] + fn test_encrypted_tx_round_trip_borsh() { + // The trivial public - private keypair + let pubkey = EncryptionKey(::G1Affine::prime_subgroup_generator()); + let privkey = ::G2Affine::prime_subgroup_generator(); + // generate encrypted payload + let plaintext = vec![Section::Data(Data::new( + "Super secret stuff".as_bytes().to_vec(), + ))]; + let encrypted = Ciphertext::new(plaintext.clone(), &pubkey); + // serialize via Borsh + let borsh = encrypted.try_to_vec().expect("Test failed"); + // deserialize again + let new_encrypted: Ciphertext = + BorshDeserialize::deserialize(&mut borsh.as_ref()) + .expect("Test failed"); + // check that decryption works as expected + let decrypted = new_encrypted.decrypt(privkey); + assert_eq!( + decrypted + .expect("Test failed") + .try_to_vec() + .expect("Test failed"), + plaintext.try_to_vec().expect("Test failed"), + ); + } + + /// Test that serializing and deserializing again via Serde produces + /// original payload + #[cfg(feature = "ferveo-tpke")] + #[test] + fn test_encrypted_tx_round_trip_serde() { + // The trivial public - private keypair + let pubkey = EncryptionKey(::G1Affine::prime_subgroup_generator()); + let privkey = ::G2Affine::prime_subgroup_generator(); + // generate encrypted payload + let plaintext = vec![Section::Data(Data::new( + "Super secret stuff".as_bytes().to_vec(), + ))]; + let encrypted = Ciphertext::new(plaintext.clone(), &pubkey); + // serialize via Serde + let js = serde_json::to_string(&encrypted).expect("Test failed"); + // deserialize it again + let new_encrypted: Ciphertext = + serde_json::from_str(&js).expect("Test failed"); + let decrypted = new_encrypted.decrypt(privkey); + // check that decryption works as expected + assert_eq!( + decrypted + .expect("Test failed") + .try_to_vec() + .expect("Test failed"), + plaintext.try_to_vec().expect("Test failed"), + ); + } } diff --git a/core/src/types/hash.rs b/core/src/types/hash.rs index 080826a415..db45d2ab58 100644 --- a/core/src/types/hash.rs +++ b/core/src/types/hash.rs @@ -31,6 +31,7 @@ pub type HashResult = std::result::Result; #[derive( Clone, + Copy, Debug, Default, PartialOrd, diff --git a/core/src/types/internal.rs b/core/src/types/internal.rs index d13d392381..a15efc8105 100644 --- a/core/src/types/internal.rs +++ b/core/src/types/internal.rs @@ -48,12 +48,14 @@ impl From for HostEnvResult { mod tx_queue { use borsh::{BorshDeserialize, BorshSerialize}; + use crate::proto::Tx; + /// A wrapper for `crate::types::transaction::WrapperTx` to conditionally /// add `has_valid_pow` flag for only used in testnets. #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] - pub struct WrapperTxInQueue { + pub struct TxInQueue { /// Wrapper tx - pub tx: crate::types::transaction::WrapperTx, + pub tx: Tx, #[cfg(not(feature = "mainnet"))] /// A PoW solution can be used to allow zero-fee testnet /// transactions. @@ -64,23 +66,21 @@ mod tx_queue { #[derive(Default, Debug, Clone, BorshDeserialize, BorshSerialize)] /// Wrapper txs to be decrypted in the next block proposal - pub struct TxQueue(std::collections::VecDeque); + pub struct TxQueue(std::collections::VecDeque); impl TxQueue { /// Add a new wrapper at the back of the queue - pub fn push(&mut self, wrapper: WrapperTxInQueue) { + pub fn push(&mut self, wrapper: TxInQueue) { self.0.push_back(wrapper); } /// Remove the wrapper at the head of the queue - pub fn pop(&mut self) -> Option { + pub fn pop(&mut self) -> Option { self.0.pop_front() } /// Get an iterator over the queue - pub fn iter( - &self, - ) -> impl std::iter::Iterator { + pub fn iter(&self) -> impl std::iter::Iterator { self.0.iter() } @@ -92,11 +92,11 @@ mod tx_queue { /// Get reference to the element at the given index. /// Returns [`None`] if index exceeds the queue lenght. - pub fn get(&self, index: usize) -> Option<&WrapperTxInQueue> { + pub fn get(&self, index: usize) -> Option<&TxInQueue> { self.0.get(index) } } } #[cfg(feature = "ferveo-tpke")] -pub use tx_queue::{TxQueue, WrapperTxInQueue}; +pub use tx_queue::{TxInQueue, TxQueue}; diff --git a/core/src/types/key/common.rs b/core/src/types/key/common.rs index b0da165bd9..8bc51afe4e 100644 --- a/core/src/types/key/common.rs +++ b/core/src/types/key/common.rs @@ -287,6 +287,13 @@ impl super::SigScheme for SigScheme { ); } + fn from_bytes(_seed: [u8; 32]) -> Self::SecretKey { + unimplemented!( + "Cannot generate common signing scheme. Must convert from \ + alternative scheme." + ); + } + fn sign(keypair: &SecretKey, data: impl AsRef<[u8]>) -> Self::Signature { match keypair { SecretKey::Ed25519(kp) => { diff --git a/core/src/types/key/ed25519.rs b/core/src/types/key/ed25519.rs index 052461de9a..9dcde9f3c0 100644 --- a/core/src/types/key/ed25519.rs +++ b/core/src/types/key/ed25519.rs @@ -336,6 +336,10 @@ impl super::SigScheme for SigScheme { SecretKey(Box::new(ed25519_consensus::SigningKey::new(csprng))) } + fn from_bytes(bytes: [u8; 32]) -> SecretKey { + SecretKey(Box::new(ed25519_consensus::SigningKey::from(bytes))) + } + fn sign(keypair: &SecretKey, data: impl AsRef<[u8]>) -> Self::Signature { Signature(keypair.0.sign(data.as_ref())) } diff --git a/core/src/types/key/mod.rs b/core/src/types/key/mod.rs index 157b0f4f5b..8d698d50ae 100644 --- a/core/src/types/key/mod.rs +++ b/core/src/types/key/mod.rs @@ -267,6 +267,8 @@ pub trait SigScheme: Eq + Ord + Debug + Serialize + Default { fn generate(csprng: &mut R) -> Self::SecretKey where R: CryptoRng + RngCore; + /// Instantiate a secret key from the bytes. + fn from_bytes(bytes: [u8; 32]) -> Self::SecretKey; /// Sign the data with a key. fn sign( keypair: &Self::SecretKey, diff --git a/core/src/types/key/secp256k1.rs b/core/src/types/key/secp256k1.rs index d901e46d25..1808ff7534 100644 --- a/core/src/types/key/secp256k1.rs +++ b/core/src/types/key/secp256k1.rs @@ -468,6 +468,13 @@ impl super::SigScheme for SigScheme { SecretKey(Box::new(libsecp256k1::SecretKey::random(csprng))) } + fn from_bytes(sk: [u8; 32]) -> SecretKey { + SecretKey(Box::new( + libsecp256k1::SecretKey::parse_slice(&sk) + .expect("Secret key parsing should not fail."), + )) + } + /// Sign the data with a key fn sign(keypair: &SecretKey, data: impl AsRef<[u8]>) -> Self::Signature { #[cfg(not(any(test, feature = "secp256k1-sign-verify")))] diff --git a/core/src/types/masp.rs b/core/src/types/masp.rs index c0ccb67d1e..5c2b3fb2d4 100644 --- a/core/src/types/masp.rs +++ b/core/src/types/masp.rs @@ -132,7 +132,7 @@ impl<'de> serde::Deserialize<'de> for ExtendedViewingKey { BorshSerialize, BorshDeserialize, )] -pub struct PaymentAddress(masp_primitives::primitives::PaymentAddress, bool); +pub struct PaymentAddress(masp_primitives::sapling::PaymentAddress, bool); impl PaymentAddress { /// Turn this PaymentAddress into a pinned/unpinned one @@ -157,14 +157,14 @@ impl PaymentAddress { } } -impl From for masp_primitives::primitives::PaymentAddress { +impl From for masp_primitives::sapling::PaymentAddress { fn from(addr: PaymentAddress) -> Self { addr.0 } } -impl From for PaymentAddress { - fn from(addr: masp_primitives::primitives::PaymentAddress) -> Self { +impl From for PaymentAddress { + fn from(addr: masp_primitives::sapling::PaymentAddress) -> Self { Self(addr, false) } } @@ -222,7 +222,7 @@ impl FromStr for PaymentAddress { }; let bytes: Vec = FromBase32::from_base32(&base32) .map_err(DecodeError::DecodeBase32)?; - masp_primitives::primitives::PaymentAddress::from_bytes( + masp_primitives::sapling::PaymentAddress::from_bytes( &bytes.try_into().map_err(addr_len_err)?, ) .ok_or_else(addr_data_err) @@ -366,6 +366,14 @@ impl TransferSource { _ => None, } } + + /// Get the contained Address, if any + pub fn address(&self) -> Option
{ + match self { + Self::Address(x) => Some(x.clone()), + _ => None, + } + } } impl Display for TransferSource { diff --git a/core/src/types/named_address.rs b/core/src/types/named_address.rs deleted file mode 100644 index ec26721471..0000000000 --- a/core/src/types/named_address.rs +++ /dev/null @@ -1,285 +0,0 @@ -//! This module is currently unused and not included in the module tree. -//! It implements named addresses as described in [Archived -//! page](docs/src/archive/domain-name-addresses.md). - -use std::collections::HashSet; -use std::fmt::{Debug, Display}; -use std::hash::Hash; -use std::iter::FromIterator; -use std::str::FromStr; -use std::string; - -use bech32::{self, FromBase32, ToBase32, Variant}; -use borsh::{BorshDeserialize, BorshSerialize}; -use sha2::{Digest, Sha256}; -use thiserror::Error; - -const MAX_RAW_ADDRESS_LEN: usize = 255; -const MIN_RAW_ADDRESS_LEN: usize = 3; -const MAX_LABEL_LEN: usize = 64; - -const HASH_LEN: usize = 64; -/// human-readable part of Bech32m encoded address -const ADDRESS_HRP: &str = "a"; -const ADDRESS_BECH32_VARIANT: bech32::Variant = Variant::Bech32m; - -#[derive(Error, Debug)] -pub enum Error { - #[error("Address must be at least {MIN_RAW_ADDRESS_LEN} characters long")] - AddressTooShort, - #[error("Address must be at most {MAX_RAW_ADDRESS_LEN} characters long")] - AddressTooLong, - #[error("Address must not contain non-ASCII characters")] - AddressNonAscii, - #[error( - "Address can only contain ASCII alphanumeric characters, hyphens and \ - full stops" - )] - AddressContainsInvalidCharacter, - #[error("Address label cannot be be empty")] - EmptyLabel, - #[error("Address label must be at most {MAX_LABEL_LEN} characters long")] - LabelTooLong, - #[error("Address label cannot begin with hyphen")] - LabelStartsWithHyphen, - #[error("Address label cannot end with hyphen")] - LabelEndsWithHyphen, - #[error("Address label cannot begin with a digit")] - LabelStartsWithDigit, - #[error("Error decoding address from Bech32m: {0}")] - DecodeBech32(bech32::Error), - #[error("Error decoding address from base32: {0}")] - DecodeBase32(bech32::Error), - #[error( - "Unexpected Bech32m human-readable part {0}, expected {ADDRESS_HRP}" - )] - UnexpectedBech32Prefix(String), - #[error( - "Unexpected Bech32m variant {0:?}, expected {ADDRESS_BECH32_VARIANT:?}" - )] - UnexpectedBech32Variant(bech32::Variant), - #[error("Unexpected address hash length {0}, expected {HASH_LEN}")] - UnexpectedHashLength(usize), - #[error("Address must be encoded with utf-8")] - NonUtf8Address(string::FromUtf8Error), -} - -pub type Result = std::result::Result; - -#[derive( - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - BorshSerialize, - BorshDeserialize, - Hash, -)] -pub struct Address { - pub hash: String, -} - -/// invariant, the raw string is equal to labels.join("."). -#[derive( - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - BorshSerialize, - BorshDeserialize, -)] -pub struct RawAddress { - pub raw: String, - labels: Vec