diff --git a/.changelog/unreleased/features/1234-masp-history-split.md b/.changelog/unreleased/features/1234-masp-history-split.md new file mode 100644 index 0000000000..f16d1f9449 --- /dev/null +++ b/.changelog/unreleased/features/1234-masp-history-split.md @@ -0,0 +1,2 @@ +- Added MASP client and wallet functionality. Added new command to view transfer + history. ([#1234](https://github.com/anoma/anoma/pull/1234)) \ No newline at end of file diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 020cc343cb..5a62a5671f 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -102,7 +102,7 @@ jobs: anoma: runs-on: ${{ matrix.os }} - timeout-minutes: 40 + timeout-minutes: 80 strategy: fail-fast: false matrix: @@ -215,6 +215,12 @@ jobs: chmod +x target/release/namadan chmod +x target/release/namadac chmod +x /usr/local/bin/tendermint + - name: Download masp parameters + run: | + mkdir /home/runner/work/masp + curl -o /home/runner/work/masp/masp-spend.params -sLO https://github.com/anoma/masp/blob/ef0ef75e81696ff4428db775c654fbec1b39c21f/masp-spend.params?raw=true + curl -o /home/runner/work/masp/masp-output.params -sLO https://github.com/anoma/masp/blob/ef0ef75e81696ff4428db775c654fbec1b39c21f/masp-output.params?raw=true + curl -o /home/runner/work/masp/masp-convert.params -sLO https://github.com/anoma/masp/blob/ef0ef75e81696ff4428db775c654fbec1b39c21f/masp-convert.params?raw=true - name: Run e2e test run: make test-e2e${{ matrix.make.suffix }} env: @@ -223,6 +229,7 @@ jobs: ANOMA_E2E_KEEP_TEMP: "true" ENV_VAR_TM_STDOUT: "false" ANOMA_LOG_COLOR: "false" + ANOMA_MASP_PARAMS_DIR: "/home/runner/work/masp" ANOMA_LOG: "info" - name: Upload e2e logs if: success() || failure() diff --git a/Cargo.lock b/Cargo.lock index f9c1414272..b4ffecbd21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -447,7 +447,7 @@ dependencies = [ "tokio", "tokio-rustls", "tungstenite 0.12.0", - "webpki-roots", + "webpki-roots 0.21.1", ] [[package]] @@ -550,12 +550,49 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" + [[package]] name = "bech32" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +[[package]] +name = "bellman" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" +dependencies = [ + "bitvec", + "blake2s_simd 0.5.11", + "byteorder", + "crossbeam-channel", + "ff", + "group", + "lazy_static 1.4.0", + "log 0.4.17", + "num_cpus", + "pairing", + "rand_core 0.6.3", + "rayon", + "subtle 2.4.1", +] + +[[package]] +name = "bigint" +version = "4.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0e8c8a600052b52482eff2cf4d810e462fdff1f656ac1ecb6232132a1ed7def" +dependencies = [ + "byteorder", + "crunchy 0.1.6", +] + [[package]] name = "bincode" version = "1.3.3" @@ -584,6 +621,20 @@ dependencies = [ "shlex", ] +[[package]] +name = "bip0039" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0830ae4cc96b0617cc912970c2b17e89456fecbf55e8eed53a956f37ab50c41" +dependencies = [ + "hmac 0.11.0", + "pbkdf2", + "rand 0.8.5", + "sha2 0.9.9", + "unicode-normalization", + "zeroize", +] + [[package]] name = "bit-set" version = "0.5.2" @@ -605,6 +656,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bitvec" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.9.2" @@ -635,6 +698,17 @@ 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", +] + [[package]] name = "blake2b_simd" version = "1.0.0" @@ -646,6 +720,28 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "blake2s_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "constant_time_eq", +] + [[package]] name = "blake3" version = "1.3.1" @@ -691,6 +787,16 @@ dependencies = [ "generic-array 0.14.5", ] +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding 0.2.1", + "cipher", +] + [[package]] name = "block-padding" version = "0.1.5" @@ -720,6 +826,19 @@ dependencies = [ "once_cell", ] +[[package]] +name = "bls12_381" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" +dependencies = [ + "ff", + "group", + "pairing", + "rand_core 0.6.3", + "subtle 2.4.1", +] + [[package]] name = "borsh" version = "0.9.4" @@ -929,6 +1048,7 @@ dependencies = [ "cfg-if 1.0.0", "cipher", "cpufeatures", + "zeroize", ] [[package]] @@ -944,6 +1064,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chacha20poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +dependencies = [ + "aead", + "chacha20 0.8.1", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.19" @@ -1257,6 +1390,12 @@ dependencies = [ "lazy_static 1.4.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" @@ -1293,6 +1432,31 @@ dependencies = [ "subtle 2.4.1", ] +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.5", + "subtle 2.4.1", +] + +[[package]] +name = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930b6a026ce9d358a17f9c9046c55d90b14bb847f36b6ebb6b19365d4feffb8" +dependencies = [ + "crypto_api", +] + [[package]] name = "ct-codecs" version = "1.1.1" @@ -1305,7 +1469,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" dependencies = [ - "sct", + "sct 0.6.1", ] [[package]] @@ -1768,6 +1932,15 @@ dependencies = [ "log 0.4.17", ] +[[package]] +name = "equihash" +version = "0.1.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "blake2b_simd 1.0.0", + "byteorder", +] + [[package]] name = "escargot" version = "0.5.7" @@ -1844,7 +2017,7 @@ dependencies = [ "ark-std", "bincode", "blake2 0.10.4", - "blake2b_simd", + "blake2b_simd 1.0.0", "borsh", "digest 0.10.3", "ed25519-dalek", @@ -1879,6 +2052,17 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "bitvec", + "rand_core 0.6.3", + "subtle 2.4.1", +] + [[package]] name = "file-lock" version = "2.1.4" @@ -1978,6 +2162,20 @@ dependencies = [ "percent-encoding 2.1.0", ] +[[package]] +name = "fpe" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" +dependencies = [ + "block-modes", + "cipher", + "libm", + "num-bigint", + "num-integer", + "num-traits 0.2.15", +] + [[package]] name = "fs_extra" version = "1.2.0" @@ -2025,6 +2223,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "funty" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" + [[package]] name = "futures" version = "0.1.31" @@ -2113,8 +2317,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a1387e07917c711fb4ee4f48ea0adb04a3c9739e53ef85bf43ae1edc2937a8b" dependencies = [ "futures-io", - "rustls", - "webpki", + "rustls 0.19.1", + "webpki 0.21.4", ] [[package]] @@ -2267,6 +2471,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "byteorder", + "ff", + "rand_core 0.6.3", + "subtle 2.4.1", +] + [[package]] name = "group-threshold-cryptography" version = "0.1.0" @@ -2279,7 +2495,7 @@ dependencies = [ "ark-poly", "ark-serialize", "ark-std", - "blake2b_simd", + "blake2b_simd 1.0.0", "chacha20 0.8.1", "hex", "itertools 0.10.3", @@ -2330,6 +2546,20 @@ dependencies = [ "tracing 0.1.35", ] +[[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" @@ -2433,6 +2663,16 @@ dependencies = [ "digest 0.8.1", ] +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + [[package]] name = "hmac-drbg" version = "0.2.0" @@ -2441,7 +2681,7 @@ checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" dependencies = [ "digest 0.8.1", "generic-array 0.12.4", - "hmac", + "hmac 0.7.1", ] [[package]] @@ -2548,7 +2788,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -2561,12 +2801,12 @@ dependencies = [ "futures-util", "hyper 0.14.19", "log 0.4.17", - "rustls", + "rustls 0.19.1", "rustls-native-certs", "tokio", "tokio-rustls", - "webpki", - "webpki-roots", + "webpki 0.21.4", + "webpki-roots 0.21.1", ] [[package]] @@ -2715,6 +2955,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "incrementalmerkletree" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186fd3ab92aeac865d4b80b410de9a7b341d31ba8281373caed0b6d17b2b5e96" +dependencies = [ + "serde 1.0.137", +] + [[package]] name = "indenter" version = "0.3.3" @@ -2859,6 +3108,20 @@ dependencies = [ "serde_json", ] +[[package]] +name = "jubjub" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" +dependencies = [ + "bitvec", + "bls12_381", + "ff", + "group", + "rand_core 0.6.3", + "subtle 2.4.1", +] + [[package]] name = "keccak" version = "0.1.2" @@ -2957,6 +3220,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "libm" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da83a57f3f5ba3680950aa3cbc806fc297bc0b289d42e8942ed528ace71b8145" + [[package]] name = "libp2p" version = "0.38.0" @@ -3353,7 +3622,7 @@ dependencies = [ "rw-stream-sink", "soketto", "url 2.2.2", - "webpki-roots", + "webpki-roots 0.21.1", ] [[package]] @@ -3390,7 +3659,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" dependencies = [ "arrayref", - "crunchy", + "crunchy 0.2.2", "digest 0.8.1", "hmac-drbg", "rand 0.7.3", @@ -3535,6 +3804,62 @@ dependencies = [ "serde_yaml", ] +[[package]] +name = "masp_primitives" +version = "0.5.0" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +dependencies = [ + "aes", + "bip0039", + "bitvec", + "blake2b_simd 1.0.0", + "blake2s_simd 1.0.0", + "bls12_381", + "borsh", + "byteorder", + "chacha20poly1305 0.9.1", + "crypto_api_chachapoly", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static 1.4.0", + "rand 0.8.5", + "rand_core 0.6.3", + "ripemd160", + "secp256k1", + "serde 1.0.137", + "sha2 0.9.9", + "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" +dependencies = [ + "bellman", + "blake2b_simd 1.0.0", + "bls12_381", + "byteorder", + "directories", + "ff", + "group", + "itertools 0.10.3", + "jubjub", + "lazy_static 1.4.0", + "masp_primitives", + "minreq", + "rand_core 0.6.3", + "wagyu-zcash-parameters", + "zcash_primitives", + "zcash_proofs", +] + [[package]] name = "match_cfg" version = "0.1.0" @@ -3596,6 +3921,15 @@ dependencies = [ "autocfg 1.1.0", ] +[[package]] +name = "memuse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f69d25cd7528769ad3d897e99eb942774bff8b23165012af490351a44c5b583b" +dependencies = [ + "nonempty", +] + [[package]] name = "merlin" version = "2.0.1" @@ -3686,6 +4020,19 @@ dependencies = [ "adler", ] +[[package]] +name = "minreq" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c785bc6027fd359756e538541c8624012ba3776d3d3fe123885643092ed4132" +dependencies = [ + "lazy_static 1.4.0", + "log 0.4.17", + "rustls 0.20.6", + "webpki 0.22.0", + "webpki-roots 0.22.4", +] + [[package]] name = "mio" version = "0.6.23" @@ -3839,6 +4186,7 @@ dependencies = [ "ark-serialize", "assert_matches", "bech32", + "bit-vec", "borsh", "byte-unit", "chrono", @@ -3854,6 +4202,8 @@ dependencies = [ "ics23", "itertools 0.10.3", "loupe", + "masp_primitives", + "masp_proofs", "namada_proof_of_stake", "parity-wasm", "pretty_assertions", @@ -3863,6 +4213,7 @@ dependencies = [ "pwasm-utils", "rand 0.8.5", "rand_core 0.6.3", + "rayon", "rust_decimal", "serde 1.0.137", "serde_json", @@ -3921,6 +4272,8 @@ dependencies = [ "libc", "libloading", "libp2p", + "masp_primitives", + "masp_proofs", "message-io", "namada", "num-derive", @@ -4046,6 +4399,8 @@ version = "0.7.0" dependencies = [ "borsh", "hex", + "masp_primitives", + "masp_proofs", "namada", "namada_macros", ] @@ -4164,6 +4519,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + [[package]] name = "notify" version = "4.0.17" @@ -4387,6 +4748,33 @@ 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", + "blake2b_simd 1.0.0", + "ff", + "fpe", + "group", + "halo2", + "incrementalmerkletree", + "lazy_static 1.4.0", + "memuse", + "nonempty", + "pasta_curves", + "rand 0.8.5", + "reddsa", + "serde 1.0.137", + "subtle 2.4.1", + "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "orion" version = "0.16.1" @@ -4420,6 +4808,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" +[[package]] +name = "pairing" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" +dependencies = [ + "group", +] + [[package]] name = "parity-multiaddr" version = "0.11.2" @@ -4529,6 +4926,32 @@ dependencies = [ "windows-sys", ] +[[package]] +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.3", + "subtle 2.4.1", +] + +[[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 1.4.0", + "rand 0.8.5", + "static_assertions", + "subtle 2.4.1", +] + [[package]] name = "paste" version = "1.0.7" @@ -4541,6 +4964,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "pbkdf2" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" +dependencies = [ + "crypto-mac 0.11.1", + "password-hash", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -5007,6 +5440,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" + [[package]] name = "rand" version = "0.6.5" @@ -5226,6 +5665,24 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "reddsa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e2c94bca3445cae0d55dff7370e29c24885d2403a1665ce19c106c79455e6" +dependencies = [ + "blake2b_simd 0.5.11", + "byteorder", + "digest 0.9.0", + "group", + "jubjub", + "pasta_curves", + "rand_core 0.6.3", + "serde 1.0.137", + "thiserror", + "zeroize", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -5519,8 +5976,20 @@ dependencies = [ "base64 0.13.0", "log 0.4.17", "ring", - "sct", - "webpki", + "sct 0.6.1", + "webpki 0.21.4", +] + +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log 0.4.17", + "ring", + "sct 0.7.0", + "webpki 0.22.0", ] [[package]] @@ -5530,7 +5999,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" dependencies = [ "openssl-probe", - "rustls", + "rustls 0.19.1", "schannel", "security-framework", ] @@ -5667,12 +6136,40 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "seahash" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "secp256k1" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.3.1" @@ -6013,7 +6510,7 @@ checksum = "6142f7c25e94f6fd25a32c3348ec230df9109b463f59c8c7acc4bd34936babb7" dependencies = [ "aes-gcm", "blake2 0.9.2", - "chacha20poly1305", + "chacha20poly1305 0.8.2", "rand 0.8.5", "rand_core 0.6.3", "ring", @@ -6220,6 +6717,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.38" @@ -6641,9 +7144,9 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ - "rustls", + "rustls 0.19.1", "tokio", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -7109,7 +7612,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" dependencies = [ "byteorder", - "crunchy", + "crunchy 0.2.2", "hex", "static_assertions", ] @@ -7324,6 +7827,56 @@ dependencies = [ "libc", ] +[[package]] +name = "wagyu-zcash-parameters" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c904628658374e651288f000934c33ef738b2d8b3e65d4100b70b395dbe2bb" +dependencies = [ + "wagyu-zcash-parameters-1", + "wagyu-zcash-parameters-2", + "wagyu-zcash-parameters-3", + "wagyu-zcash-parameters-4", + "wagyu-zcash-parameters-5", + "wagyu-zcash-parameters-6", +] + +[[package]] +name = "wagyu-zcash-parameters-1" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bf2e21bb027d3f8428c60d6a720b54a08bf6ce4e6f834ef8e0d38bb5695da8" + +[[package]] +name = "wagyu-zcash-parameters-2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a616ab2e51e74cc48995d476e94de810fb16fc73815f390bf2941b046cc9ba2c" + +[[package]] +name = "wagyu-zcash-parameters-3" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14da1e2e958ff93c0830ee68e91884069253bf3462a67831b02b367be75d6147" + +[[package]] +name = "wagyu-zcash-parameters-4" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f058aeef03a2070e8666ffb5d1057d8bb10313b204a254a6e6103eb958e9a6d6" + +[[package]] +name = "wagyu-zcash-parameters-5" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffe916b30e608c032ae1b734f02574a3e12ec19ab5cc5562208d679efe4969d" + +[[package]] +name = "wagyu-zcash-parameters-6" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b6d5a78adc3e8f198e9cd730f219a695431467f7ec29dcfc63ade885feebe1" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -7765,13 +8318,32 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "webpki-roots" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" dependencies = [ - "webpki", + "webpki 0.21.4", +] + +[[package]] +name = "webpki-roots" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +dependencies = [ + "webpki 0.22.0", ] [[package]] @@ -7998,6 +8570,15 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "wyz" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129e027ad65ce1453680623c3fb5163cbf7107bfe1aa32257e7d0e63f9ced188" +dependencies = [ + "tap", +] + [[package]] name = "x25519-dalek" version = "1.2.0" @@ -8041,6 +8622,88 @@ dependencies = [ "static_assertions", ] +[[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 0.8.1", + "chacha20poly1305 0.9.1", + "rand_core 0.6.3", + "subtle 2.4.1", +] + +[[package]] +name = "zcash_note_encryption" +version = "0.1.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "chacha20 0.8.1", + "chacha20poly1305 0.9.1", + "rand_core 0.6.3", + "subtle 2.4.1", +] + +[[package]] +name = "zcash_primitives" +version = "0.5.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "aes", + "bip0039", + "bitvec", + "blake2b_simd 1.0.0", + "blake2s_simd 1.0.0", + "bls12_381", + "byteorder", + "chacha20poly1305 0.9.1", + "equihash", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static 1.4.0", + "memuse", + "nonempty", + "orchard", + "rand 0.8.5", + "rand_core 0.6.3", + "sha2 0.9.9", + "subtle 2.4.1", + "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.0", + "bls12_381", + "byteorder", + "directories", + "ff", + "group", + "jubjub", + "lazy_static 1.4.0", + "rand_core 0.6.3", + "zcash_primitives", +] + [[package]] name = "zeroize" version = "1.3.0" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 7a4dffac76..d6ea627802 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -122,6 +122,9 @@ tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} 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"] } [dev-dependencies] namada = {path = "../shared", features = ["testing", "wasm-runtime"]} diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index d04eeff9ab..12c5ff0370 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -52,6 +52,9 @@ pub async fn main() -> Result<()> { Sub::QueryEpoch(QueryEpoch(args)) => { rpc::query_epoch(args).await; } + Sub::QueryTransfers(QueryTransfers(args)) => { + rpc::query_transfers(ctx, args).await; + } Sub::QueryBalance(QueryBalance(args)) => { rpc::query_balance(ctx, args).await; } diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index 9807516a93..bdba02efda 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -6,10 +6,14 @@ use std::io::{self, Write}; use borsh::BorshSerialize; use color_eyre::eyre::Result; use itertools::sorted; +use masp_primitives::zip32::ExtendedFullViewingKey; use namada::types::key::*; +use namada::types::masp::{MaspValue, PaymentAddress}; use namada_apps::cli; use namada_apps::cli::{args, cmds, Context}; -use namada_apps::wallet::DecryptionError; +use namada_apps::client::tx::find_valid_diversifier; +use namada_apps::wallet::{DecryptionError, FindKeyError}; +use rand_core::OsRng; pub fn main() -> Result<()> { let (cmd, ctx) = cli::anoma_wallet_cli(); @@ -36,10 +40,260 @@ pub fn main() -> Result<()> { address_add(ctx, args) } }, + cmds::AnomaWallet::Masp(sub) => match sub { + cmds::WalletMasp::GenSpendKey(cmds::MaspGenSpendKey(args)) => { + spending_key_gen(ctx, args) + } + cmds::WalletMasp::GenPayAddr(cmds::MaspGenPayAddr(args)) => { + payment_address_gen(ctx, args) + } + cmds::WalletMasp::AddAddrKey(cmds::MaspAddAddrKey(args)) => { + address_key_add(ctx, args) + } + cmds::WalletMasp::ListPayAddrs(cmds::MaspListPayAddrs) => { + payment_addresses_list(ctx) + } + cmds::WalletMasp::ListKeys(cmds::MaspListKeys(args)) => { + spending_keys_list(ctx, args) + } + cmds::WalletMasp::FindAddrKey(cmds::MaspFindAddrKey(args)) => { + address_key_find(ctx, args) + } + }, } Ok(()) } +/// Find shielded address or key +fn address_key_find( + ctx: Context, + args::AddrKeyFind { + alias, + unsafe_show_secret, + }: args::AddrKeyFind, +) { + let mut wallet = ctx.wallet; + let alias = alias.to_lowercase(); + if let Ok(viewing_key) = wallet.find_viewing_key(&alias) { + // Check if alias is a viewing key + println!("Viewing key: {}", viewing_key); + if unsafe_show_secret { + // Check if alias is also a spending key + match wallet.find_spending_key(&alias) { + Ok(spending_key) => println!("Spending key: {}", spending_key), + Err(FindKeyError::KeyNotFound) => {} + Err(err) => eprintln!("{}", err), + } + } + } else if let Some(payment_addr) = wallet.find_payment_addr(&alias) { + // Failing that, check if alias is a payment address + println!("Payment address: {}", payment_addr); + } else { + // Otherwise alias cannot be referring to any shielded value + println!( + "No shielded address or key with alias {} found. Use the commands \ + `masp list-addrs` and `masp list-keys` to see all the known \ + addresses and keys.", + alias.to_lowercase() + ); + } +} + +/// List spending keys. +fn spending_keys_list( + ctx: Context, + args::MaspKeysList { + decrypt, + unsafe_show_secret, + }: args::MaspKeysList, +) { + let wallet = ctx.wallet; + let known_view_keys = wallet.get_viewing_keys(); + let known_spend_keys = wallet.get_spending_keys(); + if known_view_keys.is_empty() { + println!( + "No known keys. Try `masp add --alias my-addr --value ...` to add \ + a new key to the wallet." + ); + } else { + let stdout = io::stdout(); + let mut w = stdout.lock(); + writeln!(w, "Known keys:").unwrap(); + for (alias, key) in known_view_keys { + write!(w, " Alias \"{}\"", alias).unwrap(); + let spending_key_opt = known_spend_keys.get(&alias); + // If this alias is associated with a spending key, indicate whether + // or not the spending key is encrypted + if let Some(spending_key) = spending_key_opt { + if spending_key.is_encrypted() { + writeln!(w, " (encrypted):") + } else { + writeln!(w, " (not encrypted):") + } + .unwrap(); + } else { + writeln!(w, ":").unwrap(); + } + // Always print the corresponding viewing key + writeln!(w, " Viewing Key: {}", key).unwrap(); + // A subset of viewing keys will have corresponding spending keys. + // Print those too if they are available and requested. + if unsafe_show_secret { + if let Some(spending_key) = spending_key_opt { + match spending_key.get(decrypt, None) { + // Here the spending key is unencrypted or successfully + // decrypted + Ok(spending_key) => { + writeln!(w, " Spending key: {}", spending_key) + .unwrap(); + } + // Here the key is encrypted but decryption has not been + // requested + Err(DecryptionError::NotDecrypting) if !decrypt => { + continue; + } + // Here the key is encrypted but incorrect password has + // been provided + Err(err) => { + writeln!( + w, + " Couldn't decrypt the spending key: {}", + err + ) + .unwrap(); + } + } + } + } + } + } +} + +/// List payment addresses. +fn payment_addresses_list(ctx: Context) { + let wallet = ctx.wallet; + let known_addresses = wallet.get_payment_addrs(); + if known_addresses.is_empty() { + println!( + "No known payment addresses. Try `masp gen-addr --alias my-addr` \ + to generate a new payment address." + ); + } else { + let stdout = io::stdout(); + let mut w = stdout.lock(); + writeln!(w, "Known payment addresses:").unwrap(); + for (alias, address) in sorted(known_addresses) { + writeln!(w, " \"{}\": {}", alias, address).unwrap(); + } + } +} + +/// Generate a spending key. +fn spending_key_gen( + ctx: Context, + args::MaspSpendKeyGen { + alias, + unsafe_dont_encrypt, + }: args::MaspSpendKeyGen, +) { + let mut wallet = ctx.wallet; + let alias = alias.to_lowercase(); + let (alias, _key) = wallet.gen_spending_key(alias, unsafe_dont_encrypt); + wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); + println!( + "Successfully added a spending key with alias: \"{}\"", + alias + ); +} + +/// Generate a shielded payment address from the given key. +fn payment_address_gen( + mut ctx: Context, + args::MaspPayAddrGen { + alias, + viewing_key, + pin, + }: args::MaspPayAddrGen, +) { + let alias = alias.to_lowercase(); + let viewing_key = + ExtendedFullViewingKey::from(ctx.get_cached(&viewing_key)) + .fvk + .vk; + let (div, _g_d) = find_valid_diversifier(&mut OsRng); + let payment_addr = viewing_key + .to_payment_address(div) + .expect("a PaymentAddress"); + let mut wallet = ctx.wallet; + let alias = wallet + .insert_payment_addr( + alias, + PaymentAddress::from(payment_addr).pinned(pin), + ) + .unwrap_or_else(|| { + eprintln!("Payment address not added"); + cli::safe_exit(1); + }); + wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); + println!( + "Successfully generated a payment address with the following alias: {}", + alias, + ); +} + +/// Add a viewing key, spending key, or payment address to wallet. +fn address_key_add( + mut ctx: Context, + args::MaspAddrKeyAdd { + alias, + value, + unsafe_dont_encrypt, + }: args::MaspAddrKeyAdd, +) { + let alias = alias.to_lowercase(); + let (alias, typ) = match value { + MaspValue::FullViewingKey(viewing_key) => { + let alias = ctx + .wallet + .insert_viewing_key(alias, viewing_key) + .unwrap_or_else(|| { + eprintln!("Viewing key not added"); + cli::safe_exit(1); + }); + (alias, "viewing key") + } + MaspValue::ExtendedSpendingKey(spending_key) => { + let alias = ctx + .wallet + .encrypt_insert_spending_key( + alias, + spending_key, + unsafe_dont_encrypt, + ) + .unwrap_or_else(|| { + eprintln!("Spending key not added"); + cli::safe_exit(1); + }); + (alias, "spending key") + } + MaspValue::PaymentAddress(payment_addr) => { + let alias = ctx + .wallet + .insert_payment_addr(alias, payment_addr) + .unwrap_or_else(|| { + eprintln!("Payment address not added"); + cli::safe_exit(1); + }); + (alias, "payment address") + } + }; + ctx.wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); + println!( + "Successfully added a {} with the following alias to wallet: {}", + typ, alias, + ); +} + /// Generate a new keypair and derive implicit address from it and store them in /// the wallet. fn key_and_address_gen( diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 5eef95b34d..83028657b1 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -185,6 +185,7 @@ pub mod cmds { .subcommand(Withdraw::def().display_order(2)) // Queries .subcommand(QueryEpoch::def().display_order(3)) + .subcommand(QueryTransfers::def().display_order(3)) .subcommand(QueryBalance::def().display_order(3)) .subcommand(QueryBonds::def().display_order(3)) .subcommand(QueryVotingPower::def().display_order(3)) @@ -219,6 +220,7 @@ pub mod cmds { let unbond = Self::parse_with_ctx(matches, Unbond); let withdraw = Self::parse_with_ctx(matches, Withdraw); let query_epoch = Self::parse_with_ctx(matches, QueryEpoch); + let query_transfers = Self::parse_with_ctx(matches, QueryTransfers); let query_balance = Self::parse_with_ctx(matches, QueryBalance); let query_bonds = Self::parse_with_ctx(matches, QueryBonds); let query_voting_power = @@ -247,6 +249,7 @@ pub mod cmds { .or(unbond) .or(withdraw) .or(query_epoch) + .or(query_transfers) .or(query_balance) .or(query_bonds) .or(query_voting_power) @@ -308,6 +311,7 @@ pub mod cmds { Unbond(Unbond), Withdraw(Withdraw), QueryEpoch(QueryEpoch), + QueryTransfers(QueryTransfers), QueryBalance(QueryBalance), QueryBonds(QueryBonds), QueryVotingPower(QueryVotingPower), @@ -321,24 +325,29 @@ pub mod cmds { SubscribeTopic(SubscribeTopic), } + #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug)] pub enum AnomaWallet { /// Key management commands Key(WalletKey), /// Address management commands Address(WalletAddress), + /// MASP key, address management commands + Masp(WalletMasp), } impl Cmd for AnomaWallet { fn add_sub(app: App) -> App { app.subcommand(WalletKey::def()) .subcommand(WalletAddress::def()) + .subcommand(WalletMasp::def()) } fn parse(matches: &ArgMatches) -> Option { let key = SubCmd::parse(matches).map(Self::Key); let address = SubCmd::parse(matches).map(Self::Address); - key.or(address) + let masp = SubCmd::parse(matches).map(Self::Masp); + key.or(address).or(masp) } } @@ -477,6 +486,170 @@ pub mod cmds { } } + #[allow(clippy::large_enum_variant)] + #[derive(Clone, Debug)] + pub enum WalletMasp { + GenPayAddr(MaspGenPayAddr), + GenSpendKey(MaspGenSpendKey), + AddAddrKey(MaspAddAddrKey), + ListPayAddrs(MaspListPayAddrs), + ListKeys(MaspListKeys), + FindAddrKey(MaspFindAddrKey), + } + + impl SubCmd for WalletMasp { + const CMD: &'static str = "masp"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).and_then(|matches| { + let genpa = SubCmd::parse(matches).map(Self::GenPayAddr); + let gensk = SubCmd::parse(matches).map(Self::GenSpendKey); + let addak = SubCmd::parse(matches).map(Self::AddAddrKey); + let listpa = SubCmd::parse(matches).map(Self::ListPayAddrs); + let listsk = SubCmd::parse(matches).map(Self::ListKeys); + let findak = SubCmd::parse(matches).map(Self::FindAddrKey); + gensk.or(genpa).or(addak).or(listpa).or(listsk).or(findak) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Multi-asset shielded pool address and keypair management \ + including methods to generate and look-up addresses and \ + keys.", + ) + .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand(MaspGenSpendKey::def()) + .subcommand(MaspGenPayAddr::def()) + .subcommand(MaspAddAddrKey::def()) + .subcommand(MaspListPayAddrs::def()) + .subcommand(MaspListKeys::def()) + .subcommand(MaspFindAddrKey::def()) + } + } + + /// Find the given shielded address or key + #[derive(Clone, Debug)] + pub struct MaspFindAddrKey(pub args::AddrKeyFind); + + impl SubCmd for MaspFindAddrKey { + const CMD: &'static str = "find"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::AddrKeyFind::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Find the given shielded address or key in the wallet") + .add_args::() + } + } + + /// List all known shielded keys + #[derive(Clone, Debug)] + pub struct MaspListKeys(pub args::MaspKeysList); + + impl SubCmd for MaspListKeys { + const CMD: &'static str = "list-keys"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::MaspKeysList::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Lists all shielded keys in the wallet") + .add_args::() + } + } + + /// List all known payment addresses + #[derive(Clone, Debug)] + pub struct MaspListPayAddrs; + + impl SubCmd for MaspListPayAddrs { + const CMD: &'static str = "list-addrs"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|_matches| MaspListPayAddrs) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Lists all payment addresses in the wallet") + } + } + + /// Add a key or an address + #[derive(Clone, Debug)] + pub struct MaspAddAddrKey(pub args::MaspAddrKeyAdd); + + impl SubCmd for MaspAddAddrKey { + const CMD: &'static str = "add"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + MaspAddAddrKey(args::MaspAddrKeyAdd::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Adds the given payment address or key to the wallet") + .add_args::() + } + } + + /// Generate a spending key + #[derive(Clone, Debug)] + pub struct MaspGenSpendKey(pub args::MaspSpendKeyGen); + + impl SubCmd for MaspGenSpendKey { + const CMD: &'static str = "gen-key"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + MaspGenSpendKey(args::MaspSpendKeyGen::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Generates a random spending key") + .add_args::() + } + } + + /// Generate a payment address from a viewing key or payment address + #[derive(Clone, Debug)] + pub struct MaspGenPayAddr(pub args::MaspPayAddrGen); + + impl SubCmd for MaspGenPayAddr { + const CMD: &'static str = "gen-addr"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + MaspGenPayAddr(args::MaspPayAddrGen::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Generates a payment address from the given spending key", + ) + .add_args::() + } + } + #[derive(Clone, Debug)] pub enum WalletAddress { Gen(AddressGen), @@ -1032,6 +1205,25 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct QueryTransfers(pub args::QueryTransfers); + + impl SubCmd for QueryTransfers { + const CMD: &'static str = "show-transfers"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + QueryTransfers(args::QueryTransfers::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Query the accepted transfers to date.") + .add_args::() + } + } + #[derive(Clone, Debug)] pub struct QueryBalance(pub args::QueryBalance); @@ -1368,6 +1560,7 @@ pub mod args { use namada::types::governance::ProposalVote; use namada::types::intent::{DecimalWrapper, Exchange}; use namada::types::key::*; + use namada::types::masp::MaspValue; use namada::types::storage::{self, Epoch}; use namada::types::token; use namada::types::transaction::GasLimit; @@ -1375,7 +1568,7 @@ pub mod args { use tendermint::Timeout; use tendermint_config::net::Address as TendermintAddress; - use super::context::{WalletAddress, WalletKeypair, WalletPublicKey}; + use super::context::*; use super::utils::*; use super::ArgMatches; use crate::config; @@ -1387,6 +1580,7 @@ pub mod args { const ALLOW_DUPLICATE_IP: ArgFlag = flag("allow-duplicate-ip"); const AMOUNT: Arg = arg("amount"); const ARCHIVE_DIR: ArgOpt = arg_opt("archive-dir"); + const BALANCE_OWNER: ArgOpt = arg_opt("owner"); const BASE_DIR: ArgDefault = arg_default( "base-dir", DefaultFn(|| match env::var("ANOMA_BASE_DIR") { @@ -1437,6 +1631,7 @@ pub mod args { const LEDGER_ADDRESS: Arg = arg("ledger-address"); const LOCALHOST: ArgFlag = flag("localhost"); + const MASP_VALUE: Arg = arg("value"); const MATCHMAKER_PATH: ArgOpt = arg_opt("matchmaker-path"); const MODE: ArgOpt = arg_opt("mode"); const MULTIADDR_OPT: ArgOpt = arg_opt("address"); @@ -1445,6 +1640,7 @@ pub mod args { const NODE: Arg = arg("node"); const NFT_ADDRESS: Arg
= arg("nft-address"); const OWNER: ArgOpt = arg_opt("owner"); + const PIN: ArgFlag = flag("pin"); const PROPOSAL_OFFLINE: ArgFlag = flag("offline"); const PROTOCOL_KEY: ArgOpt = arg_opt("protocol-key"); const PRE_GENESIS_PATH: ArgOpt = arg_opt("pre-genesis-path"); @@ -1463,12 +1659,13 @@ pub mod args { const SOURCE: Arg = arg("source"); const SOURCE_OPT: ArgOpt = SOURCE.opt(); const STORAGE_KEY: Arg = arg("storage-key"); - const TARGET: Arg = arg("target"); const TO_STDOUT: ArgFlag = flag("stdout"); const TOKEN_OPT: ArgOpt = TOKEN.opt(); const TOKEN: Arg = arg("token"); const TOPIC_OPT: ArgOpt = arg_opt("topic"); const TOPIC: Arg = arg("topic"); + const TRANSFER_SOURCE: Arg = arg("source"); + const TRANSFER_TARGET: Arg = arg("target"); const TX_CODE_PATH: ArgOpt = arg_opt("tx-code-path"); const TX_HASH: Arg = arg("tx-hash"); const UNSAFE_DONT_ENCRYPT: ArgFlag = flag("unsafe-dont-encrypt"); @@ -1481,6 +1678,7 @@ pub mod args { arg_opt("consensus-key"); const VALIDATOR_CODE_PATH: ArgOpt = arg_opt("validator-code-path"); const VALUE: ArgOpt = arg_opt("value"); + const VIEWING_KEY: Arg = arg("key"); const WASM_CHECKSUMS_PATH: Arg = arg("wasm-checksums-path"); const WASM_DIR: ArgOpt = arg_opt("wasm-dir"); @@ -1603,9 +1801,9 @@ pub mod args { /// Common tx arguments pub tx: Tx, /// Transfer source address - pub source: WalletAddress, + pub source: WalletTransferSource, /// Transfer target address - pub target: WalletAddress, + pub target: WalletTransferTarget, /// Transferred token address pub token: WalletAddress, /// Transferred token amount @@ -1615,8 +1813,8 @@ pub mod args { impl Args for TxTransfer { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); - let source = SOURCE.parse(matches); - let target = TARGET.parse(matches); + let source = TRANSFER_SOURCE.parse(matches); + let target = TRANSFER_TARGET.parse(matches); let token = TOKEN.parse(matches); let amount = AMOUNT.parse(matches); Self { @@ -1630,11 +1828,14 @@ pub mod args { fn def(app: App) -> App { app.add_args::() - .arg(SOURCE.def().about( - "The source account address. The source's key is used to \ - produce the signature.", + .arg(TRANSFER_SOURCE.def().about( + "The source account address. The source's key may be used \ + to produce the signature.", + )) + .arg(TRANSFER_TARGET.def().about( + "The target account address. The target's key may be used \ + to produce the signature.", )) - .arg(TARGET.def().about("The target account address.")) .arg(TOKEN.def().about("The transfer token.")) .arg(AMOUNT.def().about("The amount to transfer in decimal.")) } @@ -2173,7 +2374,7 @@ pub mod args { /// Common query args pub query: Query, /// Address of an owner - pub owner: Option, + pub owner: Option, /// Address of a token pub token: Option, } @@ -2181,7 +2382,7 @@ pub mod args { impl Args for QueryBalance { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); - let owner = OWNER.parse(matches); + let owner = BALANCE_OWNER.parse(matches); let token = TOKEN_OPT.parse(matches); Self { query, @@ -2193,7 +2394,7 @@ pub mod args { fn def(app: App) -> App { app.add_args::() .arg( - OWNER + BALANCE_OWNER .def() .about("The account address whose balance to query."), ) @@ -2205,6 +2406,40 @@ pub mod args { } } + /// Query historical transfer(s) + #[derive(Clone, Debug)] + pub struct QueryTransfers { + /// Common query args + pub query: Query, + /// Address of an owner + pub owner: Option, + /// Address of a token + pub token: Option, + } + + impl Args for QueryTransfers { + fn parse(matches: &ArgMatches) -> Self { + let query = Query::parse(matches); + let owner = BALANCE_OWNER.parse(matches); + let token = TOKEN_OPT.parse(matches); + Self { + query, + owner, + token, + } + } + + fn def(app: App) -> App { + app.add_args::() + .arg(BALANCE_OWNER.def().about( + "The account address that queried transfers must involve.", + )) + .arg(TOKEN_OPT.def().about( + "The token address that queried transfers must involve.", + )) + } + } + /// Helper struct for generating intents #[derive(Debug, Clone, Deserialize)] pub struct ExchangeDefinition { @@ -2718,6 +2953,116 @@ pub mod args { } } + /// MASP add key or address arguments + #[derive(Clone, Debug)] + pub struct MaspAddrKeyAdd { + /// Key alias + pub alias: String, + /// Any MASP value + pub value: MaspValue, + /// Don't encrypt the keypair + pub unsafe_dont_encrypt: bool, + } + + impl Args for MaspAddrKeyAdd { + fn parse(matches: &ArgMatches) -> Self { + let alias = ALIAS.parse(matches); + let value = MASP_VALUE.parse(matches); + let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); + Self { + alias, + value, + unsafe_dont_encrypt, + } + } + + fn def(app: App) -> App { + app.arg( + ALIAS + .def() + .about("An alias to be associated with the new entry."), + ) + .arg( + MASP_VALUE + .def() + .about("A spending key, viewing key, or payment address."), + ) + .arg(UNSAFE_DONT_ENCRYPT.def().about( + "UNSAFE: Do not encrypt the keypair. Do not use this for keys \ + used in a live network.", + )) + } + } + + /// MASP generate spending key arguments + #[derive(Clone, Debug)] + pub struct MaspSpendKeyGen { + /// Key alias + pub alias: String, + /// Don't encrypt the keypair + pub unsafe_dont_encrypt: bool, + } + + impl Args for MaspSpendKeyGen { + fn parse(matches: &ArgMatches) -> Self { + let alias = ALIAS.parse(matches); + let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); + Self { + alias, + unsafe_dont_encrypt, + } + } + + fn def(app: App) -> App { + app.arg( + ALIAS + .def() + .about("An alias to be associated with the spending key."), + ) + .arg(UNSAFE_DONT_ENCRYPT.def().about( + "UNSAFE: Do not encrypt the keypair. Do not use this for keys \ + used in a live network.", + )) + } + } + + /// MASP generate payment address arguments + #[derive(Clone, Debug)] + pub struct MaspPayAddrGen { + /// Key alias + pub alias: String, + /// Viewing key + pub viewing_key: WalletViewingKey, + /// Pin + pub pin: bool, + } + + impl Args for MaspPayAddrGen { + fn parse(matches: &ArgMatches) -> Self { + let alias = ALIAS.parse(matches); + let viewing_key = VIEWING_KEY.parse(matches); + let pin = PIN.parse(matches); + Self { + alias, + viewing_key, + pin, + } + } + + fn def(app: App) -> App { + app.arg( + ALIAS.def().about( + "An alias to be associated with the payment address.", + ), + ) + .arg(VIEWING_KEY.def().about("The viewing key.")) + .arg(PIN.def().about( + "Require that the single transaction to this address be \ + pinned.", + )) + } + } + /// Wallet generate key and implicit address arguments #[derive(Clone, Debug)] pub struct KeyAndAddressGen { @@ -2799,6 +3144,60 @@ pub mod args { } } + /// Wallet find shielded address or key arguments + #[derive(Clone, Debug)] + pub struct AddrKeyFind { + pub alias: String, + pub unsafe_show_secret: bool, + } + + impl Args for AddrKeyFind { + fn parse(matches: &ArgMatches) -> Self { + let alias = ALIAS.parse(matches); + let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); + Self { + alias, + unsafe_show_secret, + } + } + + fn def(app: App) -> App { + app.arg(ALIAS.def().about("The alias that is to be found.")) + .arg( + UNSAFE_SHOW_SECRET + .def() + .about("UNSAFE: Print the spending key values."), + ) + } + } + + /// Wallet list shielded keys arguments + #[derive(Clone, Debug)] + pub struct MaspKeysList { + pub decrypt: bool, + pub unsafe_show_secret: bool, + } + + impl Args for MaspKeysList { + fn parse(matches: &ArgMatches) -> Self { + let decrypt = DECRYPT.parse(matches); + let unsafe_show_secret = UNSAFE_SHOW_SECRET.parse(matches); + Self { + decrypt, + unsafe_show_secret, + } + } + + fn def(app: App) -> App { + app.arg(DECRYPT.def().about("Decrypt keys that are encrypted.")) + .arg( + UNSAFE_SHOW_SECRET + .def() + .about("UNSAFE: Print the spending key values."), + ) + } + } + /// Wallet list keys arguments #[derive(Clone, Debug)] pub struct KeyList { diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 8189b633bf..fd96dc3273 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -3,15 +3,16 @@ use std::env; use std::marker::PhantomData; use std::path::{Path, PathBuf}; -use std::rc::Rc; use std::str::FromStr; use namada::types::address::Address; use namada::types::chain::ChainId; use namada::types::key::*; +use namada::types::masp::*; use super::args; use crate::cli::safe_exit; +use crate::client::tx::ShieldedContext; use crate::config::genesis::genesis_config; use crate::config::global::GlobalConfig; use crate::config::{self, Config}; @@ -27,14 +28,38 @@ pub const ENV_VAR_WASM_DIR: &str = "ANOMA_WASM_DIR"; /// in the wallet pub type WalletAddress = FromContext
; +/// A raw extended spending key (bech32m encoding) or an alias of an extended +/// spending key in the wallet +pub type WalletSpendingKey = FromContext; + +/// A raw payment address (bech32m encoding) or an alias of a payment address +/// in the wallet +pub type WalletPaymentAddr = FromContext; + +/// A raw full viewing key (bech32m encoding) or an alias of a full viewing key +/// in the wallet +pub type WalletViewingKey = FromContext; + +/// A raw address or a raw extended spending key (bech32m encoding) or an alias +/// of either in the wallet +pub type WalletTransferSource = FromContext; + +/// A raw address or a raw payment address (bech32m encoding) or an alias of +/// either in the wallet +pub type WalletTransferTarget = FromContext; + /// A raw keypair (hex encoding), an alias, a public key or a public key hash of /// a keypair that may be found in the wallet -pub type WalletKeypair = FromContext>; +pub type WalletKeypair = FromContext; /// A raw public key (hex encoding), a public key hash (also hex encoding) or an /// alias of an public key that may be found in the wallet pub type WalletPublicKey = FromContext; +/// A raw address or a raw full viewing key (bech32m encoding) or an alias of +/// either in the wallet +pub type WalletBalanceOwner = FromContext; + /// Command execution context #[derive(Debug)] pub struct Context { @@ -46,6 +71,8 @@ pub struct Context { pub global_config: GlobalConfig, /// The ledger & intent gossip configuration for a specific chain ID pub config: Config, + /// The context fr shielded operations + pub shielded: ShieldedContext, } impl Context { @@ -101,6 +128,7 @@ impl Context { wallet, global_config, config, + shielded: ShieldedContext::new(chain_dir), } } @@ -109,7 +137,7 @@ impl Context { where T: ArgFromContext, { - from_context.arg_from_ctx(self) + from_context.arg_from_ctx(self).unwrap() } /// Try to parse and/or look-up an optional value from the context. @@ -119,7 +147,7 @@ impl Context { { from_context .as_ref() - .map(|from_context| from_context.arg_from_ctx(self)) + .map(|from_context| from_context.arg_from_ctx(self).unwrap()) } /// Parse and/or look-up the value from the context with cache. @@ -127,7 +155,7 @@ impl Context { where T: ArgFromMutContext, { - from_context.arg_from_mut_ctx(self) + from_context.arg_from_mut_ctx(self).unwrap() } /// Try to parse and/or look-up an optional value from the context with @@ -141,7 +169,7 @@ impl Context { { from_context .as_ref() - .map(|from_context| from_context.arg_from_mut_ctx(self)) + .map(|from_context| from_context.arg_from_mut_ctx(self).unwrap()) } /// Get the wasm directory configured for the chain. @@ -218,12 +246,53 @@ impl FromContext { } } +impl FromContext { + /// Converts this TransferSource argument to an Address. Call this function + /// only when certain that raw represents an Address. + pub fn to_address(&self) -> FromContext
{ + FromContext::
{ + raw: self.raw.clone(), + phantom: PhantomData, + } + } + + /// Converts this TransferSource argument to an ExtendedSpendingKey. Call + /// this function only when certain that raw represents an + /// ExtendedSpendingKey. + pub fn to_spending_key(&self) -> FromContext { + FromContext:: { + raw: self.raw.clone(), + phantom: PhantomData, + } + } +} + +impl FromContext { + /// Converts this TransferTarget argument to an Address. Call this function + /// only when certain that raw represents an Address. + pub fn to_address(&self) -> FromContext
{ + FromContext::
{ + raw: self.raw.clone(), + phantom: PhantomData, + } + } + + /// Converts this TransferTarget argument to a PaymentAddress. Call this + /// function only when certain that raw represents a PaymentAddress. + pub fn to_payment_address(&self) -> FromContext { + FromContext:: { + raw: self.raw.clone(), + phantom: PhantomData, + } + } +} + impl FromContext where T: ArgFromContext, { /// Parse and/or look-up the value from the context. - fn arg_from_ctx(&self, ctx: &Context) -> T { + fn arg_from_ctx(&self, ctx: &Context) -> Result { T::arg_from_ctx(ctx, &self.raw) } } @@ -233,61 +302,70 @@ where T: ArgFromMutContext, { /// Parse and/or look-up the value from the mutable context. - fn arg_from_mut_ctx(&self, ctx: &mut Context) -> T { + fn arg_from_mut_ctx(&self, ctx: &mut Context) -> Result { T::arg_from_mut_ctx(ctx, &self.raw) } } /// CLI argument that found via the [`Context`]. pub trait ArgFromContext: Sized { - fn arg_from_ctx(ctx: &Context, raw: impl AsRef) -> Self; + fn arg_from_ctx( + ctx: &Context, + raw: impl AsRef, + ) -> Result; } /// CLI argument that found via the [`Context`] and cached (as in case of an /// encrypted keypair that has been decrypted), hence using mutable context. pub trait ArgFromMutContext: Sized { - fn arg_from_mut_ctx(ctx: &mut Context, raw: impl AsRef) -> Self; + fn arg_from_mut_ctx( + ctx: &mut Context, + raw: impl AsRef, + ) -> Result; } impl ArgFromContext for Address { - fn arg_from_ctx(ctx: &Context, raw: impl AsRef) -> Self { + fn arg_from_ctx( + ctx: &Context, + raw: impl AsRef, + ) -> Result { let raw = raw.as_ref(); // An address can be either raw (bech32m encoding) FromStr::from_str(raw) // Or it can be an alias that may be found in the wallet - .unwrap_or_else(|_| { + .or_else(|_| { ctx.wallet .find_address(raw) - .unwrap_or_else(|| { - eprintln!("Unknown address {}", raw); - safe_exit(1) - }) - .clone() + .cloned() + .ok_or_else(|| format!("Unknown address {}", raw)) }) } } -impl ArgFromMutContext for Rc { - fn arg_from_mut_ctx(ctx: &mut Context, raw: impl AsRef) -> Self { +impl ArgFromMutContext for common::SecretKey { + fn arg_from_mut_ctx( + ctx: &mut Context, + raw: impl AsRef, + ) -> Result { let raw = raw.as_ref(); // A keypair can be either a raw keypair in hex string - FromStr::from_str(raw) - .map(Rc::new) - .unwrap_or_else(|_parse_err| { - // Or it can be an alias - ctx.wallet.find_key(raw).unwrap_or_else(|_find_err| { - eprintln!("Unknown key {}", raw); - safe_exit(1) - }) - }) + FromStr::from_str(raw).or_else(|_parse_err| { + // Or it can be an alias + ctx.wallet + .find_key(raw) + .map_err(|_find_err| format!("Unknown key {}", raw)) + }) } } impl ArgFromMutContext for common::PublicKey { - fn arg_from_mut_ctx(ctx: &mut Context, raw: impl AsRef) -> Self { + fn arg_from_mut_ctx( + ctx: &mut Context, + raw: impl AsRef, + ) -> Result { let raw = raw.as_ref(); // A public key can be either a raw public key in hex string - FromStr::from_str(raw).unwrap_or_else(|_parse_err| { + FromStr::from_str(raw).or_else(|_parse_err| { // Or it can be a public key hash in hex string FromStr::from_str(raw) .map(|pkh: PublicKeyHash| { @@ -295,10 +373,112 @@ impl ArgFromMutContext for common::PublicKey { key.ref_to() }) // Or it can be an alias that may be found in the wallet - .unwrap_or_else(|_parse_err| { - let key = ctx.wallet.find_key(raw).unwrap(); - key.ref_to() + .or_else(|_parse_err| { + ctx.wallet + .find_key(raw) + .map(|x| x.ref_to()) + .map_err(|x| x.to_string()) }) }) } } + +impl ArgFromMutContext for ExtendedSpendingKey { + fn arg_from_mut_ctx( + ctx: &mut Context, + raw: impl AsRef, + ) -> Result { + let raw = raw.as_ref(); + // Either the string is a raw extended spending key + FromStr::from_str(raw).or_else(|_parse_err| { + // Or it is a stored alias of one + ctx.wallet + .find_spending_key(raw) + .map_err(|_find_err| format!("Unknown spending key {}", raw)) + }) + } +} + +impl ArgFromMutContext for ExtendedViewingKey { + fn arg_from_mut_ctx( + ctx: &mut Context, + raw: impl AsRef, + ) -> Result { + let raw = raw.as_ref(); + // Either the string is a raw full viewing key + FromStr::from_str(raw).or_else(|_parse_err| { + // Or it is a stored alias of one + ctx.wallet + .find_viewing_key(raw) + .map(Clone::clone) + .map_err(|_find_err| format!("Unknown viewing key {}", raw)) + }) + } +} + +impl ArgFromContext for PaymentAddress { + fn arg_from_ctx( + ctx: &Context, + raw: impl AsRef, + ) -> Result { + let raw = raw.as_ref(); + // Either the string is a payment address + FromStr::from_str(raw).or_else(|_parse_err| { + // Or it is a stored alias of one + ctx.wallet + .find_payment_addr(raw) + .cloned() + .ok_or_else(|| format!("Unknown payment address {}", raw)) + }) + } +} + +impl ArgFromMutContext for TransferSource { + fn arg_from_mut_ctx( + ctx: &mut Context, + raw: impl AsRef, + ) -> Result { + let raw = raw.as_ref(); + // Either the string is a transparent address or a spending key + Address::arg_from_ctx(ctx, raw) + .map(Self::Address) + .or_else(|_| { + ExtendedSpendingKey::arg_from_mut_ctx(ctx, raw) + .map(Self::ExtendedSpendingKey) + }) + } +} + +impl ArgFromContext for TransferTarget { + fn arg_from_ctx( + ctx: &Context, + raw: impl AsRef, + ) -> Result { + let raw = raw.as_ref(); + // Either the string is a transparent address or a payment address + Address::arg_from_ctx(ctx, raw) + .map(Self::Address) + .or_else(|_| { + PaymentAddress::arg_from_ctx(ctx, raw).map(Self::PaymentAddress) + }) + } +} + +impl ArgFromMutContext for BalanceOwner { + fn arg_from_mut_ctx( + ctx: &mut Context, + raw: impl AsRef, + ) -> Result { + let raw = raw.as_ref(); + // Either the string is a transparent address or a viewing key + Address::arg_from_ctx(ctx, raw) + .map(Self::Address) + .or_else(|_| { + ExtendedViewingKey::arg_from_mut_ctx(ctx, raw) + .map(Self::FullViewingKey) + }) + .or_else(|_| { + PaymentAddress::arg_from_ctx(ctx, raw).map(Self::PaymentAddress) + }) + } +} diff --git a/apps/src/lib/client/gossip.rs b/apps/src/lib/client/gossip.rs index 2225898ff4..a6737d19ce 100644 --- a/apps/src/lib/client/gossip.rs +++ b/apps/src/lib/client/gossip.rs @@ -51,7 +51,7 @@ pub async fn gossip_intent( } }; let signed_ft: Signed = Signed::new( - &*source_keypair, + &source_keypair, FungibleTokenIntent { exchange: signed_exchanges, }, @@ -112,5 +112,5 @@ async fn sign_exchange( ) -> Signed { let source_keypair = signing::find_keypair(wallet, &exchange.addr, ledger_address).await; - Signed::new(&*source_keypair, exchange.clone()) + Signed::new(&source_keypair, exchange.clone()) } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 34652ac825..642bd2eb76 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1,17 +1,25 @@ //! Client RPC queries use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; +use std::cmp::Ordering; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryInto; use std::fs::File; use std::io::{self, Write}; use std::iter::Iterator; +use std::str::FromStr; -use async_std::fs::{self}; +use async_std::fs; use async_std::path::PathBuf; use async_std::prelude::*; -use borsh::BorshDeserialize; +use borsh::{BorshDeserialize, BorshSerialize}; use itertools::Itertools; +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::transaction::components::Amount; +use masp_primitives::zip32::ExtendedFullViewingKey; use namada::ledger::governance::storage as gov_storage; use namada::ledger::governance::utils::Votes; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; @@ -22,13 +30,21 @@ use namada::ledger::pos::{ self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, }; use namada::ledger::treasury::storage as treasury_storage; -use namada::types::address::Address; +use namada::proto::{SignedTxData, Tx}; +use namada::types::address::{masp, tokens, Address}; use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalVote, TallyResult, }; use namada::types::key::*; -use namada::types::storage::{Epoch, PrefixValue}; -use namada::types::token::{balance_key, Amount}; +use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; +use namada::types::storage::{ + BlockHeight, BlockResults, Epoch, PrefixValue, TxIndex, +}; +use namada::types::token::{balance_key, Transfer}; +use namada::types::transaction::{ + process_tx, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, + WrapperTx, +}; use namada::types::{address, storage, token}; use tendermint::abci::Code; use tendermint_config::net::Address as TendermintAddress; @@ -40,6 +56,9 @@ use tendermint_rpc::{ use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; +use crate::client::tx::{ + Conversions, PinnedBalanceError, TransactionDelta, TransferDelta, +}; use crate::node::ledger::rpc::Path; /// Query the epoch of the last committed block @@ -70,6 +89,296 @@ pub async fn query_epoch(args: args::Query) -> Epoch { cli::safe_exit(1) } +/// Extract the payload from the given Tx object +fn extract_payload( + tx: Tx, + wrapper: &mut Option, + transfer: &mut Option, +) { + match process_tx(tx) { + Ok(TxType::Wrapper(wrapper_tx)) => { + let privkey = ::G2Affine::prime_subgroup_generator(); + extract_payload( + Tx::from(match wrapper_tx.decrypt(privkey) { + Ok(tx) => DecryptedTx::Decrypted(tx), + _ => DecryptedTx::Undecryptable(wrapper_tx.clone()), + }), + wrapper, + transfer, + ); + *wrapper = Some(wrapper_tx); + } + Ok(TxType::Decrypted(DecryptedTx::Decrypted(tx))) => { + let empty_vec = vec![]; + let tx_data = tx.data.as_ref().unwrap_or(&empty_vec); + let _ = SignedTxData::try_from_slice(tx_data).map(|signed| { + Transfer::try_from_slice(&signed.data.unwrap()[..]) + .map(|tfer| *transfer = Some(tfer)) + }); + } + _ => {} + } +} + +/// Query the results of the last committed block +pub async fn query_results(args: args::Query) -> Vec { + let client = HttpClient::new(args.ledger_address).unwrap(); + let path = Path::Results; + let data = vec![]; + let response = client + .abci_query(Some(path.into()), data, None, false) + .await + .unwrap(); + match response.code { + Code::Ok => { + match Vec::::try_from_slice(&response.value[..]) { + Ok(results) => { + return results; + } + + Err(err) => { + eprintln!("Error decoding the results value: {}", err) + } + } + } + Code::Err(err) => eprintln!( + "Error in the query {} (error code {})", + response.info, err + ), + } + cli::safe_exit(1) +} + +/// Obtain the known effects of all accepted shielded and transparent +/// transactions. If an owner is specified, then restrict the set to only +/// transactions crediting/debiting the given owner. If token is specified, then +/// restrict set to only transactions involving the given token. +pub async fn query_tx_deltas( + ctx: &mut Context, + ledger_address: TendermintAddress, + query_owner: &Option, + query_token: &Option
, +) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, TransferDelta, TransactionDelta)> +{ + const TXS_PER_PAGE: u8 = 100; + // Connect to the Tendermint server holding the transactions + let client = HttpClient::new(ledger_address.clone()).unwrap(); + // Build up the context that will be queried for transactions + let _ = ctx.shielded.load(); + let vks = ctx.wallet.get_viewing_keys(); + let fvks: Vec<_> = vks + .values() + .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) + .collect(); + ctx.shielded.fetch(&ledger_address, &[], &fvks).await; + // Save the update state so that future fetches can be short-circuited + let _ = ctx.shielded.save(); + // Required for filtering out rejected transactions from Tendermint + // responses + let block_results = query_results(args::Query { ledger_address }).await; + let mut transfers = ctx.shielded.get_tx_deltas().clone(); + // Construct the set of addresses relevant to user's query + let relevant_addrs = match &query_owner { + Some(BalanceOwner::Address(owner)) => vec![owner.clone()], + // MASP objects are dealt with outside of tx_search + Some(BalanceOwner::FullViewingKey(_viewing_key)) => vec![], + Some(BalanceOwner::PaymentAddress(_owner)) => vec![], + // Unspecified owner means all known addresses are considered relevant + None => ctx.wallet.get_addresses().into_values().collect(), + }; + // Find all transactions to or from the relevant address set + for addr in relevant_addrs { + for prop in ["transfer.source", "transfer.target"] { + // Query transactions involving the current address + let mut tx_query = Query::eq(prop, addr.encode()); + // Elaborate the query if requested by the user + if let Some(token) = &query_token { + tx_query = tx_query.and_eq("transfer.token", token.encode()); + } + for page in 1.. { + let txs = &client + .tx_search( + tx_query.clone(), + true, + page, + TXS_PER_PAGE, + Order::Ascending, + ) + .await + .expect("Unable to query for transactions") + .txs; + for response_tx in txs { + let height = BlockHeight(response_tx.height.value()); + let idx = TxIndex(response_tx.index); + // Only process yet unprocessed transactions which have been + // accepted by node VPs + let should_process = !transfers + .contains_key(&(height, idx)) + && block_results[u64::from(height) as usize] + .is_accepted(idx.0 as usize); + if !should_process { + continue; + } + let tx = Tx::try_from(response_tx.tx.as_ref()) + .expect("Ill-formed Tx"); + let mut wrapper = None; + let mut transfer = None; + extract_payload(tx, &mut wrapper, &mut transfer); + // Epoch data is not needed for transparent transactions + let epoch = wrapper.map(|x| x.epoch).unwrap_or_default(); + if let Some(transfer) = transfer { + // Skip MASP addresses as they are already handled by + // ShieldedContext + if transfer.source == masp() + || transfer.target == masp() + { + continue; + } + // Describe how a Transfer simply subtracts from one + // account and adds the same to another + let mut delta = TransferDelta::default(); + let tfer_delta = Amount::from_nonnegative( + transfer.token.clone(), + u64::from(transfer.amount), + ) + .expect("invalid value for amount"); + delta.insert( + transfer.source, + Amount::zero() - &tfer_delta, + ); + delta.insert(transfer.target, tfer_delta); + // No shielded accounts are affected by this Transfer + transfers.insert( + (height, idx), + (epoch, delta, TransactionDelta::new()), + ); + } + } + // An incomplete page signifies no more transactions + if (txs.len() as u8) < TXS_PER_PAGE { + break; + } + } + } + } + transfers +} + +/// Query the specified accepted transfers from the ledger +pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { + let query_token = args.token.as_ref().map(|x| ctx.get(x)); + let query_owner = args.owner.as_ref().map(|x| ctx.get_cached(x)); + // Obtain the effects of all shielded and transparent transactions + let transfers = query_tx_deltas( + &mut ctx, + args.query.ledger_address.clone(), + &query_owner, + &query_token, + ) + .await; + // To facilitate lookups of human-readable token names + let tokens = tokens(); + let vks = ctx.wallet.get_viewing_keys(); + // To enable ExtendedFullViewingKeys to be displayed instead of ViewingKeys + let fvk_map: HashMap<_, _> = vks + .values() + .map(|fvk| (ExtendedFullViewingKey::from(*fvk).fvk.vk, fvk)) + .collect(); + // Connect to the Tendermint server holding the transactions + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + // Now display historical shielded and transparent transactions + for ((height, idx), (epoch, tfer_delta, tx_delta)) in transfers { + // Check if this transfer pertains to the supplied owner + let mut relevant = match &query_owner { + Some(BalanceOwner::FullViewingKey(fvk)) => tx_delta + .contains_key(&ExtendedFullViewingKey::from(*fvk).fvk.vk), + Some(BalanceOwner::Address(owner)) => { + tfer_delta.contains_key(owner) + } + Some(BalanceOwner::PaymentAddress(_owner)) => false, + None => true, + }; + // Realize and decode the shielded changes to enable relevance check + let mut shielded_accounts = HashMap::new(); + for (acc, amt) in tx_delta { + // Realize the rewards that would have been attained upon the + // transaction's reception + let amt = ctx + .shielded + .compute_exchanged_amount( + client.clone(), + amt, + epoch, + Conversions::new(), + ) + .await + .0; + let dec = + ctx.shielded.decode_amount(client.clone(), amt, epoch).await; + shielded_accounts.insert(acc, dec); + } + // Check if this transfer pertains to the supplied token + relevant &= match &query_token { + Some(token) => { + tfer_delta.values().any(|x| x[token] != 0) + || shielded_accounts.values().any(|x| x[token] != 0) + } + None => true, + }; + // Filter out those entries that do not satisfy user query + if !relevant { + continue; + } + println!("Height: {}, Index: {}, Transparent Transfer:", height, idx); + // Display the transparent changes first + for (account, amt) in tfer_delta { + if account != masp() { + print!(" {}:", account); + for (addr, val) in amt.components() { + let addr_enc = addr.encode(); + let readable = + tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()); + let sign = match val.cmp(&0) { + Ordering::Greater => "+", + Ordering::Less => "-", + Ordering::Equal => "", + }; + print!( + " {}{} {}", + sign, + token::Amount::from(val.unsigned_abs()), + readable + ); + } + println!(); + } + } + // Then display the shielded changes afterwards + for (account, amt) in shielded_accounts { + if fvk_map.contains_key(&account) { + print!(" {}:", fvk_map[&account]); + for (addr, val) in amt.components() { + let addr_enc = addr.encode(); + let readable = + tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()); + let sign = match val.cmp(&0) { + Ordering::Greater => "+", + Ordering::Less => "-", + Ordering::Equal => "", + }; + print!( + " {}{} {}", + sign, + token::Amount::from(val.unsigned_abs()), + readable + ); + } + println!(); + } + } + } +} + /// Query the raw bytes of given storage key pub async fn query_raw_bytes(_ctx: Context, args: args::QueryRawBytes) { let client = HttpClient::new(args.query.ledger_address).unwrap(); @@ -94,13 +403,44 @@ pub async fn query_raw_bytes(_ctx: Context, args: args::QueryRawBytes) { } /// Query token balance(s) -pub async fn query_balance(ctx: Context, args: args::QueryBalance) { +pub async fn query_balance(mut ctx: Context, args: args::QueryBalance) { + // Query the balances of shielded or transparent account types depending on + // the CLI arguments + match args.owner.as_ref().map(|x| ctx.get_cached(x)) { + Some(BalanceOwner::FullViewingKey(_viewing_key)) => { + query_shielded_balance(&mut ctx, args).await + } + Some(BalanceOwner::Address(_owner)) => { + query_transparent_balance(&mut ctx, args).await + } + Some(BalanceOwner::PaymentAddress(_owner)) => { + query_pinned_balance(&mut ctx, args).await + } + None => { + // Print pinned balance + query_pinned_balance(&mut ctx, args.clone()).await; + // Print shielded balance + query_shielded_balance(&mut ctx, args.clone()).await; + // Then print transparent balance + query_transparent_balance(&mut ctx, args).await; + } + }; +} + +/// Query token balance(s) +pub async fn query_transparent_balance( + ctx: &mut Context, + args: args::QueryBalance, +) { let client = HttpClient::new(args.query.ledger_address).unwrap(); let tokens = address::tokens(); match (args.token, args.owner) { (Some(token), Some(owner)) => { let token = ctx.get(&token); - let owner = ctx.get(&owner); + let owner = ctx + .get_cached(&owner) + .address() + .expect("a transparent address"); let key = token::balance_key(&token, &owner); let currency_code = tokens .get(&token) @@ -116,7 +456,10 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { } } (None, Some(owner)) => { - let owner = ctx.get(&owner); + let owner = ctx + .get_cached(&owner) + .address() + .expect("a transparent address"); let mut found_any = false; for (token, currency_code) in tokens { let key = token::balance_key(&token, &owner); @@ -184,6 +527,144 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { } } +/// Query the token pinned balance(s) +pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { + // Map addresses to token names + let tokens = address::tokens(); + let owners = if let Some(pa) = args + .owner + .and_then(|x| ctx.get_cached(&x).payment_address()) + { + vec![pa] + } else { + ctx.wallet + .get_payment_addrs() + .into_values() + .filter(PaymentAddress::is_pinned) + .collect() + }; + // Get the viewing keys with which to try note decryptions + let viewing_keys: Vec = ctx + .wallet + .get_viewing_keys() + .values() + .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) + .collect(); + // Build up the context that will be queried for asset decodings + let _ = ctx.shielded.load(); + // Establish connection with which to do exchange rate queries + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + // Print the token balances by payment address + for owner in owners { + let mut balance = Err(PinnedBalanceError::InvalidViewingKey); + // Find the viewing key that can recognize payments the current payment + // address + for vk in &viewing_keys { + balance = ctx + .shielded + .compute_exchanged_pinned_balance( + &args.query.ledger_address, + owner, + vk, + ) + .await; + if balance != Err(PinnedBalanceError::InvalidViewingKey) { + break; + } + } + // If a suitable viewing key was not found, then demand it from the user + if balance == Err(PinnedBalanceError::InvalidViewingKey) { + print!("Enter the viewing key for {}: ", owner); + io::stdout().flush().unwrap(); + let mut vk_str = String::new(); + io::stdin().read_line(&mut vk_str).unwrap(); + let fvk = match ExtendedViewingKey::from_str(vk_str.trim()) { + Ok(fvk) => fvk, + _ => { + eprintln!("Invalid viewing key entered"); + continue; + } + }; + let vk = ExtendedFullViewingKey::from(fvk).fvk.vk; + // Use the given viewing key to decrypt pinned transaction data + balance = ctx + .shielded + .compute_exchanged_pinned_balance( + &args.query.ledger_address, + owner, + &vk, + ) + .await + } + // Now print out the received quantities according to CLI arguments + match (balance, args.token.as_ref()) { + (Err(PinnedBalanceError::InvalidViewingKey), _) => println!( + "Supplied viewing key cannot decode transactions to given \ + payment address." + ), + (Err(PinnedBalanceError::NoTransactionPinned), _) => { + println!("Payment address {} has not yet been consumed.", owner) + } + (Ok((balance, epoch)), Some(token)) => { + let token = ctx.get(token); + // Extract and print only the specified token from the total + let (_asset_type, balance) = + value_by_address(&balance, token.clone(), epoch); + let currency_code = tokens + .get(&token) + .map(|c| Cow::Borrowed(*c)) + .unwrap_or_else(|| Cow::Owned(token.to_string())); + if balance == 0 { + println!( + "Payment address {} was consumed during epoch {}. \ + Received no shielded {}", + owner, epoch, currency_code + ); + } else { + let asset_value = token::Amount::from(balance as u64); + println!( + "Payment address {} was consumed during epoch {}. \ + Received {} {}", + owner, epoch, asset_value, currency_code + ); + } + } + (Ok((balance, epoch)), None) => { + let mut found_any = false; + // Print balances by human-readable token names + let balance = ctx + .shielded + .decode_amount(client.clone(), balance, epoch) + .await; + for (addr, value) in balance.components() { + let asset_value = token::Amount::from(*value as u64); + if !found_any { + println!( + "Payment address {} was consumed during epoch {}. \ + Received:", + owner, epoch + ); + found_any = true; + } + let addr_enc = addr.encode(); + println!( + " {}: {}", + tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + asset_value, + ); + } + if !found_any { + println!( + "Payment address {} was consumed during epoch {}. \ + Received no shielded assets.", + owner, epoch + ); + } + } + } + } +} + /// Query Proposals pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { async fn print_proposal( @@ -283,12 +764,247 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { } } +/// Get the component of the given amount corresponding to the given token +pub fn value_by_address( + amt: &Amount, + token: Address, + epoch: Epoch, +) -> (AssetType, i64) { + // Compute the unique asset identifier from the token address + let asset_type = AssetType::new( + (token, epoch.0) + .try_to_vec() + .expect("token addresses should serialize") + .as_ref(), + ) + .unwrap(); + (asset_type, amt[&asset_type]) +} + +/// Query token shielded balance(s) +pub async fn query_shielded_balance( + ctx: &mut Context, + args: args::QueryBalance, +) { + // Used to control whether balances for all keys or a specific key are + // printed + let owner = args + .owner + .and_then(|x| ctx.get_cached(&x).full_viewing_key()); + // Viewing keys are used to query shielded balances. If a spending key is + // provided, then convert to a viewing key first. + let viewing_keys = match owner { + Some(viewing_key) => vec![viewing_key], + None => ctx.wallet.get_viewing_keys().values().copied().collect(), + }; + // Build up the context that will be queried for balances + let _ = ctx.shielded.load(); + let fvks: Vec<_> = viewing_keys + .iter() + .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) + .collect(); + ctx.shielded + .fetch(&args.query.ledger_address, &[], &fvks) + .await; + // Save the update state so that future fetches can be short-circuited + let _ = ctx.shielded.save(); + // The epoch is required to identify timestamped tokens + let epoch = query_epoch(args.query.clone()).await; + // Establish connection with which to do exchange rate queries + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + // Map addresses to token names + let tokens = address::tokens(); + match (args.token, owner.is_some()) { + // Here the user wants to know the balance for a specific token + (Some(token), true) => { + // Query the multi-asset balance at the given spending key + let viewing_key = + ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; + let balance = ctx + .shielded + .compute_exchanged_balance(client.clone(), &viewing_key, epoch) + .await + .expect("context should contain viewing key"); + // Compute the unique asset identifier from the token address + let token = ctx.get(&token); + let asset_type = AssetType::new( + (token.clone(), epoch.0) + .try_to_vec() + .expect("token addresses should serialize") + .as_ref(), + ) + .unwrap(); + let currency_code = tokens + .get(&token) + .map(|c| Cow::Borrowed(*c)) + .unwrap_or_else(|| Cow::Owned(token.to_string())); + if balance[&asset_type] == 0 { + println!( + "No shielded {} balance found for given key", + currency_code + ); + } else { + let asset_value = + token::Amount::from(balance[&asset_type] as u64); + println!("{}: {}", currency_code, asset_value); + } + } + // Here the user wants to know the balance of all tokens across users + (None, false) => { + // Maps asset types to balances divided by viewing key + let mut balances = HashMap::new(); + for fvk in viewing_keys { + // Query the multi-asset balance at the given spending key + let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; + let balance = ctx + .shielded + .compute_exchanged_balance( + client.clone(), + &viewing_key, + epoch, + ) + .await + .expect("context should contain viewing key"); + for (asset_type, value) in balance.components() { + if !balances.contains_key(asset_type) { + balances.insert(*asset_type, Vec::new()); + } + balances.get_mut(asset_type).unwrap().push((fvk, *value)); + } + } + + // These are the asset types for which we have human-readable names + let mut read_tokens = HashSet::new(); + // Print non-zero balances whose asset types can be decoded + for (asset_type, balances) in balances { + // Decode the asset type + let decoded = ctx + .shielded + .decode_asset_type(client.clone(), asset_type) + .await; + match decoded { + Some((addr, asset_epoch)) if asset_epoch == epoch => { + // Only assets with the current timestamp count + let addr_enc = addr.encode(); + println!( + "Shielded Token {}:", + tokens + .get(&addr) + .cloned() + .unwrap_or(addr_enc.as_str()) + ); + read_tokens.insert(addr); + } + _ => continue, + } + + let mut found_any = false; + for (fvk, value) in balances { + let value = token::Amount::from(value as u64); + println!(" {}, owned by {}", value, fvk); + found_any = true; + } + if !found_any { + println!( + "No shielded {} balance found for any wallet key", + asset_type + ); + } + } + // Print zero balances for remaining assets + for (token, currency_code) in tokens { + if !read_tokens.contains(&token) { + println!("Shielded Token {}:", currency_code); + println!( + "No shielded {} balance found for any wallet key", + currency_code + ); + } + } + } + // Here the user wants to know the balance for a specific token across + // users + (Some(token), false) => { + // Compute the unique asset identifier from the token address + let token = ctx.get(&token); + let asset_type = AssetType::new( + (token.clone(), epoch.0) + .try_to_vec() + .expect("token addresses should serialize") + .as_ref(), + ) + .unwrap(); + let currency_code = tokens + .get(&token) + .map(|c| Cow::Borrowed(*c)) + .unwrap_or_else(|| Cow::Owned(token.to_string())); + println!("Shielded Token {}:", currency_code); + let mut found_any = false; + for fvk in viewing_keys { + // Query the multi-asset balance at the given spending key + let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; + let balance = ctx + .shielded + .compute_exchanged_balance( + client.clone(), + &viewing_key, + epoch, + ) + .await + .expect("context should contain viewing key"); + if balance[&asset_type] != 0 { + let asset_value = + token::Amount::from(balance[&asset_type] as u64); + println!(" {}, owned by {}", asset_value, fvk); + found_any = true; + } + } + if !found_any { + println!( + "No shielded {} balance found for any wallet key", + currency_code + ); + } + } + // Here the user wants to know all possible token balances for a key + (None, true) => { + // Query the multi-asset balance at the given spending key + let viewing_key = + ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; + let balance = ctx + .shielded + .compute_exchanged_balance(client.clone(), &viewing_key, epoch) + .await + .expect("context should contain viewing key"); + let mut found_any = false; + // Print balances by human-readable token names + let balance = ctx + .shielded + .decode_amount(client.clone(), balance, epoch) + .await; + for (addr, value) in balance.components() { + let asset_value = token::Amount::from(*value as u64); + let addr_enc = addr.encode(); + println!( + "{}: {}", + tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + asset_value + ); + found_any = true; + } + if !found_any { + println!("No shielded balance found for given key"); + } + } + } +} + /// Query token amount of owner. pub async fn get_token_balance( client: &HttpClient, token: &Address, owner: &Address, -) -> Option { +) -> Option { let balance_key = balance_key(token, owner); query_storage_value(client, &balance_key).await } @@ -447,7 +1163,7 @@ pub async fn query_protocol_parameters( ); let key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_fund = query_storage_value::(&client, &key) + let min_proposal_fund = query_storage_value::(&client, &key) .await .expect("Parameter should be definied."); println!("{:4}Min. proposal funds: {}", "", min_proposal_fund); @@ -501,9 +1217,10 @@ pub async fn query_protocol_parameters( println!("Treasury parameters"); let key = treasury_storage::get_max_transferable_fund_key(); - let max_transferable_amount = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); + let max_transferable_amount = + query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); println!( "{:4}Max. transferable amount: {}", "", max_transferable_amount @@ -1277,6 +1994,37 @@ fn process_unbonds_query( (total, withdrawable) } +/// Query a conversion. +pub async fn query_conversion( + client: HttpClient, + asset_type: AssetType, +) -> Option<(Address, Epoch, Amount, MerklePath)> { + let path = Path::Conversion(asset_type); + let data = vec![]; + let response = client + .abci_query(Some(path.into()), data, None, false) + .await + .unwrap(); + match response.code { + Code::Ok => match BorshDeserialize::try_from_slice(&response.value[..]) + { + Ok(value) => return Some(value), + Err(err) => eprintln!("Error decoding the conversion: {}", err), + }, + Code::Err(err) => { + if err == 1 { + return None; + } else { + eprintln!( + "Error in the query {} (error code {})", + response.info, err + ) + } + } + } + cli::safe_exit(1) +} + /// Query a storage value and decode it with [`BorshDeserialize`]. pub async fn query_storage_value( client: &HttpClient, @@ -1558,9 +2306,9 @@ pub async fn get_proposal_votes( query_storage_prefix::(client.clone(), vote_prefix_key) .await; - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_validators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap = HashMap::new(); + let mut nay_delegators: HashMap = HashMap::new(); if let Some(vote_iter) = vote_iter { for (key, vote) in vote_iter { @@ -1612,9 +2360,9 @@ pub async fn get_proposal_offline_votes( let proposal_hash = proposal.compute_hash(); - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_validators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap = HashMap::new(); + let mut nay_delegators: HashMap = HashMap::new(); for path in files { let file = File::open(&path).expect("Proposal file must exist."); @@ -1701,7 +2449,7 @@ pub async fn compute_tally( nay_delegators, } = votes; - let mut total_yay_stacked_tokens = Amount::from(0); + let mut total_yay_stacked_tokens = token::Amount::from(0); for (_, amount) in yay_validators.clone().into_iter() { total_yay_stacked_tokens += amount; } @@ -1789,7 +2537,7 @@ pub async fn get_total_staked_tokes( epoch: Epoch, validators: &[Address], ) -> token::Amount { - let mut total = Amount::from(0); + let mut total = token::Amount::from(0); for validator in validators { total += get_validator_stake(client, epoch, validator).await; diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index dd86470403..d380e1afa4 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -1,8 +1,6 @@ //! Helpers for making digital signatures using cryptographic keys from the //! wallet. -use std::rc::Rc; - use borsh::BorshSerialize; use namada::proto::Tx; use namada::types::address::{Address, ImplicitAddress}; @@ -12,7 +10,7 @@ use namada::types::transaction::{hash_tx, Fee, WrapperTx}; use tendermint_config::net::Address as TendermintAddress; use super::rpc; -use crate::cli::context::WalletAddress; +use crate::cli::context::{WalletAddress, WalletKeypair}; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxBroadcastData; use crate::wallet::Wallet; @@ -23,7 +21,7 @@ pub async fn find_keypair( wallet: &mut Wallet, addr: &Address, ledger_address: TendermintAddress, -) -> Rc { +) -> common::SecretKey { match addr { Address::Established(_) => { println!( @@ -69,6 +67,60 @@ pub async fn find_keypair( } } +/// Carries types that can be directly/indirectly used to sign a transaction. +#[allow(clippy::large_enum_variant)] +#[derive(Clone)] +pub enum TxSigningKey { + // Do not sign any transaction + None, + // Obtain the actual keypair from wallet and use that to sign + WalletKeypair(WalletKeypair), + // Obtain the keypair corresponding to given address from wallet and sign + WalletAddress(WalletAddress), + // Directly use the given secret key to sign transactions + SecretKey(common::SecretKey), +} + +/// Given CLI arguments and some defaults, determine the rightful transaction +/// signer. Return the given signing key or public key of the given signer if +/// possible. If no explicit signer given, use the `default`. If no `default` +/// is given, panics. +pub async fn tx_signer( + ctx: &mut Context, + args: &args::Tx, + mut default: TxSigningKey, +) -> common::SecretKey { + // Override the default signing key source if possible + if let Some(signing_key) = &args.signing_key { + default = TxSigningKey::WalletKeypair(signing_key.clone()); + } else if let Some(signer) = &args.signer { + default = TxSigningKey::WalletAddress(signer.clone()); + } + // Now actually fetch the signing key and apply it + match default { + TxSigningKey::WalletKeypair(signing_key) => { + ctx.get_cached(&signing_key) + } + TxSigningKey::WalletAddress(signer) => { + let signer = ctx.get(&signer); + let signing_key = find_keypair( + &mut ctx.wallet, + &signer, + args.ledger_address.clone(), + ) + .await; + signing_key + } + TxSigningKey::SecretKey(signing_key) => signing_key, + TxSigningKey::None => { + panic!( + "All transactions must be signed; please either specify the \ + key or the address from which to look up the signing key." + ); + } + } +} + /// Sign a transaction with a given signing key or public key of a given signer. /// If no explicit signer given, use the `default`. If no `default` is given, /// panics. @@ -81,23 +133,11 @@ pub async fn sign_tx( mut ctx: Context, tx: Tx, args: &args::Tx, - default: Option<&WalletAddress>, + default: TxSigningKey, ) -> (Context, TxBroadcastData) { - let (tx, keypair) = if let Some(signing_key) = &args.signing_key { - let signing_key = ctx.get_cached(signing_key); - (tx.sign(&signing_key), signing_key) - } else if let Some(signer) = args.signer.as_ref().or(default) { - let signer = ctx.get(signer); - let signing_key = - find_keypair(&mut ctx.wallet, &signer, args.ledger_address.clone()) - .await; - (tx.sign(&signing_key), signing_key) - } else { - panic!( - "All transactions must be signed; please either specify the key \ - or the address from which to look up the signing key." - ); - }; + let keypair = tx_signer(&mut ctx, args, default).await; + let tx = tx.sign(&keypair); + let epoch = rpc::query_epoch(args::Query { ledger_address: args.ledger_address.clone(), }) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 6f1fd96707..5094e42407 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1,23 +1,49 @@ use std::borrow::Cow; +use std::collections::hash_map::Entry; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryFrom; use std::env; -use std::fs::File; +use std::fmt::Debug; +use std::fs::{File, OpenOptions}; +use std::io::{Read, Write}; +use std::path::PathBuf; use std::time::Duration; use async_std::io::{self, WriteExt}; -use borsh::BorshSerialize; +use borsh::{BorshDeserialize, BorshSerialize}; use itertools::Either::*; +use masp_primitives::asset_type::AssetType; +use masp_primitives::consensus::{BranchId, TestNetwork}; +use masp_primitives::convert::AllowedConversion; +use masp_primitives::ff::PrimeField; +use masp_primitives::group::cofactor::CofactorGroup; +use masp_primitives::keys::FullViewingKey; +use masp_primitives::legacy::TransparentAddress; +use masp_primitives::merkle_tree::{ + CommitmentTree, IncrementalWitness, MerklePath, +}; +use masp_primitives::note_encryption::*; +use masp_primitives::primitives::{Diversifier, Note, ViewingKey}; +use masp_primitives::sapling::Node; +use masp_primitives::transaction::builder::{self, *}; +use masp_primitives::transaction::components::{Amount, OutPoint, TxOut}; +use masp_primitives::transaction::Transaction; +use masp_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; +use masp_proofs::prover::LocalTxProver; use namada::ledger::governance::storage as gov_storage; use namada::ledger::pos::{BondId, Bonds, Unbonds}; use namada::proto::Tx; -use namada::types::address::{xan as m1t, Address}; +use namada::types::address::{btc, masp, masp_tx_key, xan as m1t, Address}; use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, }; use namada::types::key::*; +use namada::types::masp::{PaymentAddress, TransferTarget}; use namada::types::nft::{self, Nft, NftToken}; -use namada::types::storage::Epoch; -use namada::types::token::Amount; +use namada::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; +use namada::types::token::{ + Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, +}; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; @@ -25,6 +51,8 @@ use namada::types::transaction::nft::{CreateNft, MintNft}; use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{address, token}; use namada::{ledger, vm}; +use rand_core::{CryptoRng, OsRng, RngCore}; +use sha2::Digest; use tendermint_config::net::Address as TendermintAddress; use tendermint_rpc::endpoint::broadcast::tx_sync::Response; use tendermint_rpc::query::{EventType, Query}; @@ -33,7 +61,8 @@ use tendermint_rpc::{Client, HttpClient}; use super::rpc; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; -use crate::client::signing::{find_keypair, sign_tx}; +use crate::client::rpc::{query_conversion, query_epoch, query_storage_value}; +use crate::client::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; use crate::client::tendermint_rpc_types::{Error, TxBroadcastData, TxResponse}; use crate::client::tendermint_websocket_client::{ Error as WsError, TendermintWebsocketClient, WebSocketAddress, @@ -60,13 +89,24 @@ const VP_NFT: &str = "vp_nft.wasm"; const ENV_VAR_ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT: &str = "ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT"; +/// Env var to point to a dir with MASP parameters. When not specified, +/// the default OS specific path is used. +const ENV_VAR_MASP_PARAMS_DIR: &str = "ANOMA_MASP_PARAMS_DIR"; + +// Circuit names +// TODO these could be exported from masp_proof crate +const MASP_SPEND_NAME: &str = "masp-spend.params"; +const MASP_OUTPUT_NAME: &str = "masp-output.params"; +const MASP_CONVERT_NAME: &str = "masp-convert.params"; + pub async fn submit_custom(ctx: Context, args: args::TxCustom) { let tx_code = ctx.read_wasm(args.code_path); let data = args.data_path.map(|data_path| { std::fs::read(data_path).expect("Expected a file at given data path") }); let tx = Tx::new(tx_code, data); - let (ctx, initialized_accounts) = process_tx(ctx, &args.tx, tx, None).await; + let (ctx, initialized_accounts) = + process_tx(ctx, &args.tx, tx, TxSigningKey::None).await; save_initialized_accounts(ctx, &args.tx, initialized_accounts).await; } @@ -121,7 +161,7 @@ pub async fn submit_update_vp(ctx: Context, args: args::TxUpdateVp) { let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - process_tx(ctx, &args.tx, tx, Some(&args.addr)).await; + process_tx(ctx, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; } pub async fn submit_init_account(mut ctx: Context, args: args::TxInitAccount) { @@ -147,7 +187,8 @@ pub async fn submit_init_account(mut ctx: Context, args: args::TxInitAccount) { let tx = Tx::new(tx_code, Some(data)); let (ctx, initialized_accounts) = - process_tx(ctx, &args.tx, tx, Some(&args.source)).await; + process_tx(ctx, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + .await; save_initialized_accounts(ctx, &args.tx, initialized_accounts).await; } @@ -255,7 +296,8 @@ pub async fn submit_init_validator( let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); let (mut ctx, initialized_accounts) = - process_tx(ctx, &tx_args, tx, Some(&source)).await; + process_tx(ctx, &tx_args, tx, TxSigningKey::WalletAddress(source)) + .await; if !tx_args.dry_run { let (validator_address_alias, validator_address, rewards_address_alias) = match &initialized_accounts[..] { @@ -360,8 +402,1026 @@ pub async fn submit_init_validator( } } -pub async fn submit_transfer(ctx: Context, args: args::TxTransfer) { - let source = ctx.get(&args.source); +/// Make a ViewingKey that can view notes encrypted by given ExtendedSpendingKey +pub fn to_viewing_key(esk: &ExtendedSpendingKey) -> FullViewingKey { + ExtendedFullViewingKey::from(esk).fvk +} + +/// Generate a valid diversifier, i.e. one that has a diversified base. Return +/// also this diversified base. +pub fn find_valid_diversifier( + rng: &mut R, +) -> (Diversifier, masp_primitives::jubjub::SubgroupPoint) { + let mut diversifier; + let g_d; + // Keep generating random diversifiers until one has a diversified base + loop { + let mut d = [0; 11]; + rng.fill_bytes(&mut d); + diversifier = Diversifier(d); + if let Some(val) = diversifier.g_d() { + g_d = val; + break; + } + } + (diversifier, g_d) +} + +/// Determine if using the current note would actually bring us closer to our +/// target +pub fn is_amount_required(src: Amount, dest: Amount, delta: Amount) -> bool { + if delta > Amount::zero() { + let gap = dest - src; + for (asset_type, value) in gap.components() { + if *value > 0 && delta[asset_type] > 0 { + return true; + } + } + } + false +} + +/// An extension of Option's cloned method for pair types +fn cloned_pair((a, b): (&T, &U)) -> (T, U) { + (a.clone(), b.clone()) +} + +/// Errors that can occur when trying to retrieve pinned transaction +#[derive(PartialEq, Eq)] +pub enum PinnedBalanceError { + /// No transaction has yet been pinned to the given payment address + NoTransactionPinned, + /// The supplied viewing key does not recognize payments to given address + InvalidViewingKey, +} + +/// Represents the amount used of different conversions +pub type Conversions = + HashMap, i64)>; + +/// Represents the changes that were made to a list of transparent accounts +pub type TransferDelta = HashMap>; + +/// Represents the changes that were made to a list of shielded accounts +pub type TransactionDelta = HashMap; + +/// Represents the current state of the shielded pool from the perspective of +/// the chosen viewing keys. +#[derive(BorshSerialize, BorshDeserialize, Debug)] +pub struct ShieldedContext { + /// Location where this shielded context is saved + #[borsh_skip] + context_dir: PathBuf, + /// The last transaction index to be processed in this context + last_txidx: u64, + /// The commitment tree produced by scanning all transactions up to tx_pos + tree: CommitmentTree, + /// Maps viewing keys to applicable note positions + pos_map: HashMap>, + /// Maps a nullifier to the note position to which it applies + nf_map: HashMap<[u8; 32], usize>, + /// Maps note positions to their corresponding notes + note_map: HashMap, + /// Maps note positions to their corresponding memos + memo_map: HashMap, + /// Maps note positions to the diversifier of their payment address + div_map: HashMap, + /// Maps note positions to their witness (used to make merkle paths) + witness_map: HashMap>, + /// Tracks what each transaction does to various account balances + delta_map: BTreeMap< + (BlockHeight, TxIndex), + (Epoch, TransferDelta, TransactionDelta), + >, + /// The set of note positions that have been spent + spents: HashSet, + /// Maps asset types to their decodings + asset_types: HashMap, + /// Maps note positions to their corresponding viewing keys + vk_map: HashMap, +} + +/// Shielded context file name +const FILE_NAME: &str = "shielded.dat"; +const TMP_FILE_NAME: &str = "shielded.tmp"; + +/// Default implementation to ease construction of TxContexts. Derive cannot be +/// used here due to CommitmentTree not implementing Default. +impl Default for ShieldedContext { + fn default() -> ShieldedContext { + ShieldedContext { + context_dir: PathBuf::from(FILE_NAME), + last_txidx: u64::default(), + tree: CommitmentTree::empty(), + pos_map: HashMap::default(), + nf_map: HashMap::default(), + note_map: HashMap::default(), + memo_map: HashMap::default(), + div_map: HashMap::default(), + witness_map: HashMap::default(), + spents: HashSet::default(), + delta_map: BTreeMap::default(), + asset_types: HashMap::default(), + vk_map: HashMap::default(), + } + } +} + +impl ShieldedContext { + /// Try to load the last saved shielded context from the given context + /// directory. If this fails, then leave the current context unchanged. + pub fn load(&mut 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(); + ctx_file.read_to_end(&mut bytes)?; + let mut new_ctx = Self::deserialize(&mut &bytes[..])?; + // Associate the originating context directory with the + // shielded context under construction + new_ctx.context_dir = self.context_dir.clone(); + *self = new_ctx; + Ok(()) + } + + /// Save this shielded context into its associated context directory + pub fn save(&self) -> std::io::Result<()> { + let tmp_path = self.context_dir.join(TMP_FILE_NAME); + { + // First serialize the shielded context into a temporary file. + // Inability to create this file implies a simultaneuous write is in + // progress. In this case, immediately fail. This is unproblematic + // because the data intended to be stored can always be re-fetched + // from the blockchain. + let mut ctx_file = OpenOptions::new() + .write(true) + .create_new(true) + .open(tmp_path.clone())?; + let mut bytes = Vec::new(); + self.serialize(&mut bytes) + .expect("cannot serialize shielded context"); + ctx_file.write_all(&bytes[..])?; + } + // Atomically update the old shielded context file with new data. + // Atomicity is required to prevent other client instances from reading + // corrupt data. + std::fs::rename(tmp_path.clone(), self.context_dir.join(FILE_NAME))?; + // Finally, remove our temporary file to allow future saving of shielded + // contexts. + std::fs::remove_file(tmp_path)?; + Ok(()) + } + + /// Merge data from the given shielded context into the current shielded + /// context. It must be the case that the two shielded contexts share the + /// same last transaction ID and share identical commitment trees. + pub fn merge(&mut self, new_ctx: ShieldedContext) { + debug_assert_eq!(self.last_txidx, new_ctx.last_txidx); + // Merge by simply extending maps. Identical keys should contain + // identical values, so overwriting should not be problematic. + self.pos_map.extend(new_ctx.pos_map); + self.nf_map.extend(new_ctx.nf_map); + self.note_map.extend(new_ctx.note_map); + self.memo_map.extend(new_ctx.memo_map); + self.div_map.extend(new_ctx.div_map); + self.witness_map.extend(new_ctx.witness_map); + self.spents.extend(new_ctx.spents); + self.asset_types.extend(new_ctx.asset_types); + self.vk_map.extend(new_ctx.vk_map); + // The deltas are the exception because different keys can reveal + // different parts of the same transaction. Hence each delta needs to be + // merged separately. + for ((height, idx), (ep, ntfer_delta, ntx_delta)) in new_ctx.delta_map { + let (_ep, tfer_delta, tx_delta) = self + .delta_map + .entry((height, idx)) + .or_insert((ep, TransferDelta::new(), TransactionDelta::new())); + tfer_delta.extend(ntfer_delta); + tx_delta.extend(ntx_delta); + } + } + + /// Fetch the current state of the multi-asset shielded pool into a + /// ShieldedContext + pub async fn fetch( + &mut self, + ledger_address: &TendermintAddress, + sks: &[ExtendedSpendingKey], + fvks: &[ViewingKey], + ) { + // First determine which of the keys requested to be fetched are new. + // Necessary because old transactions will need to be scanned for new + // keys. + let mut unknown_keys = Vec::new(); + for esk in sks { + let vk = to_viewing_key(esk).vk; + if !self.pos_map.contains_key(&vk) { + unknown_keys.push(vk); + } + } + for vk in fvks { + if !self.pos_map.contains_key(vk) { + unknown_keys.push(*vk); + } + } + + // If unknown keys are being used, we need to scan older transactions + // for any unspent notes + let (txs, mut tx_iter); + if !unknown_keys.is_empty() { + // Load all transactions accepted until this point + txs = Self::fetch_shielded_transfers(ledger_address, 0).await; + tx_iter = txs.iter(); + // Do this by constructing a shielding context only for unknown keys + let mut tx_ctx = ShieldedContext::new(self.context_dir.clone()); + for vk in unknown_keys { + tx_ctx.pos_map.entry(vk).or_insert_with(HashSet::new); + } + // Update this unknown shielded context until it is level with self + while tx_ctx.last_txidx != self.last_txidx { + if let Some(((height, idx), (epoch, tx))) = tx_iter.next() { + tx_ctx.scan_tx(*height, *idx, *epoch, tx); + } else { + break; + } + } + // Merge the context data originating from the unknown keys into the + // current context + self.merge(tx_ctx); + } else { + // Load only transactions accepted from last_txid until this point + txs = + Self::fetch_shielded_transfers(ledger_address, self.last_txidx) + .await; + tx_iter = txs.iter(); + } + // Now that we possess the unspent notes corresponding to both old and + // new keys up until tx_pos, proceed to scan the new transactions. + for ((height, idx), (epoch, tx)) in &mut tx_iter { + self.scan_tx(*height, *idx, *epoch, tx); + } + } + + /// Initialize a shielded transaction context that identifies notes + /// decryptable by any viewing key in the given set + pub fn new(context_dir: PathBuf) -> ShieldedContext { + // Make sure that MASP parameters are downloaded to enable MASP + // transaction building and verification later on + let params_dir = + if let Ok(params_dir) = env::var(ENV_VAR_MASP_PARAMS_DIR) { + println!("Using {} as masp parameter folder.", params_dir); + PathBuf::from(params_dir) + } else { + masp_proofs::default_params_folder().unwrap() + }; + let spend_path = params_dir.join(MASP_SPEND_NAME); + let convert_path = params_dir.join(MASP_CONVERT_NAME); + let output_path = params_dir.join(MASP_OUTPUT_NAME); + if !(spend_path.exists() + && convert_path.exists() + && output_path.exists()) + { + println!("MASP parameters not present, downloading..."); + masp_proofs::download_parameters() + .expect("MASP parameters not present or downloadable"); + println!("MASP parameter download complete, resuming execution..."); + } + // Finally initialize a shielded context with the supplied directory + Self { + context_dir, + ..Default::default() + } + } + + /// Obtain a chronologically-ordered list of all accepted shielded + /// transactions from the ledger. The ledger conceptually stores + /// transactions as a vector. More concretely, the HEAD_TX_KEY location + /// stores the index of the last accepted transaction and each transaction + /// is stored at a key derived from its index. + pub async fn fetch_shielded_transfers( + ledger_address: &TendermintAddress, + last_txidx: u64, + ) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, Transfer)> { + let client = HttpClient::new(ledger_address.clone()).unwrap(); + // The address of the MASP account + let masp_addr = masp(); + // Construct the key where last transaction pointer is stored + let head_tx_key = Key::from(masp_addr.to_db_key()) + .push(&HEAD_TX_KEY.to_owned()) + .expect("Cannot obtain a storage key"); + // Query for the index of the last accepted transaction + let head_txidx = query_storage_value::(&client, &head_tx_key) + .await + .unwrap_or(0); + let mut shielded_txs = BTreeMap::new(); + // Fetch all the transactions we do not have yet + for i in last_txidx..head_txidx { + // Construct the key for where the current transaction is stored + let current_tx_key = Key::from(masp_addr.to_db_key()) + .push(&(TX_KEY_PREFIX.to_owned() + &i.to_string())) + .expect("Cannot obtain a storage key"); + // Obtain the current transaction + let (tx_epoch, tx_height, tx_index, current_tx) = + query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( + &client, + ¤t_tx_key, + ) + .await + .unwrap(); + // Collect the current transaction + shielded_txs.insert((tx_height, tx_index), (tx_epoch, current_tx)); + } + shielded_txs + } + + /// Applies the given transaction to the supplied context. More precisely, + /// the shielded transaction's outputs are added to the commitment tree. + /// Newly discovered notes are associated to the supplied viewing keys. Note + /// nullifiers are mapped to their originating notes. Note positions are + /// associated to notes, memos, and diversifiers. And the set of notes that + /// we have spent are updated. The witness map is maintained to make it + /// easier to construct note merkle paths in other code. See + /// https://zips.z.cash/protocol/protocol.pdf#scan + pub fn scan_tx( + &mut self, + height: BlockHeight, + index: TxIndex, + epoch: Epoch, + tx: &Transfer, + ) { + // Ignore purely transparent transactions + let shielded = if let Some(shielded) = &tx.shielded { + shielded + } else { + return; + }; + // For tracking the account changes caused by this Transaction + let mut transaction_delta = TransactionDelta::new(); + // Listen for notes sent to our viewing keys + for so in &shielded.shielded_outputs { + // Create merkle tree leaf node from note commitment + let node = Node::new(so.cmu.to_repr()); + // Update each merkle tree in the witness map with the latest + // addition + for (_, witness) in self.witness_map.iter_mut() { + witness.append(node).expect("note commitment tree is full"); + } + let note_pos = self.tree.size(); + self.tree + .append(node) + .expect("note commitment tree is full"); + // Finally, make it easier to construct merkle paths to this new + // note + let witness = IncrementalWitness::::from_tree(&self.tree); + self.witness_map.insert(note_pos, witness); + // Let's try to see if any of our viewing keys can decrypt latest + // note + for (vk, notes) in self.pos_map.iter_mut() { + let decres = try_sapling_note_decryption::( + 0, + &vk.ivk().0, + &so.ephemeral_key.into_subgroup().unwrap(), + &so.cmu, + &so.enc_ciphertext, + ); + // So this current viewing key does decrypt this current note... + if let Some((note, pa, memo)) = decres { + // Add this note to list of notes decrypted by this viewing + // key + notes.insert(note_pos); + // Compute the nullifier now to quickly recognize when spent + let nf = note.nf(vk, note_pos.try_into().unwrap()); + self.note_map.insert(note_pos, note); + self.memo_map.insert(note_pos, memo); + // The payment address' diversifier is required to spend + // note + self.div_map.insert(note_pos, *pa.diversifier()); + self.nf_map.insert(nf.0, note_pos); + // Note the account changes + let balance = transaction_delta + .entry(*vk) + .or_insert_with(Amount::zero); + *balance += + Amount::from_nonnegative(note.asset_type, note.value) + .expect( + "found note with invalid value or asset type", + ); + self.vk_map.insert(note_pos, *vk); + break; + } + } + } + // Cancel out those of our notes that have been spent + for ss in &shielded.shielded_spends { + // If the shielded spend's nullifier is in our map, then target note + // is rendered unusable + if let Some(note_pos) = self.nf_map.get(&ss.nullifier) { + self.spents.insert(*note_pos); + // Note the account changes + let balance = transaction_delta + .entry(self.vk_map[note_pos]) + .or_insert_with(Amount::zero); + let note = self.note_map[note_pos]; + *balance -= + Amount::from_nonnegative(note.asset_type, note.value) + .expect("found note with invalid value or asset type"); + } + } + // Record the changes to the transparent accounts + let transparent_delta = + Amount::from_nonnegative(tx.token.clone(), u64::from(tx.amount)) + .expect("invalid value for amount"); + let mut transfer_delta = TransferDelta::new(); + transfer_delta + .insert(tx.source.clone(), Amount::zero() - &transparent_delta); + transfer_delta.insert(tx.target.clone(), transparent_delta); + self.delta_map.insert( + (height, index), + (epoch, transfer_delta, transaction_delta), + ); + self.last_txidx += 1; + } + + /// Summarize the effects on shielded and transparent accounts of each + /// Transfer in this context + pub fn get_tx_deltas( + &self, + ) -> &BTreeMap< + (BlockHeight, TxIndex), + (Epoch, TransferDelta, TransactionDelta), + > { + &self.delta_map + } + + /// Compute the total unspent notes associated with the viewing key in the + /// context. If the key is not in the context, then we do not know the + /// balance and hence we return None. + pub fn compute_shielded_balance(&self, vk: &ViewingKey) -> Option { + // Cannot query the balance of a key that's not in the map + if !self.pos_map.contains_key(vk) { + return None; + } + let mut val_acc = Amount::zero(); + // Retrieve the notes that can be spent by this key + if let Some(avail_notes) = self.pos_map.get(vk) { + for note_idx in avail_notes { + // Spent notes cannot contribute a new transaction's pool + if self.spents.contains(note_idx) { + continue; + } + // Get note associated with this ID + let note = self.note_map.get(note_idx).unwrap(); + // Finally add value to multi-asset accumulator + val_acc += + Amount::from_nonnegative(note.asset_type, note.value) + .expect("found note with invalid value or asset type"); + } + } + Some(val_acc) + } + + /// Query the ledger for the decoding of the given asset type and cache it + /// if it is found. + pub async fn decode_asset_type( + &mut self, + client: HttpClient, + asset_type: AssetType, + ) -> Option<(Address, Epoch)> { + // Try to find the decoding in the cache + if let decoded @ Some(_) = self.asset_types.get(&asset_type) { + return decoded.cloned(); + } + // Query for the ID of the last accepted transaction + let (addr, ep, _conv, _path): (Address, _, Amount, MerklePath) = + query_conversion(client, asset_type).await?; + self.asset_types.insert(asset_type, (addr.clone(), ep)); + Some((addr, ep)) + } + + /// Query the ledger for the conversion that is allowed for the given asset + /// type and cache it. + async fn query_allowed_conversion<'a>( + &mut self, + client: HttpClient, + asset_type: AssetType, + conversions: &'a mut Conversions, + ) -> Option<&'a mut (AllowedConversion, MerklePath, i64)> { + match conversions.entry(asset_type) { + Entry::Occupied(conv_entry) => Some(conv_entry.into_mut()), + Entry::Vacant(conv_entry) => { + // Query for the ID of the last accepted transaction + let (addr, ep, conv, path): (Address, _, _, _) = + query_conversion(client, asset_type).await?; + self.asset_types.insert(asset_type, (addr, ep)); + // If the conversion is 0, then we just have a pure decoding + if conv == Amount::zero() { + None + } else { + Some(conv_entry.insert((Amount::into(conv), path, 0))) + } + } + } + } + + /// Compute the total unspent notes associated with the viewing key in the + /// context and express that value in terms of the currently timestamped + /// asset types. If the key is not in the context, then we do not know the + /// balance and hence we return None. + pub async fn compute_exchanged_balance( + &mut self, + client: HttpClient, + vk: &ViewingKey, + target_epoch: Epoch, + ) -> Option { + // First get the unexchanged balance + if let Some(balance) = self.compute_shielded_balance(vk) { + // And then exchange balance into current asset types + Some( + self.compute_exchanged_amount( + client, + balance, + target_epoch, + HashMap::new(), + ) + .await + .0, + ) + } else { + None + } + } + + /// Try to convert as much of the given asset type-value pair using the + /// given allowed conversion. usage is incremented by the amount of the + /// conversion used, the conversions are applied to the given input, and + /// the trace amount that could not be converted is moved from input to + /// output. + fn apply_conversion( + conv: AllowedConversion, + asset_type: AssetType, + value: i64, + usage: &mut i64, + input: &mut Amount, + output: &mut Amount, + ) { + // If conversion if possible, accumulate the exchanged amount + let conv: Amount = conv.into(); + // The amount required of current asset to qualify for conversion + let threshold = -conv[&asset_type]; + // We should use an amount of the AllowedConversion that almost + // cancels the original amount + let required = value / threshold; + // Forget about the trace amount left over because we cannot + // realize its value + let trace = Amount::from_pair(asset_type, value % threshold).unwrap(); + // Record how much more of the given conversion has been used + *usage += required; + // Apply the conversions to input and move the trace amount to output + *input += conv * required - &trace; + *output += trace; + } + + /// Convert the given amount into the latest asset types whilst making a + /// note of the conversions that were used. Note that this function does + /// not assume that allowed conversions from the ledger are expressed in + /// terms of the latest asset types. + pub async fn compute_exchanged_amount( + &mut self, + client: HttpClient, + mut input: Amount, + target_epoch: Epoch, + mut conversions: Conversions, + ) -> (Amount, Conversions) { + // Where we will store our exchanged value + let mut output = Amount::zero(); + // Repeatedly exchange assets until it is no longer possible + while let Some((asset_type, value)) = + input.components().next().map(cloned_pair) + { + let target_asset_type = self + .decode_asset_type(client.clone(), asset_type) + .await + .map(|(addr, _epoch)| make_asset_type(target_epoch, &addr)) + .unwrap_or(asset_type); + if let (Some((conv, _wit, usage)), false) = ( + self.query_allowed_conversion( + client.clone(), + asset_type, + &mut conversions, + ) + .await, + asset_type == target_asset_type, + ) { + // Not at the target asset type, not at the latest asset type. + // Apply conversion to get from current asset type to the latest + // asset type. + Self::apply_conversion( + conv.clone(), + asset_type, + value, + usage, + &mut input, + &mut output, + ); + } else if let (Some((conv, _wit, usage)), false) = ( + self.query_allowed_conversion( + client.clone(), + target_asset_type, + &mut conversions, + ) + .await, + asset_type == target_asset_type, + ) { + // Not at the target asset type, yes at the latest asset type. + // Apply inverse conversion to get from latest asset type to + // the target asset type. + Self::apply_conversion( + conv.clone(), + asset_type, + value, + usage, + &mut input, + &mut output, + ); + } else { + // At the target asset type. Then move component over to output. + let comp = input.project(asset_type); + output += ∁ + // Strike from input to avoid repeating computation + input -= comp; + } + } + (output, conversions) + } + + /// Collect enough unspent notes in this context to exceed the given amount + /// of the specified asset type. Return the total value accumulated plus + /// notes and the corresponding diversifiers/merkle paths that were used to + /// achieve the total value. + pub async fn collect_unspent_notes( + &mut self, + ledger_address: TendermintAddress, + vk: &ViewingKey, + target: Amount, + target_epoch: Epoch, + ) -> ( + Amount, + Vec<(Diversifier, Note, MerklePath)>, + Conversions, + ) { + // Establish connection with which to do exchange rate queries + let client = HttpClient::new(ledger_address.clone()).unwrap(); + let mut conversions = HashMap::new(); + let mut val_acc = Amount::zero(); + let mut notes = Vec::new(); + // Retrieve the notes that can be spent by this key + if let Some(avail_notes) = self.pos_map.get(vk).cloned() { + for note_idx in &avail_notes { + // No more transaction inputs are required once we have met + // the target amount + if val_acc >= target { + break; + } + // Spent notes cannot contribute a new transaction's pool + if self.spents.contains(note_idx) { + continue; + } + // Get note, merkle path, diversifier associated with this ID + let note = *self.note_map.get(note_idx).unwrap(); + + // The amount contributed by this note before conversion + let pre_contr = Amount::from_pair(note.asset_type, note.value) + .expect("received note has invalid value or asset type"); + let (contr, proposed_convs) = self + .compute_exchanged_amount( + client.clone(), + pre_contr, + target_epoch, + conversions.clone(), + ) + .await; + + // Use this note only if it brings us closer to our target + if is_amount_required( + val_acc.clone(), + target.clone(), + contr.clone(), + ) { + // Be sure to record the conversions used in computing + // accumulated value + val_acc += contr; + // Commit the conversions that were used to exchange + conversions = proposed_convs; + let merkle_path = + self.witness_map.get(note_idx).unwrap().path().unwrap(); + let diversifier = self.div_map.get(note_idx).unwrap(); + // Commit this note to our transaction + notes.push((*diversifier, note, merkle_path)); + } + } + } + (val_acc, notes, conversions) + } + + /// Compute the combined value of the output notes of the transaction pinned + /// at the given payment address. This computation uses the supplied viewing + /// keys to try to decrypt the output notes. If no transaction is pinned at + /// the given payment address fails with + /// `PinnedBalanceError::NoTransactionPinned`. + pub async fn compute_pinned_balance( + ledger_address: &TendermintAddress, + owner: PaymentAddress, + viewing_key: &ViewingKey, + ) -> Result<(Amount, Epoch), PinnedBalanceError> { + // Check that the supplied viewing key corresponds to given payment + // address + let counter_owner = viewing_key.to_payment_address( + *masp_primitives::primitives::PaymentAddress::diversifier( + &owner.into(), + ), + ); + match counter_owner { + Some(counter_owner) if counter_owner == owner.into() => {} + _ => return Err(PinnedBalanceError::InvalidViewingKey), + } + let client = HttpClient::new(ledger_address.clone()).unwrap(); + // The address of the MASP account + let masp_addr = masp(); + // Construct the key for where the transaction ID would be stored + let pin_key = Key::from(masp_addr.to_db_key()) + .push(&(PIN_KEY_PREFIX.to_owned() + &owner.hash())) + .expect("Cannot obtain a storage key"); + // Obtain the transaction pointer at the key + let txidx = query_storage_value::(&client, &pin_key) + .await + .ok_or(PinnedBalanceError::NoTransactionPinned)?; + // Construct the key for where the pinned transaction is stored + let tx_key = Key::from(masp_addr.to_db_key()) + .push(&(TX_KEY_PREFIX.to_owned() + &txidx.to_string())) + .expect("Cannot obtain a storage key"); + // Obtain the pointed to transaction + let (tx_epoch, _tx_height, _tx_index, tx) = + query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( + &client, &tx_key, + ) + .await + .expect("Ill-formed epoch, transaction pair"); + // Accumulate the combined output note value into this Amount + let mut val_acc = Amount::zero(); + let tx = tx + .shielded + .expect("Pinned Transfers should have shielded part"); + for so in &tx.shielded_outputs { + // Let's try to see if our viewing key can decrypt current note + let decres = try_sapling_note_decryption::( + 0, + &viewing_key.ivk().0, + &so.ephemeral_key.into_subgroup().unwrap(), + &so.cmu, + &so.enc_ciphertext, + ); + match decres { + // So the given viewing key does decrypt this current note... + Some((note, pa, _memo)) if pa == owner.into() => { + val_acc += + Amount::from_nonnegative(note.asset_type, note.value) + .expect( + "found note with invalid value or asset type", + ); + break; + } + _ => {} + } + } + Ok((val_acc, tx_epoch)) + } + + /// Compute the combined value of the output notes of the pinned transaction + /// at the given payment address if there's any. The asset types may be from + /// the epoch of the transaction or even before, so exchange all these + /// amounts to the epoch of the transaction in order to get the value that + /// would have been displayed in the epoch of the transaction. + pub async fn compute_exchanged_pinned_balance( + &mut self, + ledger_address: &TendermintAddress, + owner: PaymentAddress, + viewing_key: &ViewingKey, + ) -> Result<(Amount, Epoch), PinnedBalanceError> { + // Obtain the balance that will be exchanged + let (amt, ep) = + Self::compute_pinned_balance(ledger_address, owner, viewing_key) + .await?; + // Establish connection with which to do exchange rate queries + let client = HttpClient::new(ledger_address.clone()).unwrap(); + // Finally, exchange the balance to the transaction's epoch + Ok(( + self.compute_exchanged_amount(client, amt, ep, HashMap::new()) + .await + .0, + ep, + )) + } + + /// Convert an amount whose units are AssetTypes to one whose units are + /// Addresses that they decode to. All asset types not corresponding to + /// the given epoch are ignored. + pub async fn decode_amount( + &mut self, + client: HttpClient, + amt: Amount, + target_epoch: Epoch, + ) -> Amount
{ + let mut res = Amount::zero(); + for (asset_type, val) in amt.components() { + // Decode the asset type + let decoded = + self.decode_asset_type(client.clone(), *asset_type).await; + // Only assets with the target timestamp count + match decoded { + Some((addr, epoch)) if epoch == target_epoch => { + res += &Amount::from_pair(addr, *val).unwrap() + } + _ => {} + } + } + res + } +} + +/// Make asset type corresponding to given address and epoch +fn make_asset_type(epoch: Epoch, token: &Address) -> AssetType { + // Typestamp the chosen token with the current epoch + let token_bytes = (token, epoch.0) + .try_to_vec() + .expect("token should serialize"); + // Generate the unique asset identifier from the unique token address + AssetType::new(token_bytes.as_ref()).expect("unable to create asset type") +} + +/// Convert Anoma amount and token type to MASP equivalents +fn convert_amount( + epoch: Epoch, + token: &Address, + val: token::Amount, +) -> (AssetType, Amount) { + let asset_type = make_asset_type(epoch, token); + // Combine the value and unit into one amount + let amount = Amount::from_nonnegative(asset_type, u64::from(val)) + .expect("invalid value for amount"); + (asset_type, amount) +} + +/// Make shielded components to embed within a Transfer object. If no shielded +/// payment address nor spending key is specified, then no shielded components +/// are produced. Otherwise a transaction containing nullifiers and/or note +/// commitments are produced. Dummy transparent UTXOs are sometimes used to make +/// transactions balanced, but it is understood that transparent account changes +/// are effected only by the amounts and signatures specified by the containing +/// Transfer object. +async fn gen_shielded_transfer( + ctx: &mut Context, + args: &args::TxTransfer, + shielded_gas: bool, +) -> Result, builder::Error> { + // No shielded components are needed when neither source nor destination + // are shielded + let spending_key = ctx.get_cached(&args.source).spending_key(); + let payment_address = ctx.get(&args.target).payment_address(); + if spending_key.is_none() && payment_address.is_none() { + return Ok(None); + } + // We want to fund our transaction solely from supplied spending key + let spending_key = spending_key.map(|x| x.into()); + let spending_keys: Vec<_> = spending_key.into_iter().collect(); + // Load the current shielded context given the spending key we possess + let _ = ctx.shielded.load(); + ctx.shielded + .fetch(&args.tx.ledger_address, &spending_keys, &[]) + .await; + // Save the update state so that future fetches can be short-circuited + let _ = ctx.shielded.save(); + // Determine epoch in which to submit potential shielded transaction + let epoch = query_epoch(args::Query { + ledger_address: args.tx.ledger_address.clone(), + }) + .await; + // Context required for storing which notes are in the source's possesion + let consensus_branch_id = BranchId::Sapling; + let amt: u64 = args.amount.into(); + let memo: Option = None; + + // Now we build up the transaction within this object + let mut builder = Builder::::new(0u32); + // Convert transaction amount into MASP types + let (asset_type, amount) = + convert_amount(epoch, &ctx.get(&args.token), args.amount); + // If there are shielded inputs + if let Some(sk) = spending_key { + // Transaction fees need to match the amount in the wrapper Transfer + // when MASP source is used + let (_, fee) = convert_amount( + epoch, + &ctx.get(&args.tx.fee_token), + args.tx.fee_amount, + ); + builder.set_fee(fee.clone())?; + // If the gas is coming from the shielded pool, then our shielded inputs + // must also cover the gas fee + let required_amt = if shielded_gas { amount + fee } else { amount }; + // Locate unspent notes that can help us meet the transaction amount + let (_, unspent_notes, used_convs) = ctx + .shielded + .collect_unspent_notes( + args.tx.ledger_address.clone(), + &to_viewing_key(&sk).vk, + required_amt, + epoch, + ) + .await; + // Commit the notes found to our transaction + for (diversifier, note, merkle_path) in unspent_notes { + builder.add_sapling_spend(sk, diversifier, note, merkle_path)?; + } + // Commit the conversion notes used during summation + for (conv, wit, value) in used_convs.values() { + if *value > 0 { + builder.add_convert( + conv.clone(), + *value as u64, + wit.clone(), + )?; + } + } + } else { + // No transfer fees come from the shielded transaction for non-MASP + // sources + builder.set_fee(Amount::zero())?; + // We add a dummy UTXO to our transaction, but only the source of the + // parent Transfer object is used to validate fund availability + let secp_sk = + secp256k1::SecretKey::from_slice(&[0xcd; 32]).expect("secret key"); + let secp_ctx = secp256k1::Secp256k1::::gen_new(); + let secp_pk = + secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) + .serialize(); + let hash = + ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); + let script = TransparentAddress::PublicKey(hash.into()).script(); + builder.add_transparent_input( + secp_sk, + OutPoint::new([0u8; 32], 0), + TxOut { + asset_type, + value: amt, + script_pubkey: script, + }, + )?; + } + // Now handle the outputs of this transaction + // If there is a shielded output + if let Some(pa) = payment_address { + let ovk_opt = spending_key.map(|x| x.expsk.ovk); + builder.add_sapling_output( + ovk_opt, + pa.into(), + asset_type, + amt, + memo, + )?; + } else { + // Embed the transparent target address into the shielded transaction so + // that it can be signed + let target = ctx.get(&args.target); + let target_enc = target + .address() + .expect("target address should be transparent") + .try_to_vec() + .expect("target address encoding"); + let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( + target_enc.as_ref(), + )); + builder.add_transparent_output( + &TransparentAddress::PublicKey(hash.into()), + asset_type, + amt, + )?; + } + let prover = if let Ok(params_dir) = env::var(ENV_VAR_MASP_PARAMS_DIR) { + let params_dir = PathBuf::from(params_dir); + let spend_path = params_dir.join(MASP_SPEND_NAME); + let convert_path = params_dir.join(MASP_CONVERT_NAME); + let output_path = params_dir.join(MASP_OUTPUT_NAME); + LocalTxProver::new(&spend_path, &output_path, &convert_path) + } else { + LocalTxProver::with_default_location() + .expect("unable to load MASP Parameters") + }; + // Build and return the constructed transaction + builder.build(consensus_branch_id, &prover).map(Some) +} + +pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { + let source = ctx.get_cached(&args.source).effective_address(); + let target = ctx.get(&args.target).effective_address(); // Check that the source address exists on chain let source_exists = rpc::known_address(&source, args.tx.ledger_address.clone()).await; @@ -371,7 +1431,6 @@ pub async fn submit_transfer(ctx: Context, args: args::TxTransfer) { safe_exit(1) } } - let target = ctx.get(&args.target); // Check that the target address exists on chain let target_exists = rpc::known_address(&target, args.tx.ledger_address.clone()).await; @@ -418,13 +1477,48 @@ pub async fn submit_transfer(ctx: Context, args: args::TxTransfer) { safe_exit(1) } } - } + }; + let tx_code = ctx.read_wasm(TX_TRANSFER_WASM); + let masp_addr = masp(); + // For MASP sources, use a special sentinel key recognized by VPs as default + // signer. Also, if the transaction is shielded, redact the amount and token + // types by setting the transparent value to 0 and token type to a constant. + // This has no side-effect because transaction is to self. + let (default_signer, amount, token) = + if source == masp_addr && target == masp_addr { + (TxSigningKey::SecretKey(masp_tx_key()), 0.into(), btc()) + } else if source == masp_addr { + (TxSigningKey::SecretKey(masp_tx_key()), args.amount, token) + } else { + ( + TxSigningKey::WalletAddress(args.source.to_address()), + args.amount, + token, + ) + }; + // If our chosen signer is the MASP sentinel key, then our shielded inputs + // will need to cover the gas fees. + let chosen_signer = tx_signer(&mut ctx, &args.tx, default_signer.clone()) + .await + .ref_to(); + let shielded_gas = masp_tx_key().ref_to() == chosen_signer; + // Determine whether to pin this transaction to a storage key + let key = match ctx.get(&args.target) { + TransferTarget::PaymentAddress(pa) if pa.is_pinned() => Some(pa.hash()), + _ => None, + }; + let transfer = token::Transfer { source, target, token, - amount: args.amount, + amount, + key, + shielded: gen_shielded_transfer(&mut ctx, &args, shielded_gas) + .await + .unwrap() + .map(|x| x.0), }; tracing::debug!("Transfer data {:?}", transfer); let data = transfer @@ -432,7 +1526,7 @@ pub async fn submit_transfer(ctx: Context, args: args::TxTransfer) { .expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - process_tx(ctx, &args.tx, tx, Some(&args.source)).await; + process_tx(ctx, &args.tx, tx, default_signer).await; } pub async fn submit_init_nft(ctx: Context, args: args::NftCreate) { @@ -447,7 +1541,9 @@ pub async fn submit_init_nft(ctx: Context, args: args::NftCreate) { None => ctx.read_wasm(VP_NFT), }; - let signer = Some(WalletAddress::new(nft.creator.clone().to_string())); + let signer = TxSigningKey::WalletAddress(WalletAddress::new( + nft.creator.clone().to_string(), + )); let data = CreateNft { tag: nft.tag.to_string(), @@ -465,7 +1561,7 @@ pub async fn submit_init_nft(ctx: Context, args: args::NftCreate) { let tx_code = ctx.read_wasm(TX_INIT_NFT); let tx = Tx::new(tx_code, Some(data)); - process_tx(ctx, &args.tx, tx, signer.as_ref()).await; + process_tx(ctx, &args.tx, tx, signer).await; } pub async fn submit_mint_nft(ctx: Context, args: args::NftMint) { @@ -486,7 +1582,9 @@ pub async fn submit_mint_nft(ctx: Context, args: args::NftMint) { } }; - let signer = Some(WalletAddress::new(nft_creator_address.to_string())); + let signer = TxSigningKey::WalletAddress(WalletAddress::new( + nft_creator_address.to_string(), + )); let data = MintNft { address: args.nft_address, @@ -501,7 +1599,7 @@ pub async fn submit_mint_nft(ctx: Context, args: args::NftMint) { let tx_code = ctx.read_wasm(TX_MINT_NFT); let tx = Tx::new(tx_code, Some(data)); - process_tx(ctx, &args.tx, tx, signer.as_ref()).await; + process_tx(ctx, &args.tx, tx, signer).await; } pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { @@ -544,7 +1642,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { }; let min_proposal_funds_key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_funds: Amount = + let min_proposal_funds: token::Amount = rpc::query_storage_value(&client, &min_proposal_funds_key) .await .unwrap(); @@ -559,7 +1657,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { safe_exit(1); } let min_proposal_funds_key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_funds: Amount = + let min_proposal_funds: token::Amount = rpc::query_storage_value(&client, &min_proposal_funds_key) .await .unwrap(); @@ -581,7 +1679,8 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let tx_code = ctx.read_wasm(TX_INIT_PROPOSAL); let tx = Tx::new(tx_code, Some(data)); - process_tx(ctx, &args.tx, tx, Some(&signer)).await; + process_tx(ctx, &args.tx, tx, TxSigningKey::WalletAddress(signer)) + .await; } } @@ -696,7 +1795,13 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { let tx_code = ctx.read_wasm(TX_VOTE_PROPOSAL); let tx = Tx::new(tx_code, Some(data)); - process_tx(ctx, &args.tx, tx, Some(signer)).await; + process_tx( + ctx, + &args.tx, + tx, + TxSigningKey::WalletAddress(signer.clone()), + ) + .await; } None => { eprintln!("Proposal start epoch is not in the storage.") @@ -832,8 +1937,14 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { let data = bond.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - let default_signer = args.source.as_ref().unwrap_or(&args.validator); - process_tx(ctx, &args.tx, tx, Some(default_signer)).await; + let default_signer = args.source.unwrap_or(args.validator); + process_tx( + ctx, + &args.tx, + tx, + TxSigningKey::WalletAddress(default_signer), + ) + .await; } pub async fn submit_unbond(ctx: Context, args: args::Unbond) { @@ -899,8 +2010,14 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - let default_signer = args.source.as_ref().unwrap_or(&args.validator); - process_tx(ctx, &args.tx, tx, Some(default_signer)).await; + let default_signer = args.source.unwrap_or(args.validator); + process_tx( + ctx, + &args.tx, + tx, + TxSigningKey::WalletAddress(default_signer), + ) + .await; } pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { @@ -966,8 +2083,14 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - let default_signer = args.source.as_ref().unwrap_or(&args.validator); - process_tx(ctx, &args.tx, tx, Some(default_signer)).await; + let default_signer = args.source.unwrap_or(args.validator); + process_tx( + ctx, + &args.tx, + tx, + TxSigningKey::WalletAddress(default_signer), + ) + .await; } /// Submit transaction and wait for result. Returns a list of addresses @@ -976,7 +2099,7 @@ async fn process_tx( ctx: Context, args: &args::Tx, tx: Tx, - default_signer: Option<&WalletAddress>, + default_signer: TxSigningKey, ) -> (Context, Vec
) { let (ctx, to_broadcast) = sign_tx(ctx, tx, args, default_signer).await; // NOTE: use this to print the request JSON body: diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index c377648b65..98b0d67704 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -289,7 +289,7 @@ pub async fn join_network( tendermint_node::write_validator_key( &tm_home_dir, &address, - &*pre_genesis_wallet.consensus_key, + &pre_genesis_wallet.consensus_key, ); // Derive the node ID from the node key diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 5bd3dc803f..e9abeb24b2 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -776,7 +776,7 @@ pub fn genesis() -> Genesis { let parameters = Parameters { epoch_duration: EpochDuration { min_num_of_blocks: 10, - min_duration: namada::types::time::Duration::seconds(60).into(), + min_duration: namada::types::time::Duration::seconds(600).into(), }, max_expected_time_per_block: namada::types::time::DurationSecs(30), vp_whitelist: vec![], @@ -803,6 +803,13 @@ pub fn genesis() -> Genesis { public_key: Some(wallet::defaults::christel_keypair().ref_to()), storage: HashMap::default(), }; + let masp = EstablishedAccount { + address: namada::types::address::masp(), + vp_code_path: "vp_masp.wasm".into(), + vp_sha256: Default::default(), + public_key: None, + storage: HashMap::default(), + }; let matchmaker = EstablishedAccount { address: wallet::defaults::matchmaker_address(), vp_code_path: vp_user_path.into(), @@ -853,7 +860,7 @@ pub fn genesis() -> Genesis { Genesis { genesis_time: DateTimeUtc::now(), validators: vec![validator], - established_accounts: vec![albert, bertha, christel, matchmaker], + established_accounts: vec![albert, bertha, christel, masp, matchmaker], implicit_accounts, token_accounts, parameters, diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index e5f191d6e7..cc7bdd5eb5 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -15,6 +15,7 @@ use namada::ledger::treasury::TreasuryVp; use namada::proto::{self, Tx}; use namada::types::address::{Address, InternalAddress}; use namada::types::storage; +use namada::types::storage::TxIndex; use namada::types::transaction::{DecryptedTx, TxResult, TxType, VpsResult}; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::{self, wasm, WasmCacheAccess}; @@ -66,9 +67,11 @@ pub type Result = std::result::Result; /// If the given tx is a successfully decrypted payload apply the necessary /// vps. Otherwise, we include the tx on chain with the gas charge added /// but no further validations. +#[allow(clippy::too_many_arguments)] pub fn apply_tx( tx: TxType, tx_length: usize, + tx_index: TxIndex, block_gas_meter: &mut BlockGasMeter, write_log: &mut WriteLog, storage: &Storage, @@ -89,6 +92,7 @@ where TxType::Decrypted(DecryptedTx::Decrypted(tx)) => { let verifiers = execute_tx( &tx, + &tx_index, storage, block_gas_meter, write_log, @@ -98,6 +102,7 @@ where let vps_result = check_vps( &tx, + &tx_index, storage, block_gas_meter, write_log, @@ -135,6 +140,7 @@ where /// Execute a transaction code. Returns verifiers requested by the transaction. fn execute_tx( tx: &Tx, + tx_index: &TxIndex, storage: &Storage, gas_meter: &mut BlockGasMeter, write_log: &mut WriteLog, @@ -155,6 +161,7 @@ where storage, write_log, gas_meter, + tx_index, &tx.code, tx_data, vp_wasm_cache, @@ -166,6 +173,7 @@ where /// Check the acceptance of a transaction by validity predicates fn check_vps( tx: &Tx, + tx_index: &TxIndex, storage: &Storage, gas_meter: &mut BlockGasMeter, write_log: &WriteLog, @@ -186,6 +194,7 @@ where verifiers, keys_changed, tx, + tx_index, storage, write_log, initial_gas, @@ -201,10 +210,12 @@ where } /// Execute verifiers' validity predicates +#[allow(clippy::too_many_arguments)] fn execute_vps( verifiers: BTreeSet
, keys_changed: BTreeSet, tx: &Tx, + tx_index: &TxIndex, storage: &Storage, write_log: &WriteLog, initial_gas: u64, @@ -237,6 +248,7 @@ where wasm::run::vp( vp, tx, + tx_index, addr, storage, write_log, @@ -252,6 +264,7 @@ where storage, write_log, tx, + tx_index, gas_meter, vp_wasm_cache.clone(), ); diff --git a/apps/src/lib/node/ledger/rpc.rs b/apps/src/lib/node/ledger/rpc.rs index ce3cbd592d..780afef98e 100644 --- a/apps/src/lib/node/ledger/rpc.rs +++ b/apps/src/lib/node/ledger/rpc.rs @@ -3,8 +3,10 @@ use std::fmt::Display; use std::str::FromStr; +use masp_primitives::asset_type::AssetType; use namada::types::address::Address; use namada::types::storage; +use namada::types::token::CONVERSION_KEY_PREFIX; use tendermint::abci::Path as AbciPath; use thiserror::Error; @@ -15,12 +17,16 @@ pub enum Path { DryRunTx, /// Epoch of the last committed block Epoch, + /// Results of all committed blocks + Results, /// Read a storage value with exact storage key Value(storage::Key), /// Read a range of storage values with a matching key prefix Prefix(storage::Key), /// Check if the given storage key exists HasKey(storage::Key), + /// Conversion associated with given asset type + Conversion(AssetType), } #[derive(Debug, Clone)] @@ -36,12 +42,14 @@ const EPOCH_PATH: &str = "epoch"; const VALUE_PREFIX: &str = "value"; const PREFIX_PREFIX: &str = "prefix"; const HAS_KEY_PREFIX: &str = "has_key"; +const RESULTS_PATH: &str = "results"; impl Display for Path { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Path::DryRunTx => write!(f, "{}", DRY_RUN_TX_PATH), Path::Epoch => write!(f, "{}", EPOCH_PATH), + Path::Results => write!(f, "{}", RESULTS_PATH), Path::Value(storage_key) => { write!(f, "{}/{}", VALUE_PREFIX, storage_key) } @@ -51,6 +59,9 @@ impl Display for Path { Path::HasKey(storage_key) => { write!(f, "{}/{}", HAS_KEY_PREFIX, storage_key) } + Path::Conversion(asset_type) => { + write!(f, "{}/{}", CONVERSION_KEY_PREFIX, asset_type) + } } } } @@ -62,6 +73,7 @@ impl FromStr for Path { match s { DRY_RUN_TX_PATH => Ok(Self::DryRunTx), EPOCH_PATH => Ok(Self::Epoch), + RESULTS_PATH => Ok(Self::Results), _ => match s.split_once('/') { Some((VALUE_PREFIX, storage_key)) => { let key = storage::Key::parse(storage_key) @@ -78,6 +90,11 @@ impl FromStr for Path { .map_err(PathParseError::InvalidStorageKey)?; Ok(Self::HasKey(key)) } + Some((CONVERSION_KEY_PREFIX, asset_type)) => { + let key = AssetType::from_str(asset_type) + .map_err(PathParseError::InvalidAssetType)?; + Ok(Self::Conversion(key)) + } _ => Err(PathParseError::InvalidPath(s.to_string())), }, } @@ -100,4 +117,6 @@ pub enum PathParseError { InvalidPath(String), #[error("Invalid storage key: {0}")] InvalidStorageKey(storage::Error), + #[error("Unrecognized asset type: {0}")] + InvalidAssetType(std::io::Error), } diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 1f758a2f2e..806272fd46 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -9,7 +9,7 @@ use namada::ledger::storage::types::encode; use namada::ledger::treasury::ADDRESS as treasury_address; use namada::types::address::{xan as m1t, Address}; use namada::types::governance::TallyResult; -use namada::types::storage::{BlockHash, Epoch, Header}; +use namada::types::storage::{BlockHash, BlockResults, Epoch, Header, TxIndex}; use tendermint_proto::abci::Misbehavior as Evidence; use tendermint_proto::crypto::PublicKey as TendermintPublicKey; @@ -117,6 +117,7 @@ where 0, /* this is used to compute the fee * based on the code size. We dont * need it here. */ + TxIndex(0), &mut BlockGasMeter::default(), &mut self.write_log, &self.storage, @@ -223,7 +224,9 @@ where } } - for processed_tx in &req.txs { + // Tracks the accepted transactions + self.storage.block.results = BlockResults::with_len(req.txs.len()); + for (tx_index, processed_tx) in req.txs.iter().enumerate() { let tx = if let Ok(tx) = Tx::try_from(processed_tx.tx.as_ref()) { tx } else { @@ -332,6 +335,11 @@ where match protocol::apply_tx( tx_type, tx_length, + TxIndex( + tx_index + .try_into() + .expect("transaction index out of bounds"), + ), &mut self.gas_meter, &mut self.write_log, &self.storage, @@ -351,6 +359,7 @@ where self.write_log.commit_tx(); if !tx_event.contains_key("code") { tx_event["code"] = ErrorCodes::Ok.into(); + self.storage.block.results.accept(tx_index); } if let Some(ibc_event) = &result.ibc_event { // Add the IBC event besides the tx_event diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index d93ec23c56..d665e3f7f9 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -31,9 +31,10 @@ use namada::ledger::storage::{ }; use namada::ledger::{ibc, parameters, pos}; use namada::proto::{self, Tx}; +use namada::types::address::{masp, masp_tx_key}; use namada::types::chain::ChainId; use namada::types::key::*; -use namada::types::storage::{BlockHeight, Key}; +use namada::types::storage::{BlockHeight, Key, TxIndex}; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::transaction::{ hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, @@ -567,6 +568,7 @@ where match protocol::apply_tx( tx, tx_bytes.len(), + TxIndex::default(), &mut gas_meter, &mut write_log, &self.storage, @@ -594,7 +596,7 @@ where /// Lookup a validator's keypair for their established account from their /// wallet. If the node is not validator, this function returns None #[allow(dead_code)] - fn get_account_keypair(&self) -> Option> { + fn get_account_keypair(&self) -> Option { let wallet_path = &self.base_dir.join(self.chain_id.as_str()); let genesis_path = &self .base_dir @@ -640,7 +642,7 @@ mod test_utils { use namada::types::chain::ChainId; use namada::types::hash::Hash; use namada::types::key::*; - use namada::types::storage::{BlockHash, Epoch, Header}; + use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; use namada::types::transaction::Fee; use tempfile::tempdir; use tendermint_proto::abci::{RequestInitChain, RequestProcessProposal}; @@ -883,6 +885,7 @@ mod test_utils { next_epoch_min_start_height: BlockHeight(3), next_epoch_min_start_time: DateTimeUtc::now(), address_gen: &address_gen, + results: &BlockResults::default(), tx_queue: &shell.storage.tx_queue, }) .expect("Test failed"); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 25375244ba..77d6577fd7 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -153,9 +153,18 @@ where ), } } else { + // If the public key corresponds to the MASP sentinel + // transaction key, then the fee payer is effectively + // the MASP, otherwise derive + // they payer from public key. + let fee_payer = if tx.pk != masp_tx_key().ref_to() { + tx.fee_payer() + } else { + masp() + }; // check that the fee payer has sufficient balance let balance = self - .get_balance(&tx.fee.token, &tx.fee_payer()) + .get_balance(&tx.fee.token, &fee_payer) .unwrap_or_default(); if tx.fee.amount <= balance { diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index a643501d9e..6c9c7d3a23 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -3,12 +3,14 @@ use std::cmp::max; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; +use masp_primitives::asset_type::AssetType; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::PosParams; +use namada::ledger::storage::types; use namada::types::address::Address; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; -use namada::types::storage::{Key, PrefixValue}; +use namada::types::storage::{BlockResults, Key, PrefixValue}; use namada::types::token::{self, Amount}; use tendermint_proto::crypto::{ProofOp, ProofOps}; use tendermint_proto::google::protobuf; @@ -53,6 +55,10 @@ where ..Default::default() } } + Path::Results => self.read_results(), + Path::Conversion(asset_type) => { + self.read_conversion(&asset_type) + } Path::Value(storage_key) => { self.read_storage_value(&storage_key, height, query.prove) } @@ -69,6 +75,60 @@ where } } + /// Query to read block results from storage + pub fn read_results(&self) -> response::Query { + let (iter, _gas) = self.storage.iter_results(); + let mut results = vec![ + BlockResults::default(); + self.storage.block.height.0 as usize + 1 + ]; + iter.for_each(|(key, value, _gas)| { + let key = key + .parse::() + .expect("expected integer for block height"); + let value = BlockResults::try_from_slice(&value) + .expect("expected BlockResults bytes"); + results[key] = value; + }); + let value = namada::ledger::storage::types::encode(&results); + response::Query { + value, + ..Default::default() + } + } + + /// Query to read a conversion from storage + pub fn read_conversion(&self, asset_type: &AssetType) -> response::Query { + // Conversion values are constructed on request + if let Some((addr, epoch, conv, pos)) = + self.storage.conversion_state.assets.get(asset_type) + { + let conv = ( + addr, + epoch, + Into::::into( + conv.clone(), + ), + self.storage.conversion_state.tree.path(*pos), + ); + response::Query { + value: types::encode(&conv), + proof_ops: None, + ..Default::default() + } + } else { + response::Query { + code: 1, + info: format!( + "No conversion found for asset type: {}", + asset_type + ), + proof_ops: None, + ..Default::default() + } + } + } + /// Simple helper function for the ledger to get balances /// of the specified token at the specified address pub fn get_balance( diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index ffe90c1d61..ea03b5ed5e 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -15,6 +15,8 @@ use super::abcipp_shim_types::shim::request::{FinalizeBlock, ProcessedTx}; use super::abcipp_shim_types::shim::response::TxResult; use super::abcipp_shim_types::shim::{Error, Request, Response}; use crate::config; +#[cfg(feature = "ABCI")] +use crate::node::ledger::shell::Error::TxDecoding; /// The shim wraps the shell, which implements ABCI++. /// The shim makes a crude translation between the ABCI interface currently used diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 71ed305ea5..98d2c4c216 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -38,7 +38,8 @@ use namada::ledger::storage::{ MerkleTreeStoresRead, Result, StoreType, DB, }; use namada::types::storage::{ - BlockHeight, Header, Key, KeySeg, TxQueue, KEY_SEGMENT_SEPARATOR, + BlockHeight, BlockResults, Header, Key, KeySeg, TxQueue, + KEY_SEGMENT_SEPARATOR, }; use namada::types::time::DateTimeUtc; use rocksdb::{ @@ -273,6 +274,17 @@ impl DB for RocksDB { None => return Ok(None), }; + // Block results + let results_path = format!("results/{}", height.raw()); + let results: BlockResults = match self + .0 + .get(results_path) + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => return Ok(None), + }; + // Epoch start height and time let next_epoch_min_start_height: BlockHeight = match self .0 @@ -397,6 +409,7 @@ impl DB for RocksDB { height, epoch, pred_epochs, + results, next_epoch_min_start_height, next_epoch_min_start_time, address_gen, @@ -419,6 +432,7 @@ impl DB for RocksDB { height, epoch, pred_epochs, + results, next_epoch_min_start_height, next_epoch_min_start_time, address_gen, @@ -514,6 +528,11 @@ impl DB for RocksDB { .map_err(Error::KeyError)?; batch.put(key.to_string(), types::encode(&epoch)); } + // Block results + { + let results_path = format!("results/{}", height.raw()); + batch.put(results_path, types::encode(&results)); + } // Predecessor block epochs { let key = prefix_key @@ -824,6 +843,26 @@ impl<'iter> DBIter<'iter> for RocksDB { ); PersistentPrefixIterator(PrefixIterator::new(iter, db_prefix)) } + + fn iter_results(&'iter self) -> PersistentPrefixIterator<'iter> { + let db_prefix = "results/".to_owned(); + let prefix = "results".to_owned(); + + let mut read_opts = ReadOptions::default(); + // don't use the prefix bloom filter + read_opts.set_total_order_seek(true); + let mut upper_prefix = prefix.clone().into_bytes(); + if let Some(last) = upper_prefix.pop() { + upper_prefix.push(last + 1); + } + read_opts.set_iterate_upper_bound(upper_prefix); + + let iter = self.0.iterator_opt( + IteratorMode::From(prefix.as_bytes(), Direction::Forward), + read_opts, + ); + PersistentPrefixIterator(PrefixIterator::new(iter, db_prefix)) + } } #[derive(Debug)] @@ -968,12 +1007,14 @@ mod test { let next_epoch_min_start_time = DateTimeUtc::now(); let address_gen = EstablishedAddressGen::new("whatever"); let tx_queue = TxQueue::default(); + let results = BlockResults::default(); let block = BlockStateWrite { merkle_tree_stores, header: None, hash: &hash, height, epoch, + results: &results, pred_epochs: &pred_epochs, next_epoch_min_start_height, next_epoch_min_start_time, diff --git a/apps/src/lib/node/matchmaker.rs b/apps/src/lib/node/matchmaker.rs index 0a01528b00..4ce5205486 100644 --- a/apps/src/lib/node/matchmaker.rs +++ b/apps/src/lib/node/matchmaker.rs @@ -1,7 +1,6 @@ use std::env; use std::net::SocketAddr; use std::path::{Path, PathBuf}; -use std::rc::Rc; use std::sync::Arc; use borsh::{BorshDeserialize, BorshSerialize}; @@ -35,7 +34,7 @@ pub async fn run( }: config::Matchmaker, intent_gossiper_addr: SocketAddr, ledger_addr: TendermintAddress, - tx_signing_key: Rc, + tx_signing_key: common::SecretKey, tx_source_address: Address, wasm_dir: impl AsRef, ) { @@ -98,7 +97,7 @@ pub struct ResultHandler { /// A source address for transactions created from intents. tx_source_address: Address, /// A keypair that will be used to sign transactions. - tx_signing_key: Rc, + tx_signing_key: common::SecretKey, } /// The loaded implementation's dylib and its state @@ -126,7 +125,7 @@ impl Runner { matchmaker_path: PathBuf, tx_code_path: PathBuf, ledger_address: TendermintAddress, - tx_signing_key: Rc, + tx_signing_key: common::SecretKey, tx_source_address: Address, wasm_dir: impl AsRef, ) -> (Self, ResultHandler) { diff --git a/apps/src/lib/wallet/keys.rs b/apps/src/lib/wallet/keys.rs index 1c521e7515..340a457d73 100644 --- a/apps/src/lib/wallet/keys.rs +++ b/apps/src/lib/wallet/keys.rs @@ -1,11 +1,10 @@ //! Cryptographic keys for digital signatures support for the wallet. use std::fmt::Display; -use std::rc::Rc; +use std::marker::PhantomData; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; -use namada::types::key::*; use orion::{aead, kdf}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -17,17 +16,21 @@ const UNENCRYPTED_KEY_PREFIX: &str = "unencrypted:"; /// A keypair stored in a wallet #[derive(Debug)] -pub enum StoredKeypair { +pub enum StoredKeypair +where + ::Err: Display, +{ /// An encrypted keypair - Encrypted(EncryptedKeypair), + Encrypted(EncryptedKeypair), /// An raw (unencrypted) keypair - Raw( - // Wrapped in `Rc` to avoid reference lifetimes when we borrow the key - Rc, - ), + Raw(T), } -impl Serialize for StoredKeypair { +impl Serialize + for StoredKeypair +where + ::Err: Display, +{ fn serialize( &self, serializer: S, @@ -51,7 +54,11 @@ impl Serialize for StoredKeypair { } } -impl<'de> Deserialize<'de> for StoredKeypair { +impl<'de, T: BorshSerialize + BorshDeserialize + Display + FromStr> + Deserialize<'de> for StoredKeypair +where + ::Err: Display, +{ fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, @@ -68,7 +75,7 @@ impl<'de> Deserialize<'de> for StoredKeypair { .map_err(D::Error::custom)?; if let Some(raw) = keypair_string.strip_prefix(UNENCRYPTED_KEY_PREFIX) { FromStr::from_str(raw) - .map(|keypair| Self::Raw(Rc::new(keypair))) + .map(|keypair| Self::Raw(keypair)) .map_err(|err| { DeserializeStoredKeypairError::InvalidStoredKeypairString( err.to_string(), @@ -104,19 +111,22 @@ pub enum DeserializeStoredKeypairError { /// An encrypted keypair stored in a wallet #[derive(Debug)] -pub struct EncryptedKeypair(Vec); +pub struct EncryptedKeypair( + Vec, + PhantomData, +); -impl Display for EncryptedKeypair { +impl Display for EncryptedKeypair { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", hex::encode(&self.0)) } } -impl FromStr for EncryptedKeypair { +impl FromStr for EncryptedKeypair { type Err = hex::FromHexError; fn from_str(s: &str) -> Result { - hex::decode(s).map(Self) + hex::decode(s).map(|x| Self(x, PhantomData)) } } @@ -133,26 +143,21 @@ pub enum DecryptionError { NotDecrypting, } -impl StoredKeypair { +impl + StoredKeypair +where + ::Err: Display, +{ /// Construct a keypair for storage. If no password is provided, the keypair /// will be stored raw without encryption. Returns the key for storing and a /// reference-counting point to the raw key. - pub fn new( - keypair: common::SecretKey, - password: Option, - ) -> (Self, Rc) { + pub fn new(keypair: T, password: Option) -> (Self, T) { match password { - Some(password) => { - let keypair = Rc::new(keypair); - ( - Self::Encrypted(EncryptedKeypair::new(&keypair, password)), - keypair, - ) - } - None => { - let keypair = Rc::new(keypair); - (Self::Raw(keypair.clone()), keypair) - } + Some(password) => ( + Self::Encrypted(EncryptedKeypair::new(&keypair, password)), + keypair, + ), + None => (Self::Raw(keypair.clone()), keypair), } } @@ -163,7 +168,7 @@ impl StoredKeypair { &self, decrypt: bool, password: Option, - ) -> Result, DecryptionError> { + ) -> Result { match self { StoredKeypair::Encrypted(encrypted_keypair) => { if decrypt { @@ -171,7 +176,7 @@ impl StoredKeypair { read_password("Enter decryption password: ") }); let key = encrypted_keypair.decrypt(password)?; - Ok(Rc::new(key)) + Ok(key) } else { Err(DecryptionError::NotDecrypting) } @@ -188,9 +193,9 @@ impl StoredKeypair { } } -impl EncryptedKeypair { +impl EncryptedKeypair { /// Encrypt a keypair and store it with its salt. - pub fn new(keypair: &common::SecretKey, password: String) -> Self { + pub fn new(keypair: &T, password: String) -> Self { let salt = encryption_salt(); let encryption_key = encryption_key(&salt, password); @@ -203,14 +208,11 @@ impl EncryptedKeypair { let encrypted_data = [salt.as_ref(), &encrypted_keypair].concat(); - Self(encrypted_data) + Self(encrypted_data, PhantomData) } /// Decrypt an encrypted keypair - pub fn decrypt( - &self, - password: String, - ) -> Result { + pub fn decrypt(&self, password: String) -> Result { let salt_len = encryption_salt().len(); let (raw_salt, cipher) = self.0.split_at(salt_len); @@ -222,7 +224,7 @@ impl EncryptedKeypair { let decrypted_data = aead::open(&encryption_key, cipher) .map_err(|_| DecryptionError::DecryptionError)?; - common::SecretKey::try_from_slice(&decrypted_data) + T::try_from_slice(&decrypted_data) .map_err(|_| DecryptionError::DeserializingError) } } diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 5fec7dca98..83637dfe8c 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -5,12 +5,18 @@ pub mod pre_genesis; mod store; use std::collections::HashMap; +use std::fmt::Display; use std::path::{Path, PathBuf}; -use std::rc::Rc; +use std::str::FromStr; use std::{env, fs}; +use borsh::{BorshDeserialize, BorshSerialize}; +use masp_primitives::zip32::ExtendedFullViewingKey; use namada::types::address::Address; use namada::types::key::*; +use namada::types::masp::{ + ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, +}; pub use store::wallet_file; use thiserror::Error; @@ -25,7 +31,8 @@ use crate::config::genesis::genesis_config::GenesisConfig; pub struct Wallet { store_dir: PathBuf, store: Store, - decrypted_key_cache: HashMap>, + decrypted_key_cache: HashMap, + decrypted_spendkey_cache: HashMap, } #[derive(Error, Debug)] @@ -47,6 +54,7 @@ impl Wallet { store_dir: store_dir.to_path_buf(), store, decrypted_key_cache: HashMap::default(), + decrypted_spendkey_cache: HashMap::default(), }) } @@ -61,6 +69,7 @@ impl Wallet { store_dir: store_dir.to_path_buf(), store, decrypted_key_cache: HashMap::default(), + decrypted_spendkey_cache: HashMap::default(), } } @@ -79,6 +88,7 @@ impl Wallet { store_dir: store_dir.to_path_buf(), store, decrypted_key_cache: HashMap::default(), + decrypted_spendkey_cache: HashMap::default(), } } @@ -92,6 +102,30 @@ impl Wallet { self.store.save(&self.store_dir) } + /// Prompt for pssword and confirm it if parameter is false + fn new_password_prompt(unsafe_dont_encrypt: bool) -> Option { + let password = if unsafe_dont_encrypt { + println!("Warning: The keypair will NOT be encrypted."); + None + } else { + Some(read_password("Enter your encryption password: ")) + }; + // Bis repetita for confirmation. + let pwd = if unsafe_dont_encrypt { + None + } else { + Some(read_password( + "To confirm, please enter the same encryption password once \ + more: ", + )) + }; + if pwd != password { + eprintln!("Your two inputs do not match!"); + cli::safe_exit(1) + } + password + } + /// Generate a new keypair and derive an implicit address from its public /// and insert them into the store with the provided alias, converted to /// lower case. If none provided, the alias will be the public key hash (in @@ -103,7 +137,7 @@ impl Wallet { &mut self, alias: Option, unsafe_dont_encrypt: bool, - ) -> (String, Rc) { + ) -> (String, common::SecretKey) { let password = read_and_confirm_pwd(unsafe_dont_encrypt); let (alias, key) = self.store.gen_key(alias, password); // Cache the newly added key @@ -111,6 +145,18 @@ impl Wallet { (alias.into(), key) } + pub fn gen_spending_key( + &mut self, + alias: String, + unsafe_dont_encrypt: bool, + ) -> (String, ExtendedSpendingKey) { + let password = Self::new_password_prompt(unsafe_dont_encrypt); + let (alias, key) = self.store.gen_spending_key(alias, password); + // Cache the newly added key + self.decrypted_spendkey_cache.insert(alias.clone(), key); + (alias.into(), key) + } + /// Generate keypair /// for signing protocol txs and for the DKG (which will also be stored) /// A protocol keypair may be optionally provided, indicating that @@ -126,15 +172,15 @@ impl Wallet { self.store .validator_data .take() - .map(|data| Rc::new(data.keys.protocol_keypair)) + .map(|data| data.keys.protocol_keypair) }) .ok_or(FindKeyError::KeyNotFound) }); match protocol_keypair { Some(Err(err)) => Err(err), - other => Ok(Store::gen_validator_keys( - other.map(|res| res.unwrap().as_ref().clone()), - )), + other => { + Ok(Store::gen_validator_keys(other.map(|res| res.unwrap()))) + } } } @@ -166,7 +212,7 @@ impl Wallet { pub fn find_key( &mut self, alias_pkh_or_pk: impl AsRef, - ) -> Result, FindKeyError> { + ) -> Result { // Try cache first if let Some(cached_key) = self .decrypted_key_cache @@ -186,6 +232,44 @@ impl Wallet { ) } + pub fn find_spending_key( + &mut self, + alias: impl AsRef, + ) -> Result { + // Try cache first + if let Some(cached_key) = + self.decrypted_spendkey_cache.get(&alias.as_ref().into()) + { + return Ok(*cached_key); + } + // If not cached, look-up in store + let stored_spendkey = self + .store + .find_spending_key(alias.as_ref()) + .ok_or(FindKeyError::KeyNotFound)?; + Self::decrypt_stored_key( + &mut self.decrypted_spendkey_cache, + stored_spendkey, + alias.into(), + ) + } + + pub fn find_viewing_key( + &mut self, + alias: impl AsRef, + ) -> Result<&ExtendedViewingKey, FindKeyError> { + self.store + .find_viewing_key(alias.as_ref()) + .ok_or(FindKeyError::KeyNotFound) + } + + pub fn find_payment_addr( + &self, + alias: impl AsRef, + ) -> Option<&PaymentAddress> { + self.store.find_payment_addr(alias.as_ref()) + } + /// Find the stored key by a public key. /// If the key is encrypted, will prompt for password from stdin. /// Any keys that are decrypted are stored in and read from a cache to avoid @@ -193,7 +277,7 @@ impl Wallet { pub fn find_key_by_pk( &mut self, pk: &common::PublicKey, - ) -> Result, FindKeyError> { + ) -> Result { // Try to look-up alias for the given pk. Otherwise, use the PKH string. let pkh: PublicKeyHash = pk.into(); let alias = self @@ -223,7 +307,7 @@ impl Wallet { pub fn find_key_by_pkh( &mut self, pkh: &PublicKeyHash, - ) -> Result, FindKeyError> { + ) -> Result { // Try to look-up alias for the given pk. Otherwise, use the PKH string. let alias = self .store @@ -248,18 +332,23 @@ impl Wallet { /// Decrypt stored key, if it's not stored un-encrypted. /// If a given storage key needs to be decrypted, prompt for password from /// stdin and if successfully decrypted, store it in a cache. - fn decrypt_stored_key( - decrypted_key_cache: &mut HashMap>, - stored_key: &StoredKeypair, + fn decrypt_stored_key< + T: FromStr + Display + BorshSerialize + BorshDeserialize + Clone, + >( + decrypted_key_cache: &mut HashMap, + stored_key: &StoredKeypair, alias: Alias, - ) -> Result, FindKeyError> { + ) -> Result + where + ::Err: Display, + { match stored_key { StoredKeypair::Encrypted(encrypted) => { let password = read_password("Enter decryption password: "); let key = encrypted .decrypt(password) .map_err(FindKeyError::KeyDecryptionError)?; - decrypted_key_cache.insert(alias.clone(), Rc::new(key)); + decrypted_key_cache.insert(alias.clone(), key); decrypted_key_cache .get(&alias) .cloned() @@ -272,7 +361,10 @@ impl Wallet { /// Get all known keys by their alias, paired with PKH, if known. pub fn get_keys( &self, - ) -> HashMap)> { + ) -> HashMap< + String, + (&StoredKeypair, Option<&PublicKeyHash>), + > { self.store .get_keys() .into_iter() @@ -294,6 +386,35 @@ impl Wallet { .collect() } + /// Get all known payment addresses by their alias + pub fn get_payment_addrs(&self) -> HashMap { + self.store + .get_payment_addrs() + .iter() + .map(|(alias, value)| (alias.into(), *value)) + .collect() + } + + /// Get all known viewing keys by their alias + pub fn get_viewing_keys(&self) -> HashMap { + self.store + .get_viewing_keys() + .iter() + .map(|(alias, value)| (alias.into(), *value)) + .collect() + } + + /// Get all known viewing keys by their alias + pub fn get_spending_keys( + &self, + ) -> HashMap> { + self.store + .get_spending_keys() + .iter() + .map(|(alias, value)| (alias.into(), value)) + .collect() + } + /// Add a new address with the given alias. If the alias is already used, /// will ask whether the existing alias should be replaced, a different /// alias is desired, or the alias creation should be cancelled. Return @@ -314,7 +435,7 @@ impl Wallet { pub fn insert_keypair( &mut self, alias: String, - keypair: StoredKeypair, + keypair: StoredKeypair, pkh: PublicKeyHash, ) -> Option { self.store @@ -322,6 +443,53 @@ impl Wallet { .map(Into::into) } + pub fn insert_viewing_key( + &mut self, + alias: String, + view_key: ExtendedViewingKey, + ) -> Option { + self.store + .insert_viewing_key(alias.into(), view_key) + .map(Into::into) + } + + pub fn insert_spending_key( + &mut self, + alias: String, + spend_key: StoredKeypair, + viewkey: ExtendedViewingKey, + ) -> Option { + self.store + .insert_spending_key(alias.into(), spend_key, viewkey) + .map(Into::into) + } + + pub fn encrypt_insert_spending_key( + &mut self, + alias: String, + spend_key: ExtendedSpendingKey, + unsafe_dont_encrypt: bool, + ) -> Option { + let password = Self::new_password_prompt(unsafe_dont_encrypt); + self.store + .insert_spending_key( + alias.into(), + StoredKeypair::new(spend_key, password).0, + ExtendedFullViewingKey::from(&spend_key.into()).into(), + ) + .map(Into::into) + } + + pub fn insert_payment_addr( + &mut self, + alias: String, + payment_addr: PaymentAddress, + ) -> Option { + self.store + .insert_payment_addr(alias.into(), payment_addr) + .map(Into::into) + } + /// Extend this wallet from pre-genesis validator wallet. pub fn extend_from_pre_genesis_validator( &mut self, diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 6ecb396004..b2e0cbdc62 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -1,6 +1,5 @@ use std::fs; use std::path::{Path, PathBuf}; -use std::rc::Rc; use ark_serialize::{Read, Write}; use file_lock::{FileLock, FileOptions}; @@ -37,13 +36,13 @@ pub struct ValidatorWallet { /// The wallet store that can be written/read to/from TOML pub store: ValidatorStore, /// Cryptographic keypair for validator account key - pub account_key: Rc, + pub account_key: common::SecretKey, /// Cryptographic keypair for consensus key - pub consensus_key: Rc, + pub consensus_key: common::SecretKey, /// Cryptographic keypair for rewards key - pub rewards_key: Rc, + pub rewards_key: common::SecretKey, /// Cryptographic keypair for Tendermint node key - pub tendermint_node_key: Rc, + pub tendermint_node_key: common::SecretKey, } /// Validator pre-genesis wallet store includes all the required keys for @@ -51,13 +50,13 @@ pub struct ValidatorWallet { #[derive(Serialize, Deserialize, Debug)] pub struct ValidatorStore { /// Cryptographic keypair for validator account key - pub account_key: wallet::StoredKeypair, + pub account_key: wallet::StoredKeypair, /// Cryptographic keypair for consensus key - pub consensus_key: wallet::StoredKeypair, + pub consensus_key: wallet::StoredKeypair, /// Cryptographic keypair for rewards key - pub rewards_key: wallet::StoredKeypair, + pub rewards_key: wallet::StoredKeypair, /// Cryptographic keypair for Tendermint node key - pub tendermint_node_key: wallet::StoredKeypair, + pub tendermint_node_key: wallet::StoredKeypair, /// Special validator keys pub validator_keys: wallet::ValidatorKeys, } @@ -181,7 +180,7 @@ impl ValidatorStore { fn gen_key_to_store( password: &Option, -) -> (StoredKeypair, Rc) { +) -> (StoredKeypair, common::SecretKey) { let sk = store::gen_sk(); StoredKeypair::new(sk, password.clone()) } diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 95454d3d2a..368c12defa 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -3,15 +3,18 @@ use std::fs; use std::io::prelude::*; use std::io::{self, Write}; use std::path::{Path, PathBuf}; -use std::rc::Rc; use std::str::FromStr; use ark_std::rand::prelude::*; use ark_std::rand::SeedableRng; use file_lock::{FileLock, FileOptions}; +use masp_primitives::zip32::ExtendedFullViewingKey; use namada::types::address::{Address, ImplicitAddress}; use namada::types::key::dkg_session_keys::DkgKeypair; use namada::types::key::*; +use namada::types::masp::{ + ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, +}; use namada::types::transaction::EllipticCurve; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -50,8 +53,14 @@ pub struct ValidatorData { #[derive(Serialize, Deserialize, Debug, Default)] pub struct Store { + /// Known viewing keys + view_keys: HashMap, + /// Known spending keys + spend_keys: HashMap>, + /// Known payment addresses + payment_addrs: HashMap, /// Cryptographic keypairs - keys: HashMap, + keys: HashMap>, /// Anoma address book addresses: HashMap, /// Known mappings of public key hashes to their aliases in the `keys` @@ -182,7 +191,7 @@ impl Store { pub fn find_key( &self, alias_pkh_or_pk: impl AsRef, - ) -> Option<&StoredKeypair> { + ) -> Option<&StoredKeypair> { let alias_pkh_or_pk = alias_pkh_or_pk.as_ref(); // Try to find by alias self.keys @@ -199,11 +208,32 @@ impl Store { }) } + pub fn find_spending_key( + &self, + alias: impl AsRef, + ) -> Option<&StoredKeypair> { + self.spend_keys.get(&alias.into()) + } + + pub fn find_viewing_key( + &self, + alias: impl AsRef, + ) -> Option<&ExtendedViewingKey> { + self.view_keys.get(&alias.into()) + } + + pub fn find_payment_addr( + &self, + alias: impl AsRef, + ) -> Option<&PaymentAddress> { + self.payment_addrs.get(&alias.into()) + } + /// Find the stored key by a public key. pub fn find_key_by_pk( &self, pk: &common::PublicKey, - ) -> Option<&StoredKeypair> { + ) -> Option<&StoredKeypair> { let pkh = PublicKeyHash::from(pk); self.find_key_by_pkh(&pkh) } @@ -212,7 +242,7 @@ impl Store { pub fn find_key_by_pkh( &self, pkh: &PublicKeyHash, - ) -> Option<&StoredKeypair> { + ) -> Option<&StoredKeypair> { let alias = self.pkhs.get(pkh)?; self.keys.get(alias) } @@ -230,15 +260,21 @@ impl Store { /// Get all known keys by their alias, paired with PKH, if known. pub fn get_keys( &self, - ) -> HashMap)> { - let mut keys: HashMap)> = - self.pkhs - .iter() - .filter_map(|(pkh, alias)| { - let key = &self.keys.get(alias)?; - Some((alias.clone(), (*key, Some(pkh)))) - }) - .collect(); + ) -> HashMap< + Alias, + (&StoredKeypair, Option<&PublicKeyHash>), + > { + let mut keys: HashMap< + Alias, + (&StoredKeypair, Option<&PublicKeyHash>), + > = self + .pkhs + .iter() + .filter_map(|(pkh, alias)| { + let key = &self.keys.get(alias)?; + Some((alias.clone(), (*key, Some(pkh)))) + }) + .collect(); self.keys.iter().for_each(|(alias, key)| { if !keys.contains_key(alias) { keys.insert(alias.clone(), (key, None)); @@ -252,6 +288,31 @@ impl Store { &self.addresses } + /// Get all known payment addresses by their alias. + pub fn get_payment_addrs(&self) -> &HashMap { + &self.payment_addrs + } + + /// Get all known viewing keys by their alias. + pub fn get_viewing_keys(&self) -> &HashMap { + &self.view_keys + } + + /// Get all known spending keys by their alias. + pub fn get_spending_keys( + &self, + ) -> &HashMap> { + &self.spend_keys + } + + fn generate_spending_key() -> ExtendedSpendingKey { + use rand::rngs::OsRng; + let mut spend_key = [0; 32]; + OsRng.fill_bytes(&mut spend_key); + masp_primitives::zip32::ExtendedSpendingKey::master(spend_key.as_ref()) + .into() + } + /// Generate a new keypair and insert it into the store with the provided /// alias. If none provided, the alias will be the public key hash. /// If no password is provided, the keypair will be stored raw without @@ -261,7 +322,7 @@ impl Store { &mut self, alias: Option, password: Option, - ) -> (Alias, Rc) { + ) -> (Alias, common::SecretKey) { let sk = gen_sk(); let pkh: PublicKeyHash = PublicKeyHash::from(&sk.ref_to()); let (keypair_to_store, raw_keypair) = StoredKeypair::new(sk, password); @@ -281,6 +342,27 @@ impl Store { (alias, raw_keypair) } + /// Generate a spending key similarly to how it's done for keypairs + pub fn gen_spending_key( + &mut self, + alias: String, + password: Option, + ) -> (Alias, ExtendedSpendingKey) { + let spendkey = Self::generate_spending_key(); + let viewkey = ExtendedFullViewingKey::from(&spendkey.into()).into(); + let (spendkey_to_store, _raw_spendkey) = + StoredKeypair::new(spendkey, password); + let alias = Alias::from(alias); + if self + .insert_spending_key(alias.clone(), spendkey_to_store, viewkey) + .is_none() + { + eprintln!("Action cancelled, no changes persisted."); + cli::safe_exit(1); + } + (alias, spendkey) + } + /// Generate keypair for signing protocol txs and for the DKG /// A protocol keypair may be optionally provided /// @@ -324,7 +406,7 @@ impl Store { pub(super) fn insert_keypair( &mut self, alias: Alias, - keypair: StoredKeypair, + keypair: StoredKeypair, pkh: PublicKeyHash, ) -> Option { if alias.is_empty() { @@ -333,20 +415,145 @@ impl Store { alias = Into::::into(pkh.to_string()) ); } - if self.keys.contains_key(&alias) { + // Addresses and keypairs can share aliases, so first remove any + // addresses sharing the same namesake before checking if alias has been + // used. + let counterpart_address = self.addresses.remove(&alias); + if self.contains_alias(&alias) { match show_overwrite_confirmation(&alias, "a key") { ConfirmationResponse::Replace => {} ConfirmationResponse::Reselect(new_alias) => { + // Restore the removed address in case the recursive prompt + // terminates with a cancellation + counterpart_address + .map(|x| self.addresses.insert(alias.clone(), x)); return self.insert_keypair(new_alias, keypair, pkh); } - ConfirmationResponse::Skip => return None, + ConfirmationResponse::Skip => { + // Restore the removed address since this insertion action + // has now been cancelled + counterpart_address + .map(|x| self.addresses.insert(alias.clone(), x)); + return None; + } } } + self.remove_alias(&alias); self.keys.insert(alias.clone(), keypair); self.pkhs.insert(pkh, alias.clone()); + // Since it is intended for the inserted keypair to share its namesake + // with the pre-existing address + counterpart_address.map(|x| self.addresses.insert(alias.clone(), x)); + Some(alias) + } + + /// Insert spending keys similarly to how it's done for keypairs + pub fn insert_spending_key( + &mut self, + alias: Alias, + spendkey: StoredKeypair, + viewkey: ExtendedViewingKey, + ) -> Option { + if alias.is_empty() { + eprintln!("Empty alias given."); + return None; + } + if self.contains_alias(&alias) { + match show_overwrite_confirmation(&alias, "a spending key") { + ConfirmationResponse::Replace => {} + ConfirmationResponse::Reselect(new_alias) => { + return self + .insert_spending_key(new_alias, spendkey, viewkey); + } + ConfirmationResponse::Skip => return None, + } + } + self.remove_alias(&alias); + self.spend_keys.insert(alias.clone(), spendkey); + // Simultaneously add the derived viewing key to ease balance viewing + self.view_keys.insert(alias.clone(), viewkey); + Some(alias) + } + + /// Insert viewing keys similarly to how it's done for keypairs + pub fn insert_viewing_key( + &mut self, + alias: Alias, + viewkey: ExtendedViewingKey, + ) -> Option { + if alias.is_empty() { + eprintln!("Empty alias given."); + return None; + } + if self.contains_alias(&alias) { + match show_overwrite_confirmation(&alias, "a viewing key") { + ConfirmationResponse::Replace => {} + ConfirmationResponse::Reselect(new_alias) => { + return self.insert_viewing_key(new_alias, viewkey); + } + ConfirmationResponse::Skip => return None, + } + } + self.remove_alias(&alias); + self.view_keys.insert(alias.clone(), viewkey); Some(alias) } + /// Check if any map of the wallet contains the given alias + fn contains_alias(&self, alias: &Alias) -> bool { + self.payment_addrs.contains_key(alias) + || self.view_keys.contains_key(alias) + || self.spend_keys.contains_key(alias) + || self.keys.contains_key(alias) + || self.addresses.contains_key(alias) + } + + /// Completely remove the given alias from all maps in the wallet + fn remove_alias(&mut self, alias: &Alias) { + self.payment_addrs.remove(alias); + self.view_keys.remove(alias); + self.spend_keys.remove(alias); + self.keys.remove(alias); + self.addresses.remove(alias); + self.pkhs.retain(|_key, val| val != alias); + } + + /// Insert payment addresses similarly to how it's done for keypairs + pub fn insert_payment_addr( + &mut self, + alias: Alias, + payment_addr: PaymentAddress, + ) -> Option { + if alias.is_empty() { + eprintln!("Empty alias given."); + return None; + } + if self.contains_alias(&alias) { + match show_overwrite_confirmation(&alias, "a payment address") { + ConfirmationResponse::Replace => {} + ConfirmationResponse::Reselect(new_alias) => { + return self.insert_payment_addr(new_alias, payment_addr); + } + ConfirmationResponse::Skip => return None, + } + } + self.remove_alias(&alias); + self.payment_addrs.insert(alias.clone(), payment_addr); + Some(alias) + } + + /// Helper function to restore keypair given alias-keypair mapping and the + /// pkhs-alias mapping. + fn restore_keypair( + &mut self, + alias: Alias, + key: Option>, + pkh: Option, + ) { + key.map(|x| self.keys.insert(alias.clone(), x)); + pkh.map(|x| self.pkhs.insert(x, alias.clone())); + } + /// Insert a new address with the given alias. If the alias is already used, /// will prompt for overwrite/reselection confirmation, which when declined, /// the address won't be added. Return the selected alias if the address has @@ -362,16 +569,48 @@ impl Store { alias = address.encode() ); } - if self.addresses.contains_key(&alias) { + // Addresses and keypairs can share aliases, so first remove any keys + // sharing the same namesake before checking if alias has been used. + let counterpart_key = self.keys.remove(&alias); + let mut counterpart_pkh = None; + self.pkhs.retain(|k, v| { + if v == &alias { + counterpart_pkh = Some(k.clone()); + false + } else { + true + } + }); + if self.contains_alias(&alias) { match show_overwrite_confirmation(&alias, "an address") { ConfirmationResponse::Replace => {} ConfirmationResponse::Reselect(new_alias) => { + // Restore the removed keypair in case the recursive prompt + // terminates with a cancellation + self.restore_keypair( + alias, + counterpart_key, + counterpart_pkh, + ); return self.insert_address(new_alias, address); } - ConfirmationResponse::Skip => return None, + ConfirmationResponse::Skip => { + // Restore the removed keypair since this insertion action + // has now been cancelled + self.restore_keypair( + alias, + counterpart_key, + counterpart_pkh, + ); + return None; + } } } + self.remove_alias(&alias); self.addresses.insert(alias.clone(), address); + // Since it is intended for the inserted address to share its namesake + // with the pre-existing keypair + self.restore_keypair(alias.clone(), counterpart_key, counterpart_pkh); Some(alias) } diff --git a/genesis/dev.toml b/genesis/dev.toml index 4c48bcc279..3b921e3265 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -144,7 +144,7 @@ sha256 = "e428a11f570d21dd3c871f5d35de6fe18098eb8ee0456b3e11a72ccdd8685cd0" # Minimum number of blocks in an epoch. min_num_of_blocks = 10 # Minimum duration of an epoch (in seconds). -min_duration = 60 +min_duration = 600 # Maximum expected time per block (in seconds). max_expected_time_per_block = 30 diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 96fd787ca2..92d11a18c4 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -122,6 +122,10 @@ vp = "vp_user" [established.Christel] vp = "vp_user" +[established.masp] +address = "atest1v4ehgw36xaryysfsx5unvve4g5my2vjz89p52sjxxgenzd348yuyyv3hg3pnjs35g5unvde4ca36y5" +vp = "vp_masp" + [implicit.Daewon] # Wasm VP definitions @@ -143,6 +147,10 @@ sha256 = "e428a11f570d21dd3c871f5d35de6fe18098eb8ee0456b3e11a72ccdd8685cd0" filename = "vp_testnet_faucet.wasm" sha256 = "2038d93afd456a77c45123811b671627f488c8d2a72b714d82dd494cbbd552bc" +# MASP VP +[wasm.vp_masp] +filename = "vp_masp.wasm" + # General protocol parameters. [parameters] # Minimum number of blocks in an epoch. diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 241fd034da..d86203f13c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -48,6 +48,7 @@ ark-bls12-381 = {version = "0.3"} ark-ec = {version = "0.3", optional = true} ark-serialize = "0.3" bech32 = "0.8.0" +bit-vec = "0.6.3" borsh = "0.9.0" chrono = "0.4.19" # Using unreleased commit on top of version 0.5.0 that adds Sync to the CLruCache @@ -73,6 +74,7 @@ pwasm-utils = {version = "0.18.0", optional = true} rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} +rayon = "=1.5.1" rust_decimal = "1.14.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" @@ -92,6 +94,9 @@ wasmer-engine-dylib = {version = "=2.2.0", optional = true} wasmer-engine-universal = {version = "=2.2.0", optional = true} wasmer-vm = {version = "2.2.0", optional = true} wasmparser = "0.83.0" +#libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } [dev-dependencies] assert_matches = "1.5.0" diff --git a/shared/proptest-regressions/ledger/storage/mod.txt b/shared/proptest-regressions/ledger/storage/mod.txt new file mode 100644 index 0000000000..c3826edd4b --- /dev/null +++ b/shared/proptest-regressions/ledger/storage/mod.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc e02bfe6892bfa6d068daddc0abb2b66ae6d79b3bc4ebc345813df7e8574c78bd # shrinks to (epoch_duration, max_expected_time_per_block, start_height, start_time, block_height, block_time, min_blocks_delta, min_duration_delta, max_time_per_block_delta) = (EpochDuration { min_num_of_blocks: 1, min_duration: DurationSecs(1) }, 1, BlockHeight(0), DateTimeUtc(1970-01-01T00:00:00Z), BlockHeight(1), DateTimeUtc(1970-01-01T00:00:01Z), 0, 0, 0) diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index b6bd15e43b..9579273e52 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -373,6 +373,7 @@ mod tests { use crate::proto::Tx; use crate::types::ibc::data::{PacketAck, PacketReceipt}; use crate::vm::wasm; + use crate::types::storage::TxIndex; use crate::types::storage::{BlockHash, BlockHeight}; fn get_client_id() -> ClientId { @@ -561,6 +562,7 @@ mod tests { let event = make_create_client_event(&get_client_id(), &msg); write_log.set_ibc_event(event.try_into().unwrap()); + let tx_index = TxIndex::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -568,7 +570,14 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); let client_state_key = client_state_key(&get_client_id()); @@ -592,13 +601,21 @@ mod tests { fn test_create_client_fail() { let storage = TestStorage::default(); let write_log = WriteLog::default(); + let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); let client_state_key = client_state_key(&get_client_id()); @@ -661,6 +678,7 @@ mod tests { ) .expect("write failed"); + let tx_index = TxIndex::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -668,7 +686,14 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(client_state_key); @@ -710,6 +735,7 @@ mod tests { let event = make_open_init_connection_event(&conn_id, &msg); write_log.set_ibc_event(event.try_into().unwrap()); + let tx_index = TxIndex::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -717,7 +743,14 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(conn_key); @@ -756,6 +789,7 @@ mod tests { let bytes = conn.encode_vec().expect("encoding failed"); write_log.write(&conn_key, bytes).expect("write failed"); + let tx_index = TxIndex::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -763,7 +797,14 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(conn_key); @@ -828,6 +869,7 @@ mod tests { let event = make_open_try_connection_event(&conn_id, &msg); write_log.set_ibc_event(event.try_into().unwrap()); + let tx_index = TxIndex::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -835,7 +877,14 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(conn_key); @@ -907,13 +956,21 @@ mod tests { let event = make_open_ack_connection_event(&msg); write_log.set_ibc_event(event.try_into().unwrap()); + let tx_index = TxIndex::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(conn_key); @@ -972,13 +1029,21 @@ mod tests { let event = make_open_confirm_connection_event(&msg); write_log.set_ibc_event(event.try_into().unwrap()); + let tx_index = TxIndex::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(conn_key); @@ -1022,6 +1087,7 @@ mod tests { let event = make_open_init_channel_event(&get_channel_id(), &msg); write_log.set_ibc_event(event.try_into().unwrap()); + let tx_index = TxIndex::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1029,7 +1095,14 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(channel_key); @@ -1092,6 +1165,7 @@ mod tests { let event = make_open_try_channel_event(&get_channel_id(), &msg); write_log.set_ibc_event(event.try_into().unwrap()); + let tx_index = TxIndex::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1099,7 +1173,14 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(channel_key); @@ -1170,6 +1251,7 @@ mod tests { let event = make_open_ack_channel_event(&msg); write_log.set_ibc_event(event.try_into().unwrap()); + let tx_index = TxIndex::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1177,7 +1259,14 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(channel_key); @@ -1243,6 +1332,7 @@ mod tests { let event = make_open_confirm_channel_event(&msg); write_log.set_ibc_event(event.try_into().unwrap()); + let tx_index = TxIndex::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1250,7 +1340,14 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(channel_key); @@ -1275,13 +1372,21 @@ mod tests { set_port(&mut write_log, 0); write_log.commit_tx(); + let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(port_key(&get_port_id())); @@ -1307,13 +1412,21 @@ mod tests { set_port(&mut write_log, index); write_log.commit_tx(); + let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); let cap_key = capability_key(index); @@ -1383,6 +1496,7 @@ mod tests { .write(&key, commitment_bytes) .expect("write failed"); + let tx_index = TxIndex::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1390,7 +1504,14 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(seq_key); @@ -1462,6 +1583,7 @@ mod tests { let ack = PacketAck::default().encode_to_vec(); write_log.write(&key, ack).expect("write failed"); + let tx_index = TxIndex::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1469,7 +1591,14 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(seq_key); @@ -1546,6 +1675,7 @@ mod tests { // delete the commitment write_log.delete(&commitment_key).expect("delete failed"); + let tx_index = TxIndex::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1553,7 +1683,14 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(seq_key); @@ -1626,6 +1763,7 @@ mod tests { let event = make_send_packet_event(packet); write_log.set_ibc_event(event.try_into().unwrap()); + let tx_index = TxIndex::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1633,7 +1771,14 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(commitment_key); @@ -1710,6 +1855,7 @@ mod tests { write_log.write(&ack_key, ack).expect("write failed"); write_log.commit_tx(); + let tx_index = TxIndex::default(); let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1717,7 +1863,14 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(receipt_key); @@ -1751,13 +1904,21 @@ mod tests { write_log.write(&ack_key, ack).expect("write failed"); write_log.commit_tx(); + let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); + let ctx = Ctx::new( + &storage, + &write_log, + &tx, + &tx_index, + gas_meter, + vp_wasm_cache, + ); let mut keys_changed = BTreeSet::new(); keys_changed.insert(ack_key); diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index f56382b360..fc09499b22 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -13,7 +13,9 @@ use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::proto::SignedTxData; -use crate::types::address::{Address, Error as AddressError, InternalAddress}; +use crate::types::address::{ + Address, DecodeError as AddressError, InternalAddress, +}; use crate::types::ibc::data::{ Error as IbcDataError, FungibleTokenPacketData, IbcMessage, }; diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index faad84a0f4..6da3a2f021 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -11,7 +11,7 @@ use crate::ledger::storage::{Storage, StorageHasher}; use crate::ledger::{storage, vp_env}; use crate::proto::Tx; use crate::types::address::{Address, InternalAddress}; -use crate::types::storage::{BlockHash, BlockHeight, Epoch, Key}; +use crate::types::storage::{BlockHash, BlockHeight, Epoch, Key, TxIndex}; use crate::vm::prefix_iter::PrefixIterators; use crate::vm::WasmCacheAccess; @@ -64,6 +64,9 @@ where pub write_log: &'a WriteLog, /// The transaction code is used for signature verification pub tx: &'a Tx, + /// The transaction index is used to obtain the shielded transaction's + /// parent + pub tx_index: &'a TxIndex, /// VP WASM compilation cache #[cfg(feature = "wasm-runtime")] pub vp_wasm_cache: crate::vm::wasm::VpCache, @@ -83,6 +86,7 @@ where storage: &'a Storage, write_log: &'a WriteLog, tx: &'a Tx, + tx_index: &'a TxIndex, gas_meter: VpGasMeter, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: crate::vm::wasm::VpCache, @@ -93,6 +97,7 @@ where storage, write_log, tx, + tx_index, #[cfg(feature = "wasm-runtime")] vp_wasm_cache, #[cfg(not(feature = "wasm-runtime"))] @@ -270,6 +275,7 @@ where self.write_log, &mut *self.gas_meter.borrow_mut(), self.tx, + self.tx_index, &mut iterators, verifiers, &mut result_buffer, diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 3cc7e8863c..ad881b5e36 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -16,7 +16,7 @@ use crate::ledger::storage::types::{self, KVBytes, PrefixIterator}; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ - BlockHeight, Header, Key, KeySeg, KEY_SEGMENT_SEPARATOR, + BlockHeight, BlockResults, Header, Key, KeySeg, KEY_SEGMENT_SEPARATOR, }; use crate::types::time::DateTimeUtc; @@ -57,6 +57,15 @@ impl DB for MockDB { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, None => return Ok(None), }; + // Block results + let results_path = format!("results/{}", height.raw()); + let results: BlockResults = + match self.0.borrow().get(results_path.as_str()) { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; // Epoch start height and time let next_epoch_min_start_height: BlockHeight = @@ -154,6 +163,7 @@ impl DB for MockDB { next_epoch_min_start_height, next_epoch_min_start_time, address_gen, + results, #[cfg(feature = "ferveo-tpke")] tx_queue, })) @@ -176,6 +186,7 @@ impl DB for MockDB { next_epoch_min_start_height, next_epoch_min_start_time, address_gen, + results, #[cfg(feature = "ferveo-tpke")] tx_queue, }: BlockStateWrite = state; @@ -274,6 +285,13 @@ impl DB for MockDB { self.0 .borrow_mut() .insert("height".to_owned(), types::encode(&height)); + // Block results + { + let results_path = format!("results/{}", height.raw()); + self.0 + .borrow_mut() + .insert(results_path, types::encode(&results)); + } Ok(()) } @@ -433,6 +451,13 @@ impl<'iter> DBIter<'iter> for MockDB { let iter = self.0.borrow().clone().into_iter(); MockPrefixIterator::new(MockIterator { prefix, iter }, db_prefix) } + + fn iter_results(&'iter self) -> MockPrefixIterator { + let db_prefix = "results/".to_owned(); + let prefix = "results".to_owned(); + let iter = self.0.borrow().clone().into_iter(); + MockPrefixIterator::new(MockIterator { prefix, iter }, db_prefix) + } } /// A prefix iterator base for the [`MockPrefixIterator`]. diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 0b3d19a742..d0d46e7089 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -7,7 +7,19 @@ pub mod types; pub mod write_log; use core::fmt::Debug; - +use std::collections::BTreeMap; + +use borsh::{BorshDeserialize, BorshSerialize}; +use masp_primitives::asset_type::AssetType; +use masp_primitives::convert::AllowedConversion; +use masp_primitives::ff::PrimeField; +use masp_primitives::merkle_tree::FrozenCommitmentTree; +use masp_primitives::sapling::Node; +use masp_primitives::transaction::components::Amount; +use rayon::iter::{ + IndexedParallelIterator, IntoParallelIterator, ParallelIterator, +}; +use rayon::prelude::ParallelSlice; use tendermint::merkle::proof::Proof; use thiserror::Error; @@ -22,18 +34,29 @@ pub use crate::ledger::storage::merkle_tree::{ MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, Sha256Hasher, StorageHasher, StoreType, }; -use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; +use crate::types::address::{ + Address, EstablishedAddressGen, InternalAddress, *, +}; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ - BlockHash, BlockHeight, Epoch, Epochs, Header, Key, KeySeg, + BlockHash, BlockHeight, BlockResults, Epoch, Epochs, Header, Key, KeySeg, BLOCK_HASH_LENGTH, }; use crate::types::time::DateTimeUtc; +use crate::types::token; /// A result of a function that may fail pub type Result = std::result::Result; +/// A representation of the conversion state +#[derive(Debug, Default, BorshSerialize, BorshDeserialize)] +pub struct ConversionState { + /// The tree currently containing all the conversions + pub tree: FrozenCommitmentTree, + /// Map assets to their latest conversion and position in Merkle tree + pub assets: BTreeMap, +} /// The storage data #[derive(Debug)] @@ -60,6 +83,8 @@ where pub next_epoch_min_start_time: DateTimeUtc, /// The current established address generator pub address_gen: EstablishedAddressGen, + /// The currently saved conversion state + pub conversion_state: ConversionState, /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, @@ -76,6 +101,8 @@ pub struct BlockStorage { pub height: BlockHeight, /// Epoch of the block pub epoch: Epoch, + /// Results of applying transactions + pub results: BlockResults, /// Predecessor block epochs pub pred_epochs: Epochs, } @@ -119,6 +146,8 @@ pub struct BlockStateRead { pub next_epoch_min_start_time: DateTimeUtc, /// Established address generator pub address_gen: EstablishedAddressGen, + /// Results of applying transactions + pub results: BlockResults, /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, @@ -144,6 +173,8 @@ pub struct BlockStateWrite<'a> { pub next_epoch_min_start_time: DateTimeUtc, /// Established address generator pub address_gen: &'a EstablishedAddressGen, + /// Results of applying transactions + pub results: &'a BlockResults, /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: &'a TxQueue, @@ -244,6 +275,9 @@ pub trait DBIter<'iter> { /// Read account subspace key value pairs with the given prefix from the DB fn iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter; + + /// Read results subspace key value pairs from the DB + fn iter_results(&'iter self) -> Self::PrefixIter; } /// Atomic batch write. @@ -276,6 +310,7 @@ where height: BlockHeight::default(), epoch: Epoch::default(), pred_epochs: Epochs::default(), + results: BlockResults::default(), }; Storage:: { db: D::open(db_path, cache), @@ -289,6 +324,7 @@ where address_gen: EstablishedAddressGen::new( "Privacy is a function of liberty.", ), + conversion_state: ConversionState::default(), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), } @@ -305,6 +341,7 @@ where pred_epochs, next_epoch_min_start_height, next_epoch_min_start_time, + results, address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, @@ -314,12 +351,30 @@ where self.block.hash = hash; self.block.height = height; self.block.epoch = epoch; + self.block.results = results; self.block.pred_epochs = pred_epochs; self.last_height = height; 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.address_gen = address_gen; + if self.last_epoch.0 > 1 { + // The derived conversions will be placed in MASP address space + let masp_addr = masp(); + let key_prefix: Key = masp_addr.to_db_key().into(); + // Load up the conversions currently being given as query + // results + let state_key = key_prefix + .push(&(token::CONVERSION_KEY_PREFIX.to_owned())) + .map_err(Error::KeyError)?; + self.conversion_state = types::decode( + self.read(&state_key) + .expect("unable to read conversion state") + .0 + .expect("unable to find conversion state"), + ) + .expect("unable to decode conversion state") + } #[cfg(feature = "ferveo-tpke")] { self.tx_queue = tx_queue; @@ -349,6 +404,7 @@ where hash: &self.block.hash, height: self.block.height, epoch: self.block.epoch, + results: &self.block.results, 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, @@ -418,6 +474,11 @@ where (self.db.iter_prefix(prefix), prefix.len() as _) } + /// Returns a prefix iterator and the gas cost + pub fn iter_results(&self) -> (>::PrefixIter, u64) { + (self.db.iter_results(), 0) + } + /// Write a value to the specified subspace and returns the gas cost and the /// size difference pub fn write( @@ -612,11 +673,176 @@ where .pred_epochs .new_epoch(height, evidence_max_age_num_blocks); tracing::info!("Began a new epoch {}", self.block.epoch); + self.update_allowed_conversions()?; } self.update_epoch_in_merkle_tree()?; Ok(new_epoch) } + /// Get the current conversions + pub fn get_conversion_state(&self) -> &ConversionState { + &self.conversion_state + } + + // Construct MASP asset type with given timestamp for given token + fn encode_asset_type(addr: Address, epoch: Epoch) -> AssetType { + let new_asset_bytes = (addr, epoch.0) + .try_to_vec() + .expect("unable to serialize address and epoch"); + AssetType::new(new_asset_bytes.as_ref()) + .expect("unable to derive asset identifier") + } + + /// Update the MASP's allowed conversions + fn update_allowed_conversions(&mut self) -> Result<()> { + // The derived conversions will be placed in MASP address space + let masp_addr = masp(); + let key_prefix: Key = masp_addr.to_db_key().into(); + + let masp_rewards = masp_rewards(); + // The total transparent value of the rewards being distributed + let mut total_reward = token::Amount::from(0); + + // Construct MASP asset type for rewards. Always timestamp reward tokens + // with the zeroth epoch to minimize the number of convert notes clients + // have to use. This trick works under the assumption that reward tokens + // from different epochs are exactly equivalent. + let reward_asset_bytes = (xan(), 0u64) + .try_to_vec() + .expect("unable to serialize address and epoch"); + let reward_asset = AssetType::new(reward_asset_bytes.as_ref()) + .expect("unable to derive asset identifier"); + // Conversions from the previous to current asset for each address + let mut current_convs = BTreeMap::::new(); + // Reward all tokens according to above reward rates + for (addr, reward) in &masp_rewards { + // Dispence a transparent reward in parallel to the shielded rewards + let token_key = self.read(&token::balance_key(addr, &masp_addr)); + if let Ok((Some(addr_balance), _)) = token_key { + // The reward for each reward.1 units of the current asset is + // reward.0 units of the reward token + let addr_bal: token::Amount = + types::decode(addr_balance).expect("invalid balance"); + // Since floor(a) + floor(b) <= floor(a+b), there will always be + // enough rewards to reimburse users + total_reward += (addr_bal * *reward).0; + } + // Provide an allowed conversion from previous timestamp. The + // negative sign allows each instance of the old asset to be + // cancelled out/replaced with the new asset + let old_asset = + Self::encode_asset_type(addr.clone(), self.last_epoch.prev()); + let new_asset = + Self::encode_asset_type(addr.clone(), self.last_epoch); + current_convs.insert( + addr.clone(), + (Amount::from_pair(old_asset, -(reward.1 as i64)).unwrap() + + Amount::from_pair(new_asset, reward.1).unwrap() + + Amount::from_pair(reward_asset, reward.0).unwrap()) + .into(), + ); + // Add a conversion from the previous asset type + self.conversion_state.assets.insert( + old_asset, + ( + addr.clone(), + self.last_epoch.prev(), + Amount::zero().into(), + 0, + ), + ); + } + + // Try to distribute Merkle leaf updating as evenly as possible across + // multiple cores + let num_threads = rayon::current_num_threads(); + // Put assets into vector to enable computation batching + let assets: Vec<_> = self + .conversion_state + .assets + .values_mut() + .enumerate() + .collect(); + // ceil(assets.len() / num_threads) + let notes_per_thread_max = (assets.len() - 1) / num_threads + 1; + // floor(assets.len() / num_threads) + let notes_per_thread_min = assets.len() / num_threads; + // Now on each core, add the latest conversion to each conversion + let conv_notes: Vec = assets + .into_par_iter() + .with_min_len(notes_per_thread_min) + .with_max_len(notes_per_thread_max) + .map(|(idx, (addr, _epoch, conv, pos))| { + // Use transitivity to update conversion + *conv += current_convs[addr].clone(); + // Update conversion position to leaf we are about to create + *pos = idx; + // The merkle tree need only provide the conversion commitment, + // the remaining information is provided through the storage API + Node::new(conv.cmu().to_repr()) + }) + .collect(); + + // Update the MASP's transparent reward token balance to ensure that it + // is sufficiently backed to redeem rewards + let reward_key = token::balance_key(&xan(), &masp_addr); + if let Ok((Some(addr_bal), _)) = self.read(&reward_key) { + // If there is already a balance, then add to it + let addr_bal: token::Amount = + types::decode(addr_bal).expect("invalid balance"); + let new_bal = types::encode(&(addr_bal + total_reward)); + self.write(&reward_key, new_bal) + .expect("unable to update MASP transparent balance"); + } else { + // Otherwise the rewards form the entirity of the reward token + // balance + self.write(&reward_key, types::encode(&total_reward)) + .expect("unable to update MASP transparent balance"); + } + // Try to distribute Merkle tree construction as evenly as possible + // across multiple cores + // Merkle trees must have exactly 2^n leaves to be mergeable + let mut notes_per_thread_rounded = 1; + while notes_per_thread_max > notes_per_thread_rounded * 4 { + notes_per_thread_rounded *= 2; + } + // Make the sub-Merkle trees in parallel + let tree_parts: Vec<_> = conv_notes + .par_chunks(notes_per_thread_rounded) + .map(FrozenCommitmentTree::new) + .collect(); + // Convert conversion vector into tree so that Merkle paths can be + // obtained + self.conversion_state.tree = FrozenCommitmentTree::merge(&tree_parts); + + // Add purely decoding entries to the assets map. These will be + // overwritten before the creation of the next commitment tree + for addr in masp_rewards.keys() { + // Add the decoding entry for the new asset type. An uncommited + // node position is used since this is not a conversion. + let new_asset = + Self::encode_asset_type(addr.clone(), self.last_epoch); + self.conversion_state.assets.insert( + new_asset, + ( + addr.clone(), + self.last_epoch, + Amount::zero().into(), + self.conversion_state.tree.size(), + ), + ); + } + + // Save the current conversion state in order to avoid computing + // conversion commitments from scratch in the next epoch + let state_key = key_prefix + .push(&(token::CONVERSION_KEY_PREFIX.to_owned())) + .map_err(Error::KeyError)?; + self.write(&state_key, types::encode(&self.conversion_state)) + .expect("unable to save current conversion state"); + Ok(()) + } + /// Update the merkle tree with epoch data fn update_epoch_in_merkle_tree(&mut self) -> Result<()> { let key_prefix: Key = @@ -642,6 +868,7 @@ where self.block .tree .update(&key, types::encode(&self.block.epoch))?; + Ok(()) } @@ -711,6 +938,7 @@ pub mod testing { height: BlockHeight::default(), epoch: Epoch::default(), pred_epochs: Epochs::default(), + results: BlockResults::default(), }; Self { db: MockDB::default(), @@ -724,6 +952,7 @@ pub mod testing { address_gen: EstablishedAddressGen::new( "Test address generator seed", ), + conversion_state: ConversionState::default(), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), } diff --git a/shared/src/ledger/vp_env.rs b/shared/src/ledger/vp_env.rs index 1f59613e54..e92671f06f 100644 --- a/shared/src/ledger/vp_env.rs +++ b/shared/src/ledger/vp_env.rs @@ -12,7 +12,7 @@ use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{self, write_log, Storage, StorageHasher}; use crate::proto::Tx; use crate::types::hash::Hash; -use crate::types::storage::{BlockHash, BlockHeight, Epoch, Key}; +use crate::types::storage::{BlockHash, BlockHeight, Epoch, Key, TxIndex}; /// These runtime errors will abort VP execution immediately #[allow(missing_docs)] @@ -267,6 +267,16 @@ where Ok(epoch) } +/// Getting the block epoch. The epoch is that of the block to which the +/// current transaction is being applied. +pub fn get_tx_index( + gas_meter: &mut VpGasMeter, + tx_index: &TxIndex, +) -> Result { + add_gas(gas_meter, MIN_STORAGE_GAS)?; + Ok(*tx_index) +} + /// Storage prefix iterator. It will try to get an iterator from the storage. pub fn iter_prefix<'a, DB, H>( gas_meter: &mut VpGasMeter, diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 7a936dd58f..fb99679633 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -6,12 +6,36 @@ use std::hash::{Hash, Hasher}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use prost::Message; use serde::{Deserialize, Serialize}; +#[cfg(not(feature = "ABCI"))] +#[cfg(feature = "ferveo-tpke")] +use tendermint_proto::abci::Event; +#[cfg(not(feature = "ABCI"))] +#[cfg(feature = "ferveo-tpke")] +use tendermint_proto::abci::EventAttribute; +#[cfg(not(feature = "ABCI"))] +use tendermint_proto::abci::ResponseDeliverTx; +#[cfg(feature = "ABCI")] +#[cfg(feature = "ferveo-tpke")] +use tendermint_proto_abci::abci::Event; +#[cfg(feature = "ABCI")] +#[cfg(feature = "ferveo-tpke")] +use tendermint_proto_abci::abci::EventAttribute; +#[cfg(feature = "ABCI")] +use tendermint_proto_abci::abci::ResponseDeliverTx; use thiserror::Error; use super::generated::types; use crate::types::key::*; 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; +#[cfg(feature = "ferveo-tpke")] +use crate::types::transaction::DecryptedTx; +#[cfg(feature = "ferveo-tpke")] +use crate::types::transaction::TxType; #[derive(Error, Debug)] pub enum Error { @@ -157,6 +181,85 @@ impl From for types::Tx { } } +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 { + #[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))) => { + 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() + } + } + _ => Default::default(), + } + } +} + impl Tx { pub fn new(code: Vec, data: Option>) -> Self { Tx { diff --git a/shared/src/types/address.rs b/shared/src/types/address.rs index 704dcdbdb2..d46a8a3324 100644 --- a/shared/src/types/address.rs +++ b/shared/src/types/address.rs @@ -5,7 +5,6 @@ use std::collections::HashMap; use std::fmt::{Debug, Display}; use std::hash::Hash; use std::str::FromStr; -use std::string; use bech32::{self, FromBase32, ToBase32, Variant}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -25,7 +24,8 @@ pub const ADDRESS_LEN: usize = 79 + ADDRESS_HRP.len(); /// human-readable part of Bech32m encoded address // TODO use "a" for live network const ADDRESS_HRP: &str = "atest"; -const ADDRESS_BECH32_VARIANT: bech32::Variant = Variant::Bech32m; +/// We're using "Bech32m" variant +pub const BECH32M_VARIANT: bech32::Variant = Variant::Bech32m; pub(crate) const HASH_LEN: usize = 40; /// An address string before bech32m encoding must be this size. @@ -78,29 +78,21 @@ const PREFIX_INTERNAL: &str = "ano"; #[allow(missing_docs)] #[derive(Error, Debug)] -pub enum Error { +pub enum DecodeError { #[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:?}" - )] + #[error("Unexpected Bech32m human-readable part {0}, expected {1}")] + UnexpectedBech32Prefix(String, String), + #[error("Unexpected Bech32m variant {0:?}, expected {BECH32M_VARIANT:?}")] UnexpectedBech32Variant(bech32::Variant), - #[error("Address must be encoded with utf-8")] - NonUtf8Address(string::FromUtf8Error), #[error("Invalid address encoding")] - InvalidAddressEncoding(std::io::Error), - #[error("Unexpected address hash length {0}, expected {HASH_LEN}")] - UnexpectedHashLength(usize), + InvalidInnerEncoding(std::io::Error), } /// Result of a function that may fail -pub type Result = std::result::Result; +pub type Result = std::result::Result; /// An account's address #[derive( @@ -127,7 +119,7 @@ impl Address { /// Encode an address with Bech32m encoding pub fn encode(&self) -> String { let bytes = self.to_fixed_len_string(); - bech32::encode(ADDRESS_HRP, bytes.to_base32(), ADDRESS_BECH32_VARIANT) + bech32::encode(ADDRESS_HRP, bytes.to_base32(), BECH32M_VARIANT) .unwrap_or_else(|_| { panic!( "The human-readable part {} should never cause a failure", @@ -138,19 +130,22 @@ impl Address { /// Decode an address from Bech32m encoding pub fn decode(string: impl AsRef) -> Result { - let (prefix, hash_base32, variant) = - bech32::decode(string.as_ref()).map_err(Error::DecodeBech32)?; + let (prefix, hash_base32, variant) = bech32::decode(string.as_ref()) + .map_err(DecodeError::DecodeBech32)?; if prefix != ADDRESS_HRP { - return Err(Error::UnexpectedBech32Prefix(prefix)); + return Err(DecodeError::UnexpectedBech32Prefix( + prefix, + ADDRESS_HRP.into(), + )); } match variant { - ADDRESS_BECH32_VARIANT => {} - _ => return Err(Error::UnexpectedBech32Variant(variant)), + BECH32M_VARIANT => {} + _ => return Err(DecodeError::UnexpectedBech32Variant(variant)), } let bytes: Vec = FromBase32::from_base32(&hash_base32) - .map_err(Error::DecodeBase32)?; + .map_err(DecodeError::DecodeBase32)?; Self::try_from_fixed_len_string(&mut &bytes[..]) - .map_err(Error::InvalidAddressEncoding) + .map_err(DecodeError::InvalidInnerEncoding) } /// Try to get a raw hash of an address, only defined for established and @@ -326,7 +321,7 @@ impl Debug for Address { } impl FromStr for Address { - type Err = Error; + type Err = DecodeError; fn from_str(s: &str) -> Result { Address::decode(s) @@ -520,6 +515,22 @@ pub fn kartoffel() -> Address { Address::decode("atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90").expect("The token address decoding shouldn't fail") } +/// Temporary helper for testing +pub fn masp() -> Address { + Address::decode("atest1v4ehgw36xaryysfsx5unvve4g5my2vjz89p52sjxxgenzd348yuyyv3hg3pnjs35g5unvde4ca36y5").expect("The token address decoding shouldn't fail") +} + +/// Sentinel secret key to indicate a MASP source +pub fn masp_tx_key() -> crate::types::key::common::SecretKey { + use crate::types::key::common; + let bytes = [ + 0, 27, 238, 157, 32, 131, 242, 184, 142, 146, 189, 24, 249, 68, 165, + 205, 71, 213, 158, 25, 253, 52, 217, 87, 52, 171, 225, 110, 131, 238, + 58, 94, 56, + ]; + common::SecretKey::try_from_slice(bytes.as_ref()).unwrap() +} + /// Temporary helper for testing, a hash map of tokens addresses with their /// informal currency codes. pub fn tokens() -> HashMap { @@ -536,6 +547,23 @@ pub fn tokens() -> HashMap { .collect() } +/// Temporary helper for testing, a hash map of tokens addresses with their +/// MASP XAN incentive schedules. If the reward is (a, b) then a rewarded tokens +/// are dispensed for every b possessed tokens. +pub fn masp_rewards() -> HashMap { + vec![ + (xan(), (0, 100)), + (btc(), (1, 100)), + (eth(), (2, 100)), + (dot(), (3, 100)), + (schnitzel(), (4, 100)), + (apfel(), (5, 100)), + (kartoffel(), (6, 100)), + ] + .into_iter() + .collect() +} + #[cfg(test)] pub mod tests { use proptest::prelude::*; diff --git a/shared/src/types/intent.rs b/shared/src/types/intent.rs index c3effbb4fc..9566586f02 100644 --- a/shared/src/types/intent.rs +++ b/shared/src/types/intent.rs @@ -317,12 +317,16 @@ mod tests { target: albert_addr.clone(), token: Address::from_str(BTC).unwrap(), amount: token::Amount::from(100), + key: None, + shielded: None, }, token::Transfer { source: albert_addr, target: bertha_addr, token: Address::from_str(XAN).unwrap(), amount: token::Amount::from(1), + key: None, + shielded: None, }, ] .into_iter(), @@ -425,12 +429,16 @@ mod tests { target: albert_addr.clone(), token: Address::from_str(BTC).unwrap(), amount: token::Amount::from(100), + key: None, + shielded: None, }, token::Transfer { source: albert_addr, target: bertha_addr, token: Address::from_str(XAN).unwrap(), amount: token::Amount::from(1), + key: None, + shielded: None, }, ] .into_iter(), diff --git a/shared/src/types/masp.rs b/shared/src/types/masp.rs new file mode 100644 index 0000000000..f14addf4da --- /dev/null +++ b/shared/src/types/masp.rs @@ -0,0 +1,483 @@ +//! MASP types + +use std::fmt::Display; +use std::io::{Error, ErrorKind}; +use std::str::FromStr; + +use bech32::{FromBase32, ToBase32}; +use borsh::{BorshDeserialize, BorshSerialize}; +use sha2::{Digest, Sha256}; + +use crate::types::address::{ + masp, Address, DecodeError, BECH32M_VARIANT, HASH_LEN, +}; + +/// human-readable part of Bech32m encoded address +// TODO remove "test" suffix for live network +const EXT_FULL_VIEWING_KEY_HRP: &str = "xfvktest"; +const PAYMENT_ADDRESS_HRP: &str = "patest"; +const PINNED_PAYMENT_ADDRESS_HRP: &str = "ppatest"; +const EXT_SPENDING_KEY_HRP: &str = "xsktest"; + +/// Wrapper for masp_primitive's FullViewingKey +#[derive( + Clone, + Debug, + Copy, + Hash, + BorshSerialize, + BorshDeserialize, + Eq, + PartialEq, + PartialOrd, + Ord, +)] +pub struct ExtendedViewingKey(masp_primitives::zip32::ExtendedFullViewingKey); + +impl Display for ExtendedViewingKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut bytes = [0; 169]; + self.0 + .write(&mut bytes[..]) + .expect("should be able to serialize an ExtendedFullViewingKey"); + let encoded = bech32::encode( + EXT_FULL_VIEWING_KEY_HRP, + bytes.to_base32(), + BECH32M_VARIANT, + ) + .unwrap_or_else(|_| { + panic!( + "The human-readable part {} should never cause a failure", + EXT_FULL_VIEWING_KEY_HRP + ) + }); + write!(f, "{encoded}") + } +} + +impl FromStr for ExtendedViewingKey { + type Err = DecodeError; + + fn from_str(string: &str) -> Result { + let (prefix, base32, variant) = + bech32::decode(string).map_err(DecodeError::DecodeBech32)?; + if prefix != EXT_FULL_VIEWING_KEY_HRP { + return Err(DecodeError::UnexpectedBech32Prefix( + prefix, + EXT_FULL_VIEWING_KEY_HRP.into(), + )); + } + match variant { + BECH32M_VARIANT => {} + _ => return Err(DecodeError::UnexpectedBech32Variant(variant)), + } + let bytes: Vec = FromBase32::from_base32(&base32) + .map_err(DecodeError::DecodeBase32)?; + masp_primitives::zip32::ExtendedFullViewingKey::read(&mut &bytes[..]) + .map_err(DecodeError::InvalidInnerEncoding) + .map(Self) + } +} + +impl From + for masp_primitives::zip32::ExtendedFullViewingKey +{ + fn from(key: ExtendedViewingKey) -> Self { + key.0 + } +} + +impl From + for ExtendedViewingKey +{ + fn from(key: masp_primitives::zip32::ExtendedFullViewingKey) -> Self { + Self(key) + } +} + +impl serde::Serialize for ExtendedViewingKey { + fn serialize( + &self, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + let encoded = self.to_string(); + serde::Serialize::serialize(&encoded, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for ExtendedViewingKey { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + let encoded: String = serde::Deserialize::deserialize(deserializer)?; + Self::from_str(&encoded).map_err(D::Error::custom) + } +} + +/// Wrapper for masp_primitive's PaymentAddress +#[derive( + Clone, + Debug, + Copy, + PartialOrd, + Ord, + Eq, + PartialEq, + Hash, + BorshSerialize, + BorshDeserialize, +)] +pub struct PaymentAddress(masp_primitives::primitives::PaymentAddress, bool); + +impl PaymentAddress { + /// Turn this PaymentAddress into a pinned/unpinned one + pub fn pinned(self, pin: bool) -> PaymentAddress { + PaymentAddress(self.0, pin) + } + + /// Determine whether this PaymentAddress is pinned + pub fn is_pinned(&self) -> bool { + self.1 + } + + /// Hash this payment address + pub fn hash(&self) -> String { + let bytes = (self.0, self.1) + .try_to_vec() + .expect("Payment address encoding shouldn't fail"); + let mut hasher = Sha256::new(); + hasher.update(bytes); + // hex of the first 40 chars of the hash + format!("{:.width$X}", hasher.finalize(), width = HASH_LEN) + } +} + +impl From for masp_primitives::primitives::PaymentAddress { + fn from(addr: PaymentAddress) -> Self { + addr.0 + } +} + +impl From for PaymentAddress { + fn from(addr: masp_primitives::primitives::PaymentAddress) -> Self { + Self(addr, false) + } +} + +impl Display for PaymentAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let bytes = self.0.to_bytes(); + let hrp = if self.1 { + PINNED_PAYMENT_ADDRESS_HRP + } else { + PAYMENT_ADDRESS_HRP + }; + let encoded = bech32::encode(hrp, bytes.to_base32(), BECH32M_VARIANT) + .unwrap_or_else(|_| { + panic!( + "The human-readable part {} should never cause a failure", + PAYMENT_ADDRESS_HRP + ) + }); + write!(f, "{encoded}") + } +} + +impl FromStr for PaymentAddress { + type Err = DecodeError; + + fn from_str(string: &str) -> Result { + let (prefix, base32, variant) = + bech32::decode(string).map_err(DecodeError::DecodeBech32)?; + let pinned = if prefix == PAYMENT_ADDRESS_HRP { + false + } else if prefix == PINNED_PAYMENT_ADDRESS_HRP { + true + } else { + return Err(DecodeError::UnexpectedBech32Prefix( + prefix, + PAYMENT_ADDRESS_HRP.into(), + )); + }; + match variant { + BECH32M_VARIANT => {} + _ => return Err(DecodeError::UnexpectedBech32Variant(variant)), + } + let addr_len_err = |_| { + DecodeError::InvalidInnerEncoding(Error::new( + ErrorKind::InvalidData, + "expected 43 bytes for the payment address", + )) + }; + let addr_data_err = || { + DecodeError::InvalidInnerEncoding(Error::new( + ErrorKind::InvalidData, + "invalid payment address provided", + )) + }; + let bytes: Vec = FromBase32::from_base32(&base32) + .map_err(DecodeError::DecodeBase32)?; + masp_primitives::primitives::PaymentAddress::from_bytes( + &bytes.try_into().map_err(addr_len_err)?, + ) + .ok_or_else(addr_data_err) + .map(|x| Self(x, pinned)) + } +} + +impl serde::Serialize for PaymentAddress { + fn serialize( + &self, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + let encoded = self.to_string(); + serde::Serialize::serialize(&encoded, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for PaymentAddress { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + let encoded: String = serde::Deserialize::deserialize(deserializer)?; + Self::from_str(&encoded).map_err(D::Error::custom) + } +} + +/// Wrapper for masp_primitive's ExtendedSpendingKey +#[derive(Clone, Debug, Copy, BorshSerialize, BorshDeserialize)] +pub struct ExtendedSpendingKey(masp_primitives::zip32::ExtendedSpendingKey); + +impl Display for ExtendedSpendingKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut bytes = [0; 169]; + self.0 + .write(&mut &mut bytes[..]) + .expect("should be able to serialize an ExtendedSpendingKey"); + let encoded = bech32::encode( + EXT_SPENDING_KEY_HRP, + bytes.to_base32(), + BECH32M_VARIANT, + ) + .unwrap_or_else(|_| { + panic!( + "The human-readable part {} should never cause a failure", + EXT_SPENDING_KEY_HRP + ) + }); + write!(f, "{encoded}") + } +} + +impl FromStr for ExtendedSpendingKey { + type Err = DecodeError; + + fn from_str(string: &str) -> Result { + let (prefix, base32, variant) = + bech32::decode(string).map_err(DecodeError::DecodeBech32)?; + if prefix != EXT_SPENDING_KEY_HRP { + return Err(DecodeError::UnexpectedBech32Prefix( + prefix, + EXT_SPENDING_KEY_HRP.into(), + )); + } + match variant { + BECH32M_VARIANT => {} + _ => return Err(DecodeError::UnexpectedBech32Variant(variant)), + } + let bytes: Vec = FromBase32::from_base32(&base32) + .map_err(DecodeError::DecodeBase32)?; + masp_primitives::zip32::ExtendedSpendingKey::read(&mut &bytes[..]) + .map_err(DecodeError::InvalidInnerEncoding) + .map(Self) + } +} + +impl From for masp_primitives::zip32::ExtendedSpendingKey { + fn from(key: ExtendedSpendingKey) -> Self { + key.0 + } +} + +impl From for ExtendedSpendingKey { + fn from(key: masp_primitives::zip32::ExtendedSpendingKey) -> Self { + Self(key) + } +} + +impl serde::Serialize for ExtendedSpendingKey { + fn serialize( + &self, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + let encoded = self.to_string(); + serde::Serialize::serialize(&encoded, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for ExtendedSpendingKey { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + let encoded: String = serde::Deserialize::deserialize(deserializer)?; + Self::from_str(&encoded).map_err(D::Error::custom) + } +} + +/// Represents a source of funds for a transfer +#[derive(Debug, Clone)] +pub enum TransferSource { + /// A transfer coming from a transparent address + Address(Address), + /// A transfer coming from a shielded address + ExtendedSpendingKey(ExtendedSpendingKey), +} + +impl TransferSource { + /// Get the transparent address that this source would effectively draw from + pub fn effective_address(&self) -> Address { + match self { + Self::Address(x) => x.clone(), + // An ExtendedSpendingKey for a source effectively means that + // assets will be drawn from the MASP + Self::ExtendedSpendingKey(_) => masp(), + } + } + + /// Get the contained ExtendedSpendingKey contained, if any + pub fn spending_key(&self) -> Option { + match self { + Self::ExtendedSpendingKey(x) => Some(*x), + _ => None, + } + } +} + +/// Represents a target for the funds of a transfer +#[derive(Debug, Clone)] +pub enum TransferTarget { + /// A transfer going to a transparent address + Address(Address), + /// A transfer going to a shielded address + PaymentAddress(PaymentAddress), +} + +impl TransferTarget { + /// Get the transparent address that this target would effectively go to + pub fn effective_address(&self) -> Address { + match self { + Self::Address(x) => x.clone(), + // An ExtendedSpendingKey for a source effectively means that + // assets will be drawn from the MASP + Self::PaymentAddress(_) => masp(), + } + } + + /// Get the contained PaymentAddress, if any + pub fn payment_address(&self) -> Option { + match self { + Self::PaymentAddress(x) => Some(*x), + _ => None, + } + } + + /// Get the contained Address, if any + pub fn address(&self) -> Option
{ + match self { + Self::Address(x) => Some(x.clone()), + _ => None, + } + } +} + +/// Represents the owner of arbitrary funds +#[allow(clippy::large_enum_variant)] +#[derive( + Debug, + Clone, + Hash, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + PartialOrd, + Ord, +)] +pub enum BalanceOwner { + /// A balance stored at a transparent address + Address(Address), + /// A balance stored at a shielded address + FullViewingKey(ExtendedViewingKey), + /// A balance stored at a payment address + PaymentAddress(PaymentAddress), +} + +impl BalanceOwner { + /// Get the contained Address, if any + pub fn address(&self) -> Option
{ + match self { + Self::Address(x) => Some(x.clone()), + _ => None, + } + } + + /// Get the contained FullViewingKey, if any + pub fn full_viewing_key(&self) -> Option { + match self { + Self::FullViewingKey(x) => Some(*x), + _ => None, + } + } + + /// Get the contained PaymentAddress, if any + pub fn payment_address(&self) -> Option { + match self { + Self::PaymentAddress(x) => Some(*x), + _ => None, + } + } +} + +/// Represents any MASP value +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Clone)] +pub enum MaspValue { + /// A MASP PaymentAddress + PaymentAddress(PaymentAddress), + /// A MASP ExtendedSpendingKey + ExtendedSpendingKey(ExtendedSpendingKey), + /// A MASP FullViewingKey + FullViewingKey(ExtendedViewingKey), +} + +impl FromStr for MaspValue { + type Err = DecodeError; + + fn from_str(s: &str) -> Result { + // Try to decode this value first as a PaymentAddress, then as an + // ExtendedSpendingKey, then as FullViewingKey + PaymentAddress::from_str(s) + .map(Self::PaymentAddress) + .or_else(|_err| { + ExtendedSpendingKey::from_str(s).map(Self::ExtendedSpendingKey) + }) + .or_else(|_err| { + ExtendedViewingKey::from_str(s).map(Self::FullViewingKey) + }) + } +} diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 4b8d7288ca..53e6732d16 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -9,6 +9,7 @@ pub mod ibc; pub mod intent; pub mod internal; pub mod key; +pub mod masp; pub mod matchmaker; pub mod nft; pub mod storage; diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index fc87bc8d51..a0bf65fdb3 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -5,8 +5,10 @@ use std::num::ParseIntError; use std::ops::{Add, Div, Mul, Rem, Sub}; use std::str::FromStr; +use bit_vec::BitVec; +use borsh::maybestd::io::Write; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use thiserror::Error; #[cfg(feature = "ferveo-tpke")] @@ -22,7 +24,7 @@ pub enum Error { #[error("TEMPORARY error: {error}")] Temporary { error: String }, #[error("Error parsing address: {0}")] - ParseAddress(address::Error), + ParseAddress(address::DecodeError), #[error("Error parsing address from a storage key")] ParseAddressFromKey, #[error("Reserved prefix or string is specified: {0}")] @@ -44,6 +46,115 @@ pub const VP_KEY_PREFIX: char = '?'; /// The reserved storage key for validity predicates pub const RESERVED_VP_KEY: &str = "?"; +/// Transaction index within block. +#[derive( + Default, + Clone, + Copy, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Debug, + Serialize, + Deserialize, +)] +pub struct TxIndex(pub u32); + +impl Display for TxIndex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Add for TxIndex { + type Output = TxIndex; + + fn add(self, rhs: u32) -> Self::Output { + Self(self.0 + rhs) + } +} + +impl From for u32 { + fn from(index: TxIndex) -> Self { + index.0 + } +} + +fn serialize_bitvec(x: &BitVec, s: S) -> std::result::Result +where + S: Serializer, +{ + Serialize::serialize(&x.to_bytes(), s) +} + +fn deserialize_bitvec<'de, D>( + deserializer: D, +) -> std::result::Result +where + D: Deserializer<'de>, +{ + let s: Vec = Deserialize::deserialize(deserializer)?; + Ok(BitVec::from_bytes(&s)) +} + +/// Represents the accepted transactions in a block +#[derive( + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Debug, + Serialize, + Deserialize, + Default, +)] +pub struct BlockResults( + #[serde(serialize_with = "serialize_bitvec")] + #[serde(deserialize_with = "deserialize_bitvec")] + BitVec, +); + +impl BlockResults { + /// Create `len` rejection results + pub fn with_len(len: usize) -> Self { + BlockResults(BitVec::from_elem(len, true)) + } + + /// Accept the tx at the given position + pub fn accept(&mut self, idx: usize) { + self.0.set(idx, false) + } + + /// Reject the tx at the given position + pub fn reject(&mut self, idx: usize) { + self.0.set(idx, true) + } + + /// Check if the tx at the given position is accepted + pub fn is_accepted(&self, idx: usize) -> bool { + !self.0[idx] + } +} + +impl BorshSerialize for BlockResults { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + BorshSerialize::serialize(&self.0.to_bytes(), writer) + } +} + +impl BorshDeserialize for BlockResults { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + let vec: Vec<_> = BorshDeserialize::deserialize(buf)?; + Ok(Self(BitVec::from_bytes(&vec))) + } +} + /// Height of a block, i.e. the level. #[derive( Default, @@ -520,6 +631,12 @@ impl Epoch { pub fn next(&self) -> Self { Self(self.0 + 1) } + + /// Change to the previous epoch. This will underflow if the given epoch is + /// `0`. + pub fn prev(&self) -> Self { + Self(self.0 - 1) + } } impl Add for Epoch { diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index f3e4cd4ed2..74011296c1 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -6,10 +6,13 @@ use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use masp_primitives::transaction::Transaction; use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::types::address::{Address, Error as AddressError, InternalAddress}; +use crate::types::address::{ + masp, Address, DecodeError as AddressError, InternalAddress, +}; use crate::types::ibc::data::FungibleTokenPacketData; use crate::types::storage::{DbKeySeg, Key, KeySeg}; @@ -148,6 +151,27 @@ impl Add for Amount { } } +impl Mul for Amount { + type Output = Amount; + + fn mul(mut self, rhs: u64) -> Self::Output { + self.micro *= rhs; + self + } +} + +/// A combination of Euclidean division and fractions: +/// x*(a,b) = (a*(x//b), x%b) +impl Mul<(u64, u64)> for Amount { + type Output = (Amount, Amount); + + fn mul(mut self, rhs: (u64, u64)) -> Self::Output { + let ant = Amount::from((self.micro / rhs.1) * rhs.0); + self.micro %= rhs.1; + (ant, self) + } +} + impl Mul for u64 { type Output = Amount; @@ -233,6 +257,14 @@ impl From for Change { /// Key segment for a balance key pub const BALANCE_STORAGE_KEY: &str = "balance"; +/// Key segment for head shielded transaction pointer key +pub const HEAD_TX_KEY: &str = "head-tx"; +/// Key segment prefix for shielded transaction key +pub const TX_KEY_PREFIX: &str = "tx-"; +/// Key segment prefix for MASP conversions +pub const CONVERSION_KEY_PREFIX: &str = "conv"; +/// Key segment prefix for pinned shielded transactions +pub const PIN_KEY_PREFIX: &str = "pin-"; /// Obtain a storage key for user's balance. pub fn balance_key(token_addr: &Address, owner: &Address) -> Key { @@ -297,6 +329,21 @@ pub fn is_non_owner_balance_key(key: &Key) -> Option<&Address> { } } +/// Check if the given storage key is a masp key +pub fn is_masp_key(key: &Key) -> bool { + match &key.segments[..] { + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] + if *addr == masp() + && (key == HEAD_TX_KEY + || key.starts_with(TX_KEY_PREFIX) + || key.starts_with(PIN_KEY_PREFIX)) => + { + true + } + _ => false, + } +} + /// A simple bilateral token transfer #[derive( Debug, @@ -320,6 +367,10 @@ pub struct Transfer { pub token: Address, /// The amount of tokens pub amount: Amount, + /// The unused storage location at which to place TxId + pub key: Option, + /// Shielded transaction part + pub shielded: Option, } #[allow(missing_docs)] @@ -355,6 +406,8 @@ impl TryFrom for Transfer { target, token, amount, + key: None, + shielded: None, }) } } diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index ac9f89c31e..fca75f633f 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -21,7 +21,7 @@ use crate::types::address::{self, Address}; use crate::types::ibc::IbcEvent; use crate::types::internal::HostEnvResult; use crate::types::key::*; -use crate::types::storage::Key; +use crate::types::storage::{Key, TxIndex}; use crate::vm::memory::VmMemory; use crate::vm::prefix_iter::{PrefixIteratorId, PrefixIterators}; use crate::vm::types::KeyVal; @@ -58,7 +58,7 @@ pub enum TxRuntimeError { #[error("Encoding error: {0}")] EncodingError(std::io::Error), #[error("Address error: {0}")] - AddressError(address::Error), + AddressError(address::DecodeError), #[error("Numeric conversion error: {0}")] NumConversionError(TryFromIntError), #[error("Memory error: {0}")] @@ -96,6 +96,9 @@ where pub iterators: MutHostRef<'a, &'a PrefixIterators<'a, DB>>, /// Transaction gas meter. pub gas_meter: MutHostRef<'a, &'a BlockGasMeter>, + /// The transaction index is used to identify a shielded transaction's + /// parent + pub tx_index: HostRef<'a, &'a TxIndex>, /// The verifiers whose validity predicates should be triggered. pub verifiers: MutHostRef<'a, &'a BTreeSet
>, /// Cache for 2-step reads from host environment. @@ -133,6 +136,7 @@ where write_log: &mut WriteLog, iterators: &mut PrefixIterators<'a, DB>, gas_meter: &mut BlockGasMeter, + tx_index: &TxIndex, verifiers: &mut BTreeSet
, result_buffer: &mut Option>, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, @@ -142,6 +146,7 @@ where let write_log = unsafe { MutHostRef::new(write_log) }; let iterators = unsafe { MutHostRef::new(iterators) }; let gas_meter = unsafe { MutHostRef::new(gas_meter) }; + let tx_index = unsafe { HostRef::new(tx_index) }; let verifiers = unsafe { MutHostRef::new(verifiers) }; let result_buffer = unsafe { MutHostRef::new(result_buffer) }; #[cfg(feature = "wasm-runtime")] @@ -153,6 +158,7 @@ where write_log, iterators, gas_meter, + tx_index, verifiers, result_buffer, #[cfg(feature = "wasm-runtime")] @@ -194,6 +200,7 @@ where write_log: self.write_log.clone(), iterators: self.iterators.clone(), gas_meter: self.gas_meter.clone(), + tx_index: self.tx_index.clone(), verifiers: self.verifiers.clone(), result_buffer: self.result_buffer.clone(), #[cfg(feature = "wasm-runtime")] @@ -241,6 +248,9 @@ where pub gas_meter: MutHostRef<'a, &'a VpGasMeter>, /// The transaction code is used for signature verification pub tx: HostRef<'a, &'a Tx>, + /// The transaction index is used to identify a shielded transaction's + /// parent + pub tx_index: HostRef<'a, &'a TxIndex>, /// The runner of the [`vp_eval`] function pub eval_runner: HostRef<'a, &'a EVAL>, /// Cache for 2-step reads from host environment. @@ -305,6 +315,7 @@ where write_log: &WriteLog, gas_meter: &mut VpGasMeter, tx: &Tx, + tx_index: &TxIndex, iterators: &mut PrefixIterators<'a, DB>, verifiers: &BTreeSet
, result_buffer: &mut Option>, @@ -318,6 +329,7 @@ where write_log, gas_meter, tx, + tx_index, iterators, verifiers, result_buffer, @@ -368,6 +380,7 @@ where write_log: &WriteLog, gas_meter: &mut VpGasMeter, tx: &Tx, + tx_index: &TxIndex, iterators: &mut PrefixIterators<'a, DB>, verifiers: &BTreeSet
, result_buffer: &mut Option>, @@ -379,6 +392,7 @@ where let storage = unsafe { HostRef::new(storage) }; let write_log = unsafe { HostRef::new(write_log) }; let tx = unsafe { HostRef::new(tx) }; + let tx_index = unsafe { HostRef::new(tx_index) }; let iterators = unsafe { MutHostRef::new(iterators) }; let gas_meter = unsafe { MutHostRef::new(gas_meter) }; let verifiers = unsafe { HostRef::new(verifiers) }; @@ -394,6 +408,7 @@ where iterators, gas_meter, tx, + tx_index, eval_runner, result_buffer, keys_changed, @@ -421,6 +436,7 @@ where iterators: self.iterators.clone(), gas_meter: self.gas_meter.clone(), tx: self.tx.clone(), + tx_index: self.tx_index.clone(), eval_runner: self.eval_runner.clone(), result_buffer: self.result_buffer.clone(), keys_changed: self.keys_changed.clone(), @@ -1456,6 +1472,42 @@ where Ok(height.0) } +/// Getting the block height function exposed to the wasm VM Tx +/// environment. The height is that of the block to which the current +/// transaction is being applied. +pub fn tx_get_tx_index( + env: &TxEnv, +) -> TxResult +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + let tx_index = unsafe { env.ctx.tx_index.get() }; + tx_add_gas(env, crate::vm::host_env::gas::MIN_STORAGE_GAS)?; + Ok(tx_index.0) +} + +/// Getting the block height function exposed to the wasm VM VP +/// environment. The height is that of the block to which the current +/// transaction is being applied. +pub fn vp_get_tx_index( + env: &VpEnv, +) -> vp_env::Result +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + EVAL: VpEvaluator, + CA: WasmCacheAccess, +{ + let gas_meter = unsafe { env.ctx.gas_meter.get() }; + let tx_index = unsafe { env.ctx.tx_index.get() }; + let tx_idx = vp_env::get_tx_index(gas_meter, tx_index)?; + Ok(tx_idx.0) +} + /// Getting the block hash function exposed to the wasm VM Tx environment. The /// hash is that of the block to which the current transaction is being applied. pub fn tx_get_block_hash( @@ -1770,6 +1822,7 @@ pub mod testing { iterators: &mut PrefixIterators<'static, DB>, verifiers: &mut BTreeSet
, gas_meter: &mut BlockGasMeter, + tx_index: &TxIndex, result_buffer: &mut Option>, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, #[cfg(feature = "wasm-runtime")] tx_wasm_cache: &mut TxCache, @@ -1785,6 +1838,7 @@ pub mod testing { write_log, iterators, gas_meter, + tx_index, verifiers, result_buffer, #[cfg(feature = "wasm-runtime")] @@ -1803,6 +1857,7 @@ pub mod testing { iterators: &mut PrefixIterators<'static, DB>, gas_meter: &mut VpGasMeter, tx: &Tx, + tx_index: &TxIndex, verifiers: &BTreeSet
, result_buffer: &mut Option>, keys_changed: &BTreeSet, @@ -1822,6 +1877,7 @@ pub mod testing { write_log, gas_meter, tx, + tx_index, iterators, verifiers, result_buffer, diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index 3b6715f383..d0abee6388 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -73,6 +73,7 @@ where "anoma_tx_init_account" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_init_account), "anoma_tx_emit_ibc_event" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_emit_ibc_event), "anoma_tx_get_chain_id" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_chain_id), + "anoma_tx_get_tx_index" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_tx_index), "anoma_tx_get_block_height" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_block_height), "anoma_tx_get_block_time" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_block_time), "anoma_tx_get_block_hash" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_block_hash), @@ -110,6 +111,7 @@ where "anoma_vp_iter_pre_next" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_pre_next), "anoma_vp_iter_post_next" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_post_next), "anoma_vp_get_chain_id" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_chain_id), + "anoma_vp_get_tx_index" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_tx_index), "anoma_vp_get_block_height" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_block_height), "anoma_vp_get_block_hash" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_block_hash), "anoma_vp_get_tx_code_hash" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_tx_code_hash), diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 75fbfb4add..f7a4ca82cd 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -16,7 +16,7 @@ use crate::ledger::storage::{self, Storage, StorageHasher}; use crate::proto::Tx; use crate::types::address::Address; use crate::types::internal::HostEnvResult; -use crate::types::storage::Key; +use crate::types::storage::{Key, TxIndex}; use crate::vm::host_env::{TxEnv, VpCtx, VpEnv, VpEvaluator}; use crate::vm::prefix_iter::PrefixIterators; use crate::vm::types::VpInput; @@ -70,10 +70,12 @@ pub type Result = std::result::Result; /// Execute a transaction code. Returns the set verifiers addresses requested by /// the transaction. +#[allow(clippy::too_many_arguments)] pub fn tx( storage: &Storage, write_log: &mut WriteLog, gas_meter: &mut BlockGasMeter, + tx_index: &TxIndex, tx_code: impl AsRef<[u8]>, tx_data: impl AsRef<[u8]>, vp_wasm_cache: &mut VpCache, @@ -100,6 +102,7 @@ where write_log, &mut iterators, gas_meter, + tx_index, &mut verifiers, &mut result_buffer, vp_wasm_cache, @@ -155,6 +158,7 @@ where pub fn vp( vp_code: impl AsRef<[u8]>, tx: &Tx, + tx_index: &TxIndex, address: &Address, storage: &Storage, write_log: &WriteLog, @@ -196,6 +200,7 @@ where write_log, gas_meter, tx, + tx_index, &mut iterators, verifiers, &mut result_buffer, @@ -465,6 +470,7 @@ mod tests { let storage = TestStorage::default(); let mut write_log = WriteLog::default(); let mut gas_meter = BlockGasMeter::default(); + let tx_index = TxIndex::default(); // This code will allocate memory of the given size let tx_code = @@ -484,6 +490,7 @@ mod tests { &storage, &mut write_log, &mut gas_meter, + &tx_index, tx_code.clone(), tx_data, &mut vp_cache, @@ -498,6 +505,7 @@ mod tests { &storage, &mut write_log, &mut gas_meter, + &tx_index, tx_code, tx_data, &mut vp_cache, @@ -519,6 +527,7 @@ mod tests { let mut gas_meter = VpGasMeter::new(0); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); + let tx_index = TxIndex::default(); // This code will call `eval` with the other VP below let vp_eval = std::fs::read(VP_EVAL_WASM).expect("cannot load wasm"); @@ -544,6 +553,7 @@ mod tests { let passed = vp( vp_eval.clone(), &tx, + &tx_index, &addr, &storage, &write_log, @@ -570,6 +580,7 @@ mod tests { let passed = vp( vp_eval, &tx, + &tx_index, &addr, &storage, &write_log, @@ -593,6 +604,7 @@ mod tests { let mut gas_meter = VpGasMeter::new(0); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); + let tx_index = TxIndex::default(); // This code will allocate memory of the given size let vp_code = @@ -609,6 +621,7 @@ mod tests { let result = vp( vp_code.clone(), &tx, + &tx_index, &addr, &storage, &write_log, @@ -626,6 +639,7 @@ mod tests { let error = vp( vp_code, &tx, + &tx_index, &addr, &storage, &write_log, @@ -646,6 +660,7 @@ mod tests { let storage = TestStorage::default(); let mut write_log = WriteLog::default(); let mut gas_meter = BlockGasMeter::default(); + let tx_index = TxIndex::default(); let tx_no_op = std::fs::read(TX_NO_OP_WASM).expect("cannot load wasm"); @@ -664,6 +679,7 @@ mod tests { &storage, &mut write_log, &mut gas_meter, + &tx_index, tx_no_op, tx_data, &mut vp_cache, @@ -698,6 +714,7 @@ mod tests { let mut gas_meter = VpGasMeter::new(0); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); + let tx_index = TxIndex::default(); let vp_code = std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); @@ -714,6 +731,7 @@ mod tests { let result = vp( vp_code, &tx, + &tx_index, &addr, &storage, &write_log, @@ -751,6 +769,7 @@ mod tests { let mut storage = TestStorage::default(); let mut write_log = WriteLog::default(); let mut gas_meter = BlockGasMeter::default(); + let tx_index = TxIndex::default(); let tx_read_key = std::fs::read(TX_READ_STORAGE_KEY_WASM).expect("cannot load wasm"); @@ -775,6 +794,7 @@ mod tests { &storage, &mut write_log, &mut gas_meter, + &tx_index, tx_read_key, tx_data, &mut vp_cache, @@ -796,6 +816,7 @@ mod tests { let mut gas_meter = VpGasMeter::new(0); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); + let tx_index = TxIndex::default(); let vp_read_key = std::fs::read(VP_READ_STORAGE_KEY_WASM).expect("cannot load wasm"); @@ -817,6 +838,7 @@ mod tests { let error = vp( vp_read_key, &tx, + &tx_index, &addr, &storage, &write_log, @@ -842,6 +864,7 @@ mod tests { let mut gas_meter = VpGasMeter::new(0); let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); + let tx_index = TxIndex::default(); // This code will call `eval` with the other VP below let vp_eval = std::fs::read(VP_EVAL_WASM).expect("cannot load wasm"); @@ -871,6 +894,7 @@ mod tests { let passed = vp( vp_eval, &tx, + &tx_index, &addr, &storage, &write_log, @@ -919,6 +943,7 @@ mod tests { .into_owned(); let tx_data = vec![]; + let tx_index = TxIndex::default(); let storage = TestStorage::default(); let mut write_log = WriteLog::default(); let mut gas_meter = BlockGasMeter::default(); @@ -930,6 +955,7 @@ mod tests { &storage, &mut write_log, &mut gas_meter, + &tx_index, tx_code, tx_data, &mut vp_cache, @@ -967,6 +993,7 @@ mod tests { .expect("unexpected error converting wat2wasm").into_owned(); let tx = Tx::new(vec![], None); + let tx_index = TxIndex::default(); let mut storage = TestStorage::default(); let addr = storage.address_gen.generate_address("rng seed"); let write_log = WriteLog::default(); @@ -977,6 +1004,7 @@ mod tests { vp( vp_code, &tx, + &tx_index, &addr, &storage, &write_log, diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index b276dd409e..0e7b6cf4b0 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -37,7 +37,11 @@ fn everything() { anoman_ledger .exp_string("Anoma ledger node started") .unwrap(); - anoman_ledger.exp_string("Tendermint node started").unwrap(); + if !cfg!(feature = "ABCI") { + anoman_ledger.exp_string("started node").unwrap(); + } else { + anoman_ledger.exp_string("Started node").unwrap(); + } anoman_ledger.exp_string("Committed block hash").unwrap(); let _bg_ledger = anoman_ledger.background(); diff --git a/tests/src/e2e/gossip_tests.rs b/tests/src/e2e/gossip_tests.rs index 5da19c0758..1776a84381 100644 --- a/tests/src/e2e/gossip_tests.rs +++ b/tests/src/e2e/gossip_tests.rs @@ -224,6 +224,7 @@ fn match_intents() -> Result<()> { let rpc_address = format!("http://{}", rpc_address); // Send intent A + let bertha_lower = BERTHA_KEY.to_lowercase(); let mut session_send_intent_a = run!( test, Bin::Client, @@ -236,7 +237,7 @@ fn match_intents() -> Result<()> { "--topic", "asset_v1", "--signing-key", - BERTHA_KEY, + &bertha_lower, "--ledger-address", &validator_one_rpc ], @@ -255,6 +256,7 @@ fn match_intents() -> Result<()> { let bg_matchmaker = matchmaker.background(); // Send intent B + let albert_lower = ALBERT_KEY.to_lowercase(); let mut session_send_intent_b = run!( test, Bin::Client, @@ -267,7 +269,7 @@ fn match_intents() -> Result<()> { "--topic", "asset_v1", "--signing-key", - ALBERT_KEY, + &albert_lower, "--ledger-address", &validator_one_rpc ], @@ -286,6 +288,7 @@ fn match_intents() -> Result<()> { let bg_matchmaker = matchmaker.background(); // Send intent C + let christel_lower = CHRISTEL_KEY.to_lowercase(); let mut session_send_intent_c = run!( test, Bin::Client, @@ -298,7 +301,7 @@ fn match_intents() -> Result<()> { "--topic", "asset_v1", "--signing-key", - CHRISTEL_KEY, + &christel_lower, "--ledger-address", &validator_one_rpc ], diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index cc0c45cf8c..325157e26e 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -3,6 +3,7 @@ use std::path::Path; use std::process::Command; use std::str::FromStr; +use std::time::{Duration, Instant}; use std::{env, time}; use color_eyre::eyre::Result; @@ -15,7 +16,7 @@ use namada::types::storage::Epoch; use namada_apps::config::{Config, TendermintMode}; use super::setup::{Test, ENV_VAR_DEBUG, ENV_VAR_USE_PREBUILT_BINARIES}; -use crate::e2e::setup::{Bin, Who, APPS_PACKAGE}; +use crate::e2e::setup::{sleep, Bin, Who, APPS_PACKAGE}; use crate::run; /// Find the address of an account by its alias from the wallet @@ -226,3 +227,25 @@ fn strip_trailing_newline(input: &str) -> &str { .or_else(|| input.strip_suffix('\n')) .unwrap_or(input) } + +/// Sleep until the next epoch starts +pub fn epoch_sleep( + test: &Test, + ledger_address: &str, + timeout_secs: u64, +) -> Result { + let old_epoch = get_epoch(test, ledger_address)?; + let start = Instant::now(); + let loop_timeout = Duration::new(timeout_secs, 0); + loop { + if Instant::now().duration_since(start) > loop_timeout { + panic!("Timed out waiting for the next epoch"); + } + let epoch = get_epoch(test, ledger_address)?; + if epoch > old_epoch { + break Ok(epoch); + } else { + sleep(10); + } + } +} diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 552e675e67..1a0260177c 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -12,12 +12,15 @@ use std::fs::{self, OpenOptions}; use std::path::PathBuf; use std::process::Command; +use std::str::FromStr; use std::sync::Arc; use std::time::{Duration, Instant}; use borsh::BorshSerialize; use color_eyre::eyre::Result; +use namada::types::address::{btc, eth, masp_rewards}; use namada::types::token; +use namada_apps::client::tx::ShieldedContext; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; @@ -26,7 +29,7 @@ use setup::constants::*; use super::setup::working_dir; use crate::e2e::helpers::{ - find_address, find_voting_power, get_actor_rpc, get_epoch, + epoch_sleep, find_address, find_voting_power, get_actor_rpc, get_epoch, }; use crate::e2e::setup::{self, sleep, Bin, Who}; use crate::{run, run_as}; @@ -60,13 +63,27 @@ fn run_ledger() -> Result<()> { /// In this test we: /// 1. Run 2 genesis validator ledger nodes and 1 non-validator node -/// 2. Submit a valid token transfer tx -/// 3. Check that all the nodes processed the tx with the same result +/// 2. Cross over epoch to check for consensus with multiple nodes +/// 3. Submit a valid token transfer tx +/// 4. Check that all the nodes processed the tx with the same result #[test] -fn test_node_connectivity() -> Result<()> { +fn test_node_connectivity_and_consensus() -> Result<()> { // Setup 2 genesis validator nodes - let test = - setup::network(|genesis| setup::add_validators(1, genesis), None)?; + let test = setup::network( + |genesis| { + let genesis = setup::add_validators(1, genesis); + let parameters = ParametersConfig { + min_duration: 1, + min_num_of_blocks: 5, + ..genesis.parameters + }; + GenesisConfig { + parameters, + ..genesis + } + }, + None, + )?; // 1. Run 2 genesis validator ledger nodes and 1 non-validator node let args = ["ledger"]; @@ -87,8 +104,11 @@ fn test_node_connectivity() -> Result<()> { let bg_validator_1 = validator_1.background(); let bg_non_validator = non_validator.background(); - // 2. Submit a valid token transfer tx + // 2. Cross over epoch to check for consensus with multiple nodes let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let _ = epoch_sleep(&test, &validator_one_rpc, 720)?; + + // 3. Submit a valid token transfer tx let tx_args = [ "transfer", "--source", @@ -112,7 +132,7 @@ fn test_node_connectivity() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // 3. Check that all the nodes processed the tx with the same result + // 4. Check that all the nodes processed the tx with the same result let mut validator_0 = bg_validator_0.foreground(); let mut validator_1 = bg_validator_1.foreground(); let mut non_validator = bg_non_validator.foreground(); @@ -401,98 +421,1247 @@ fn ledger_txs_and_queries() -> Result<()> { /// In this test we: /// 1. Run the ledger node -/// 2. Submit an invalid transaction (disallowed by state machine) -/// 3. Shut down the ledger -/// 4. Restart the ledger -/// 5. Submit and invalid transactions (malformed) +/// 2. Attempt to spend 10 BTC at SK(A) to PA(B) +/// 3. Attempt to spend 15 BTC at SK(A) to Bertha +/// 4. Send 20 BTC from Albert to PA(A) +/// 5. Attempt to spend 10 ETH at SK(A) to PA(B) +/// 6. Spend 7 BTC at SK(A) to PA(B) +/// 7. Spend 7 BTC at SK(A) to PA(B) +/// 8. Attempt to spend 7 BTC at SK(A) to PA(B) +/// 9. Spend 6 BTC at SK(A) to PA(B) +/// 10. Assert BTC balance at VK(A) is 0 +/// 11. Assert ETH balance at VK(A) is 0 +/// 12. Assert balance at VK(B) is 10 BTC +/// 13. Send 10 BTC from SK(B) to Bertha + #[test] -fn invalid_transactions() -> Result<()> { - let test = setup::single_node_net()?; +fn masp_txs_and_queries() -> Result<()> { + // Download the shielded pool parameters before starting node + let _ = ShieldedContext::new(PathBuf::new()); + // Lengthen epoch to ensure that a transaction can be constructed and + // submitted within the same block. Necessary to ensure that conversion is + // not invalidated. + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + min_duration: 3600, + ..genesis.parameters + }; + GenesisConfig { + parameters, + ..genesis + } + }, + None, + )?; // 1. Run the ledger node let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Anoma ledger node started")?; - // Wait to commit a block - ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; - let bg_ledger = ledger.background(); + ledger.exp_string("Anoma ledger node started")?; + if !cfg!(feature = "ABCI") { + ledger.exp_string("started node")?; + } else { + ledger.exp_string("Started node")?; + } - // 2. Submit a an invalid transaction (trying to mint tokens should fail - // in the token's VP) - let tx_data_path = test.test_dir.path().join("tx.data"); - let transfer = token::Transfer { - source: find_address(&test, DAEWON)?, - target: find_address(&test, ALBERT)?, - token: find_address(&test, XAN)?, - amount: token::Amount::whole(1), - }; - let data = transfer - .try_to_vec() - .expect("Encoding unsigned transfer shouldn't fail"); - let tx_wasm_path = wasm_abs_path(TX_MINT_TOKENS_WASM); - std::fs::write(&tx_data_path, data).unwrap(); - let tx_wasm_path = tx_wasm_path.to_string_lossy(); - let tx_data_path = tx_data_path.to_string_lossy(); + let _bg_ledger = ledger.background(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - let tx_args = vec![ - "tx", - "--code-path", - &tx_wasm_path, - "--data-path", - &tx_data_path, - "--signing-key", - DAEWON, - "--fee-amount", - "0", - "--gas-limit", - "0", - "--fee-token", - XAN, - "--ledger-address", - &validator_one_rpc, + let txs_args = vec![ + // 2. Attempt to spend 10 BTC at SK(A) to PA(B) + ( + vec![ + "transfer", + "--source", + A_SPENDING_KEY, + "--target", + AB_PAYMENT_ADDRESS, + "--token", + BTC, + "--amount", + "10", + "--ledger-address", + &validator_one_rpc, + ], + "No balance found", + ), + // 3. Attempt to spend 15 BTC at SK(A) to Bertha + ( + vec![ + "transfer", + "--source", + A_SPENDING_KEY, + "--target", + BERTHA, + "--token", + BTC, + "--amount", + "15", + "--ledger-address", + &validator_one_rpc, + ], + "No balance found", + ), + // 4. Send 20 BTC from Albert to PA(A) + ( + vec![ + "transfer", + "--source", + ALBERT, + "--target", + AA_PAYMENT_ADDRESS, + "--token", + BTC, + "--amount", + "20", + "--ledger-address", + &validator_one_rpc, + ], + "Transaction is valid", + ), + // 5. Attempt to spend 10 ETH at SK(A) to PA(B) + ( + vec![ + "transfer", + "--source", + A_SPENDING_KEY, + "--target", + AB_PAYMENT_ADDRESS, + "--token", + ETH, + "--amount", + "10", + "--ledger-address", + &validator_one_rpc, + ], + "No balance found", + ), + // 6. Spend 7 BTC at SK(A) to PA(B) + ( + vec![ + "transfer", + "--source", + A_SPENDING_KEY, + "--target", + AB_PAYMENT_ADDRESS, + "--token", + BTC, + "--amount", + "7", + "--ledger-address", + &validator_one_rpc, + ], + "Transaction is valid", + ), + // 7. Spend 7 BTC at SK(A) to PA(B) + ( + vec![ + "transfer", + "--source", + A_SPENDING_KEY, + "--target", + BB_PAYMENT_ADDRESS, + "--token", + BTC, + "--amount", + "7", + "--ledger-address", + &validator_one_rpc, + ], + "Transaction is valid", + ), + // 8. Attempt to spend 7 BTC at SK(A) to PA(B) + ( + vec![ + "transfer", + "--source", + A_SPENDING_KEY, + "--target", + BB_PAYMENT_ADDRESS, + "--token", + BTC, + "--amount", + "7", + "--ledger-address", + &validator_one_rpc, + ], + "ChangeIsNegative", + ), + // 9. Spend 6 BTC at SK(A) to PA(B) + ( + vec![ + "transfer", + "--source", + A_SPENDING_KEY, + "--target", + BB_PAYMENT_ADDRESS, + "--token", + BTC, + "--amount", + "6", + "--ledger-address", + &validator_one_rpc, + ], + "Transaction is valid", + ), + // 10. Assert BTC balance at VK(A) is 0 + ( + vec![ + "balance", + "--owner", + AA_VIEWING_KEY, + "--token", + BTC, + "--ledger-address", + &validator_one_rpc, + ], + "No shielded BTC balance found", + ), + // 11. Assert ETH balance at VK(A) is 0 + ( + vec![ + "balance", + "--owner", + AA_VIEWING_KEY, + "--token", + ETH, + "--ledger-address", + &validator_one_rpc, + ], + "No shielded ETH balance found", + ), + // 12. Assert balance at VK(B) is 10 BTC + ( + vec![ + "balance", + "--owner", + AB_VIEWING_KEY, + "--ledger-address", + &validator_one_rpc, + ], + "BTC: 20", + ), + // 13. Send 10 BTC from SK(B) to Bertha + ( + vec![ + "transfer", + "--source", + B_SPENDING_KEY, + "--target", + BERTHA, + "--token", + BTC, + "--amount", + "20", + "--ledger-address", + &validator_one_rpc, + ], + "Transaction is valid", + ), ]; - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction accepted")?; - client.exp_string("Transaction applied")?; - client.exp_string("Transaction is invalid")?; - client.exp_string(r#""code": "1"#)?; + // Wait till epoch boundary + let _ep0 = epoch_sleep(&test, &validator_one_rpc, 720)?; - client.assert_success(); - let mut ledger = bg_ledger.foreground(); - ledger.exp_string("some VPs rejected transaction")?; + for (tx_args, tx_result) in &txs_args { + for &dry_run in &[true, false] { + let tx_args = if dry_run && tx_args[0] == "transfer" { + vec![tx_args.clone(), vec!["--dry-run"]].concat() + } else { + tx_args.clone() + }; + let mut client = run!(test, Bin::Client, tx_args, Some(300))?; - // Wait to commit a block - ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + if *tx_result == "Transaction is valid" && !dry_run { + if !cfg!(feature = "ABCI") { + client.exp_string("Transaction accepted")?; + } + client.exp_string("Transaction applied")?; + } + client.exp_string(tx_result)?; + } + } - // 3. Shut it down - ledger.send_control('c')?; - // Wait for the node to stop running to finish writing the state and tx - // queue - ledger.exp_string("Anoma ledger node has shut down.")?; - ledger.exp_eof()?; - drop(ledger); + Ok(()) +} - // 4. Restart the ledger +/// In this test we: +/// 1. Run the ledger node +/// 2. Assert PPA(C) cannot be recognized by incorrect viewing key +/// 3. Assert PPA(C) has not transaction pinned to it +/// 4. Send 20 BTC from Albert to PPA(C) +/// 5. Assert PPA(C) has the 20 BTC transaction pinned to it + +#[test] +fn masp_pinned_txs() -> Result<()> { + // Download the shielded pool parameters before starting node + let _ = ShieldedContext::new(PathBuf::new()); + // Lengthen epoch to ensure that a transaction can be constructed and + // submitted within the same block. Necessary to ensure that conversion is + // not invalidated. + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + min_duration: 60, + ..genesis.parameters + }; + GenesisConfig { + parameters, + ..genesis + } + }, + None, + )?; + + // 1. Run the ledger node let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; ledger.exp_string("Anoma ledger node started")?; + if !cfg!(feature = "ABCI") { + ledger.exp_string("started node")?; + } else { + ledger.exp_string("Started node")?; + } - // There should be previous state now - ledger.exp_string("Last state root hash:")?; let _bg_ledger = ledger.background(); - // 5. Submit an invalid transactions (invalid token address) - let tx_args = vec![ - "transfer", - "--source", - DAEWON, - "--signing-key", - DAEWON, + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + // Wait till epoch boundary + let _ep0 = epoch_sleep(&test, &validator_one_rpc, 720)?; + + // Assert PPA(C) cannot be recognized by incorrect viewing key + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AC_PAYMENT_ADDRESS, + "--token", + BTC, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.send_line(AB_VIEWING_KEY)?; + client.exp_string("Supplied viewing key cannot decode transactions to")?; + client.assert_success(); + + // Assert PPA(C) has no transaction pinned to it + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AC_PAYMENT_ADDRESS, + "--token", + BTC, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.send_line(AC_VIEWING_KEY)?; + client.exp_string("has not yet been consumed")?; + client.assert_success(); + + // Send 20 BTC from Albert to PPA(C) + let mut client = run!( + test, + Bin::Client, + vec![ + "transfer", + "--source", + ALBERT, + "--target", + AC_PAYMENT_ADDRESS, + "--token", + BTC, + "--amount", + "20", + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("Transaction is valid")?; + client.assert_success(); + + // Assert PPA(C) has the 20 BTC transaction pinned to it + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AC_PAYMENT_ADDRESS, + "--token", + BTC, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.send_line(AC_VIEWING_KEY)?; + client.exp_string("Received 20 BTC")?; + client.assert_success(); + + // Assert PPA(C) has no XAN pinned to it + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AC_PAYMENT_ADDRESS, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.send_line(AC_VIEWING_KEY)?; + client.exp_string("Received no shielded XAN")?; + client.assert_success(); + + // Wait till epoch boundary + let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; + + // Assert PPA(C) does not XAN pinned to it on epoch boundary + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AC_PAYMENT_ADDRESS, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.send_line(AC_VIEWING_KEY)?; + client.exp_string("Received no shielded XAN")?; + client.assert_success(); + + Ok(()) +} + +/// In this test we verify that users of the MASP receive the correct rewards +/// for leaving their assets in the pool for varying periods of time. + +#[test] +fn masp_incentives() -> Result<()> { + // Download the shielded pool parameters before starting node + let _ = ShieldedContext::new(PathBuf::new()); + // Lengthen epoch to ensure that a transaction can be constructed and + // submitted within the same block. Necessary to ensure that conversion is + // not invalidated. + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + min_duration: 240, + ..genesis.parameters + }; + GenesisConfig { + parameters, + ..genesis + } + }, + None, + )?; + + // 1. Run the ledger node + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + + ledger.exp_string("Anoma ledger node started")?; + if !cfg!(feature = "ABCI") { + ledger.exp_string("started node")?; + } else { + ledger.exp_string("Started node")?; + } + + let _bg_ledger = ledger.background(); + + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + // Wait till epoch boundary + let ep0 = epoch_sleep(&test, &validator_one_rpc, 720)?; + + // Send 20 BTC from Albert to PA(A) + let mut client = run!( + test, + Bin::Client, + vec![ + "transfer", + "--source", + ALBERT, + "--target", + AA_PAYMENT_ADDRESS, + "--token", + BTC, + "--amount", + "20", + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("Transaction is valid")?; + client.assert_success(); + + // Assert BTC balance at VK(A) is 20 + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AA_VIEWING_KEY, + "--token", + BTC, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("BTC: 20")?; + client.assert_success(); + + // Assert XAN balance at VK(A) is 0 + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AA_VIEWING_KEY, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("No shielded XAN balance found")?; + client.assert_success(); + + let masp_rewards = masp_rewards(); + + // Wait till epoch boundary + let ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; + + // Assert BTC balance at VK(A) is 20 + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AA_VIEWING_KEY, + "--token", + BTC, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("BTC: 20")?; + client.assert_success(); + + let amt20 = token::Amount::from_str("20").unwrap(); + let amt30 = token::Amount::from_str("30").unwrap(); + + // Assert XAN balance at VK(A) is 20*BTC_reward*(epoch_1-epoch_0) + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AA_VIEWING_KEY, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string(&format!( + "XAN: {}", + (amt20 * masp_rewards[&btc()]).0 * (ep1.0 - ep0.0) + ))?; + client.assert_success(); + + // Assert XAN balance at MASP pool is 20*BTC_reward*(epoch_1-epoch_0) + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + MASP, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string(&format!( + "XAN: {}", + (amt20 * masp_rewards[&btc()]).0 * (ep1.0 - ep0.0) + ))?; + client.assert_success(); + + // Wait till epoch boundary + let ep2 = epoch_sleep(&test, &validator_one_rpc, 720)?; + + // Assert BTC balance at VK(A) is 20 + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AA_VIEWING_KEY, + "--token", + BTC, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("BTC: 20")?; + client.assert_success(); + + // Assert XAN balance at VK(A) is 20*BTC_reward*(epoch_2-epoch_0) + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AA_VIEWING_KEY, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string(&format!( + "XAN: {}", + (amt20 * masp_rewards[&btc()]).0 * (ep2.0 - ep0.0) + ))?; + client.assert_success(); + + // Assert XAN balance at MASP pool is 20*BTC_reward*(epoch_2-epoch_0) + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + MASP, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string(&format!( + "XAN: {}", + (amt20 * masp_rewards[&btc()]).0 * (ep2.0 - ep0.0) + ))?; + client.assert_success(); + + // Wait till epoch boundary + let ep3 = epoch_sleep(&test, &validator_one_rpc, 720)?; + + // Send 30 ETH from Albert to PA(B) + let mut client = run!( + test, + Bin::Client, + vec![ + "transfer", + "--source", + ALBERT, + "--target", + AB_PAYMENT_ADDRESS, + "--token", + ETH, + "--amount", + "30", + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("Transaction is valid")?; + client.assert_success(); + + // Assert ETH balance at VK(B) is 30 + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AB_VIEWING_KEY, + "--token", + ETH, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("ETH: 30")?; + client.assert_success(); + + // Assert XAN balance at VK(B) is 0 + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AB_VIEWING_KEY, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("No shielded XAN balance found")?; + client.assert_success(); + + // Wait till epoch boundary + let ep4 = epoch_sleep(&test, &validator_one_rpc, 720)?; + + // Assert ETH balance at VK(B) is 30 + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AB_VIEWING_KEY, + "--token", + ETH, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("ETH: 30")?; + client.assert_success(); + + // Assert XAN balance at VK(B) is 30*ETH_reward*(epoch_4-epoch_3) + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AB_VIEWING_KEY, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string(&format!( + "XAN: {}", + (amt30 * masp_rewards[ð()]).0 * (ep4.0 - ep3.0) + ))?; + client.assert_success(); + + // Assert XAN balance at MASP pool is + // 20*BTC_reward*(epoch_4-epoch_0)+30*ETH_reward*(epoch_4-epoch_3) + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + MASP, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string(&format!( + "XAN: {}", + ((amt20 * masp_rewards[&btc()]).0 * (ep4.0 - ep0.0)) + + ((amt30 * masp_rewards[ð()]).0 * (ep4.0 - ep3.0)) + ))?; + client.assert_success(); + + // Wait till epoch boundary + let ep5 = epoch_sleep(&test, &validator_one_rpc, 720)?; + + // Send 30 ETH from SK(B) to Christel + let mut client = run!( + test, + Bin::Client, + vec![ + "transfer", + "--source", + B_SPENDING_KEY, + "--target", + CHRISTEL, + "--token", + ETH, + "--amount", + "30", + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("Transaction is valid")?; + client.assert_success(); + + // Assert ETH balance at VK(B) is 0 + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AB_VIEWING_KEY, + "--token", + ETH, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("No shielded ETH balance found")?; + client.assert_success(); + + // Assert XAN balance at VK(B) is 30*ETH_reward*(epoch_5-epoch_3) + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AB_VIEWING_KEY, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string(&format!( + "XAN: {}", + (amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0) + ))?; + client.assert_success(); + + // Assert XAN balance at MASP pool is + // 20*BTC_reward*(epoch_5-epoch_0)+30*ETH_reward*(epoch_5-epoch_3) + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + MASP, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string(&format!( + "XAN: {}", + ((amt20 * masp_rewards[&btc()]).0 * (ep5.0 - ep0.0)) + + ((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)) + ))?; + client.assert_success(); + + // Wait till epoch boundary + let ep6 = epoch_sleep(&test, &validator_one_rpc, 720)?; + + // Send 20 BTC from SK(A) to Christel + let mut client = run!( + test, + Bin::Client, + vec![ + "transfer", + "--source", + A_SPENDING_KEY, + "--target", + CHRISTEL, + "--token", + BTC, + "--amount", + "20", + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("Transaction is valid")?; + client.assert_success(); + + // Assert BTC balance at VK(A) is 0 + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AA_VIEWING_KEY, + "--token", + BTC, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("No shielded BTC balance found")?; + client.assert_success(); + + // Assert XAN balance at VK(A) is 20*BTC_reward*(epoch_6-epoch_0) + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AA_VIEWING_KEY, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string(&format!( + "XAN: {}", + (amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0) + ))?; + client.assert_success(); + + // Assert XAN balance at MASP pool is + // 20*BTC_reward*(epoch_6-epoch_0)+20*ETH_reward*(epoch_5-epoch_3) + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + MASP, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string(&format!( + "XAN: {}", + ((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) + + ((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)) + ))?; + client.assert_success(); + + // Wait till epoch boundary + let _ep7 = epoch_sleep(&test, &validator_one_rpc, 720)?; + + // Assert XAN balance at VK(A) is 20*BTC_reward*(epoch_6-epoch_0) + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AA_VIEWING_KEY, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string(&format!( + "XAN: {}", + (amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0) + ))?; + client.assert_success(); + + // Assert XAN balance at VK(B) is 30*ETH_reward*(epoch_5-epoch_3) + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AB_VIEWING_KEY, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string(&format!( + "XAN: {}", + (amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0) + ))?; + client.assert_success(); + + // Assert XAN balance at MASP pool is + // 20*BTC_reward*(epoch_6-epoch_0)+30*ETH_reward*(epoch_5-epoch_3) + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + MASP, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string(&format!( + "XAN: {}", + ((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) + + ((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)) + ))?; + client.assert_success(); + + // Wait till epoch boundary to prevent conversion expiry during transaction + // construction + let _ep8 = epoch_sleep(&test, &validator_one_rpc, 720)?; + + // Send 30*ETH_reward*(epoch_5-epoch_3) XAN from SK(B) to Christel + let mut client = run!( + test, + Bin::Client, + vec![ + "transfer", + "--source", + B_SPENDING_KEY, + "--target", + CHRISTEL, + "--token", + XAN, + "--amount", + &((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)).to_string(), + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("Transaction is valid")?; + client.assert_success(); + + // Wait till epoch boundary + let _ep9 = epoch_sleep(&test, &validator_one_rpc, 720)?; + + // Send 20*BTC_reward*(epoch_6-epoch_0) XAN from SK(A) to Christel + let mut client = run!( + test, + Bin::Client, + vec![ + "transfer", + "--source", + A_SPENDING_KEY, + "--target", + BERTHA, + "--token", + XAN, + "--amount", + &((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)).to_string(), + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("Transaction is valid")?; + client.assert_success(); + + // Assert XAN balance at VK(A) is 0 + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AA_VIEWING_KEY, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("No shielded XAN balance found")?; + client.assert_success(); + + // Assert XAN balance at VK(B) is 0 + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + AB_VIEWING_KEY, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("No shielded XAN balance found")?; + client.assert_success(); + + // Assert XAN balance at MASP pool is 0 + let mut client = run!( + test, + Bin::Client, + vec![ + "balance", + "--owner", + MASP, + "--token", + XAN, + "--ledger-address", + &validator_one_rpc + ], + Some(300) + )?; + client.exp_string("XAN: 0")?; + client.assert_success(); + + Ok(()) +} + +/// In this test we: +/// 1. Run the ledger node +/// 2. Submit an invalid transaction (disallowed by state machine) +/// 3. Shut down the ledger +/// 4. Restart the ledger +/// 5. Submit and invalid transactions (malformed) +#[test] +fn invalid_transactions() -> Result<()> { + let test = setup::single_node_net()?; + + // 1. Run the ledger node + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + ledger.exp_string("Anoma ledger node started")?; + // Wait to commit a block + ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + + let bg_ledger = ledger.background(); + + // 2. Submit a an invalid transaction (trying to mint tokens should fail + // in the token's VP) + let tx_data_path = test.test_dir.path().join("tx.data"); + let transfer = token::Transfer { + source: find_address(&test, DAEWON)?, + target: find_address(&test, ALBERT)?, + token: find_address(&test, XAN)?, + amount: token::Amount::whole(1), + key: None, + shielded: None, + }; + let data = transfer + .try_to_vec() + .expect("Encoding unsigned transfer shouldn't fail"); + let tx_wasm_path = wasm_abs_path(TX_MINT_TOKENS_WASM); + std::fs::write(&tx_data_path, data).unwrap(); + let tx_wasm_path = tx_wasm_path.to_string_lossy(); + let tx_data_path = tx_data_path.to_string_lossy(); + + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + let daewon_lower = DAEWON.to_lowercase(); + let tx_args = vec![ + "tx", + "--code-path", + &tx_wasm_path, + "--data-path", + &tx_data_path, + "--signing-key", + &daewon_lower, + "--fee-amount", + "0", + "--gas-limit", + "0", + "--fee-token", + XAN, + "--ledger-address", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; + client.exp_string("Transaction is invalid")?; + client.exp_string(r#""code": "1"#)?; + + client.assert_success(); + let mut ledger = bg_ledger.foreground(); + ledger.exp_string("some VPs rejected transaction")?; + + // Wait to commit a block + ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + + // 3. Shut it down + ledger.send_control('c')?; + // Wait for the node to stop running to finish writing the state and tx + // queue + ledger.exp_string("Anoma ledger node has shut down.")?; + ledger.exp_eof()?; + drop(ledger); + + // 4. Restart the ledger + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + + ledger.exp_string("Anoma ledger node started")?; + + // There should be previous state now + ledger.exp_string("Last state root hash:")?; + let _bg_ledger = ledger.background(); + + // 5. Submit an invalid transactions (invalid token address) + let daewon_lower = DAEWON.to_lowercase(); + let tx_args = vec![ + "transfer", + "--source", + DAEWON, + "--signing-key", + &daewon_lower, "--target", ALBERT, "--token", diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 6499bf9806..021007d8b1 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -770,6 +770,26 @@ pub mod constants { pub const CHRISTEL_KEY: &str = "Christel-key"; pub const DAEWON: &str = "Daewon"; pub const MATCHMAKER_KEY: &str = "matchmaker-key"; + pub const MASP: &str = "atest1v4ehgw36xaryysfsx5unvve4g5my2vjz89p52sjxxgenzd348yuyyv3hg3pnjs35g5unvde4ca36y5"; + + // Shielded spending and viewing keys and payment addresses + pub const A_SPENDING_KEY: &str = "xsktest1qqqqqqqqqqqqqq9v0sls5r5de7njx8ehu49pqgmqr9ygelg87l5x8y4s9r0pjlvu69au6gn3su5ewneas486hdccyayx32hxvt64p3d0hfuprpgcgv2q9gdx3jvxrn02f0nnp3jtdd6f5vwscfuyum083cvfv4jun75ak5sdgrm2pthzj3sflxc0jx0edrakx3vdcngrfjmru8ywkguru8mxss2uuqxdlglaz6undx5h8w7g70t2es850g48xzdkqay5qs0yw06rtxcvedhsv"; + pub const B_SPENDING_KEY: &str = "xsktest1qqqqqqqqqqqqqqpagte43rsza46v55dlz8cffahv0fnr6eqacvnrkyuf9lmndgal7c2k4r7f7zu2yr5rjwr374unjjeuzrh6mquzy6grfdcnnu5clzaq2llqhr70a8yyx0p62aajqvrqjxrht3myuyypsvm725uyt5vm0fqzrzuuedtf6fala4r4nnazm9y9hq5yu6pq24arjskmpv4mdgfn3spffxxv8ugvym36kmnj45jcvvmm227vqjm5fq8882yhjsq97p7xrwqqd82s0"; + // A payment address derived from A_SPENDING_KEY + pub const AA_PAYMENT_ADDRESS: &str = "patest1a8sfz9c6axdhn925e5qrgzz86msq6yj4uhmxayynucea7gssepk89dgqkx00srfkn4m6kt9jpau"; + // A payment address derived from B_SPENDING_KEY + pub const AB_PAYMENT_ADDRESS: &str = "patest1dxj5kfjvm27rk5wg8ym0mjrhthz6whagdfj9krqfvyszffh4n0mx9f7cauvz6tr43vp22qgsefr"; + // A viewing key derived from B_SPENDING_KEY + pub const AB_VIEWING_KEY: &str = "xfvktest1qqqqqqqqqqqqqqpagte43rsza46v55dlz8cffahv0fnr6eqacvnrkyuf9lmndgal7erg38awgq60r259csg3lxeeyy5355f5nj3ywpeqgd2guqd73uxz46645d0ayt9em88wflka0vsrq29u47x55psw93ly80lvftzdr5ccrzuuedtf6fala4r4nnazm9y9hq5yu6pq24arjskmpv4mdgfn3spffxxv8ugvym36kmnj45jcvvmm227vqjm5fq8882yhjsq97p7xrwqt7n63v"; + // A payment address derived from B_VIEWING_KEY + pub const BB_PAYMENT_ADDRESS: &str = "patest1vqe0vyxh6wmhahwa52gthgd6edgqxfmgyv8e94jtwn55mdvpvylcyqnp59595272qrz3zxn0ysg"; + // A viewing key derived from A_SPENDING_KEY + pub const AA_VIEWING_KEY: &str = "xfvktest1qqqqqqqqqqqqqq9v0sls5r5de7njx8ehu49pqgmqr9ygelg87l5x8y4s9r0pjlvu6x74w9gjpw856zcu826qesdre628y6tjc26uhgj6d9zqur9l5u3p99d9ggc74ald6s8y3sdtka74qmheyqvdrasqpwyv2fsmxlz57lj4grm2pthzj3sflxc0jx0edrakx3vdcngrfjmru8ywkguru8mxss2uuqxdlglaz6undx5h8w7g70t2es850g48xzdkqay5qs0yw06rtxc5292sl"; + pub const C_SPENDING_KEY: &str = "xsktest1qqqqqqqqqqqqqq8cxw3ef0fardt9wq0aqeh29wwljyctw39q4j2t5kmwu6c8x2hfwftnwm6pxtmzyyawm3kruxvk2fdgey90pv3jj9ffvdkxq5vmew5s495qwfyrerrwhxcmx6dl08xh7t36fnn99cdkmsefdv3p3cvw7cq8f4y37q0kh60pdsm6vfkgft2thpu6t9y6ucn68aerump87dgv864yfrxg5529kek99uhzheqajyfrynvsm70v44vsxj2pq5x0wwudryg6zznrz"; + // A viewing key derived from C_SPENDING_KEY + pub const AC_VIEWING_KEY: &str = "xfvktest1qqqqqqqqqqqqqq8cxw3ef0fardt9wq0aqeh29wwljyctw39q4j2t5kmwu6c8x2hfwtlqw4tv6u0me086mffgk9mutyarawfl9mpgjg320fn5jhyes4fmjauwa0yj4gqpg3clnqck5w8xa5svdzm2ngyex4tvpvr7e4t7tcx3f4y37q0kh60pdsm6vfkgft2thpu6t9y6ucn68aerump87dgv864yfrxg5529kek99uhzheqajyfrynvsm70v44vsxj2pq5x0wwudrygd9jdpk"; + // A viewing key derived from C_VIEWING_KEY + pub const AC_PAYMENT_ADDRESS: &str = "ppatest1rjs986uryqf6qf7v0yrkgmn0kds857xkehk6cd6e8xlqpujsqx69gh08n7m77yxw2emsylq9wx2"; // Native VP aliases pub const GOVERNANCE_ADDRESS: &str = "governance"; diff --git a/tests/src/native_vp/mod.rs b/tests/src/native_vp/mod.rs index be450a7086..0f34a02b66 100644 --- a/tests/src/native_vp/mod.rs +++ b/tests/src/native_vp/mod.rs @@ -71,6 +71,7 @@ impl TestNativeVpEnv { storage: &self.tx_env.storage, write_log: &self.tx_env.write_log, tx: &self.tx_env.tx, + tx_index: &self.tx_env.tx_index, vp_wasm_cache: self.tx_env.vp_wasm_cache.clone(), }; let tx_data = self.tx_env.tx.data.as_ref().cloned().unwrap_or_default(); diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 13e7bd3882..882fd2bb98 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -65,7 +65,7 @@ use namada::tendermint_proto::Protobuf; use namada::types::address::{self, Address, InternalAddress}; use namada::types::ibc::data::FungibleTokenPacketData; use namada::types::ibc::IbcEvent; -use namada::types::storage::{BlockHash, BlockHeight, Key}; +use namada::types::storage::{BlockHash, BlockHeight, Key, TxIndex}; use namada::types::time::Rfc3339String; use namada::types::token::{self, Amount}; use namada::vm::{wasm, WasmCacheRwAccess}; @@ -193,6 +193,7 @@ pub fn init_ibc_vp_from_tx<'a>( &tx_env.storage, &tx_env.write_log, tx, + &TxIndex(0), VpGasMeter::new(0), vp_wasm_cache, ); @@ -224,6 +225,7 @@ pub fn init_token_vp_from_tx<'a>( &tx_env.storage, &tx_env.write_log, tx, + &TxIndex(0), VpGasMeter::new(0), vp_wasm_cache, ); diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 3a684e8382..da2c538328 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -9,7 +9,7 @@ use namada::ledger::storage::testing::TestStorage; use namada::ledger::storage::write_log::WriteLog; use namada::proto::Tx; use namada::types::address::Address; -use namada::types::storage::Key; +use namada::types::storage::{Key, TxIndex}; use namada::types::time::DurationSecs; use namada::types::{key, token}; use namada::vm::prefix_iter::PrefixIterators; @@ -38,6 +38,7 @@ pub struct TestTxEnv { pub iterators: PrefixIterators<'static, MockDB>, pub verifiers: BTreeSet
, pub gas_meter: BlockGasMeter, + pub tx_index: TxIndex, pub result_buffer: Option>, pub vp_wasm_cache: VpCache, pub vp_cache_dir: TempDir, @@ -57,6 +58,7 @@ impl Default for TestTxEnv { write_log: WriteLog::default(), iterators: PrefixIterators::default(), gas_meter: BlockGasMeter::default(), + tx_index: TxIndex::default(), verifiers: BTreeSet::default(), result_buffer: None, vp_wasm_cache, @@ -241,7 +243,8 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, - result_buffer, + result_buffer, + tx_index, vp_wasm_cache, vp_cache_dir: _, tx_wasm_cache, @@ -255,6 +258,7 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, + tx_index, result_buffer, vp_wasm_cache, tx_wasm_cache, @@ -274,6 +278,7 @@ mod native_tx_host_env { #[no_mangle] extern "C" fn extern_fn_name( $($arg: $type),* ) -> $ret { with(|TestTxEnv { + tx_index, storage, write_log, iterators, @@ -293,6 +298,7 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, + tx_index, result_buffer, vp_wasm_cache, tx_wasm_cache, @@ -342,6 +348,7 @@ mod native_tx_host_env { native_host_fn!(tx_emit_ibc_event(event_ptr: u64, event_len: u64)); native_host_fn!(tx_get_chain_id(result_ptr: u64)); native_host_fn!(tx_get_block_height() -> u64); + native_host_fn!(tx_get_tx_index() -> u32); native_host_fn!(tx_get_block_time() -> i64); native_host_fn!(tx_get_block_hash(result_ptr: u64)); native_host_fn!(tx_get_block_epoch() -> u64); diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 61b87e1b3b..487d310d80 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -6,7 +6,7 @@ use namada::ledger::storage::testing::TestStorage; use namada::ledger::storage::write_log::WriteLog; use namada::proto::Tx; use namada::types::address::{self, Address}; -use namada::types::storage::{self, Key}; +use namada::types::storage::{self, Key, TxIndex}; use namada::vm::prefix_iter::PrefixIterators; use namada::vm::wasm::{self, VpCache}; use namada::vm::{self, WasmCacheRwAccess}; @@ -32,6 +32,7 @@ pub struct TestVpEnv { pub iterators: PrefixIterators<'static, MockDB>, pub gas_meter: VpGasMeter, pub tx: Tx, + pub tx_index: TxIndex, pub keys_changed: BTreeSet, pub verifiers: BTreeSet
, pub eval_runner: native_vp_host_env::VpEval, @@ -57,6 +58,7 @@ impl Default for TestVpEnv { iterators: PrefixIterators::default(), gas_meter: VpGasMeter::default(), tx: Tx::new(vec![], None), + tx_index: TxIndex::default(), keys_changed: BTreeSet::default(), verifiers: BTreeSet::default(), eval_runner, @@ -234,7 +236,8 @@ mod native_vp_host_env { write_log, iterators, gas_meter, - tx, + tx, + tx_index, keys_changed, verifiers, eval_runner, @@ -250,6 +253,7 @@ mod native_vp_host_env { iterators, gas_meter, tx, + tx_index, verifiers, result_buffer, keys_changed, @@ -276,7 +280,8 @@ mod native_vp_host_env { write_log, iterators, gas_meter, - tx, + tx, + tx_index, keys_changed, verifiers, eval_runner, @@ -292,6 +297,7 @@ mod native_vp_host_env { iterators, gas_meter, tx, + tx_index, verifiers, result_buffer, keys_changed, @@ -320,6 +326,7 @@ mod native_vp_host_env { native_host_fn!(vp_iter_post_next(iter_id: u64) -> i64); native_host_fn!(vp_get_chain_id(result_ptr: u64)); native_host_fn!(vp_get_block_height() -> u64); + native_host_fn!(vp_get_tx_index() -> u32); native_host_fn!(vp_get_block_hash(result_ptr: u64)); native_host_fn!(vp_get_tx_code_hash(result_ptr: u64)); native_host_fn!(vp_get_block_epoch() -> u64); diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index f11d57d1b0..fa4ebac935 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -13,4 +13,7 @@ default = [] namada = {path = "../shared"} namada_macros = {path = "../macros"} borsh = "0.9.0" +#libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } hex = "0.4.3" diff --git a/vm_env/src/governance.rs b/vm_env/src/governance.rs index db4ea7916f..dd5514f16f 100644 --- a/vm_env/src/governance.rs +++ b/vm_env/src/governance.rs @@ -64,6 +64,8 @@ pub mod tx { &governance_address, &m1t(), min_proposal_funds, + &None, + &None, ); } diff --git a/vm_env/src/ibc.rs b/vm_env/src/ibc.rs index febaa78560..428735403d 100644 --- a/vm_env/src/ibc.rs +++ b/vm_env/src/ibc.rs @@ -37,7 +37,7 @@ impl IbcActions for Ibc { token: &Address, amount: Amount, ) { - transfer(src, dest, token, amount) + transfer(src, dest, token, amount, &None, &None) } fn get_height(&self) -> BlockHeight { diff --git a/vm_env/src/imports.rs b/vm_env/src/imports.rs index 2eabe77e54..ab38842ac1 100644 --- a/vm_env/src/imports.rs +++ b/vm_env/src/imports.rs @@ -61,7 +61,7 @@ pub mod tx { use namada::types::ibc::IbcEvent; use namada::types::internal::HostEnvResult; use namada::types::storage::{ - BlockHash, BlockHeight, Epoch, BLOCK_HASH_LENGTH, + BlockHash, BlockHeight, Epoch, TxIndex, BLOCK_HASH_LENGTH, }; use namada::types::time::Rfc3339String; @@ -266,6 +266,11 @@ pub mod tx { Epoch(unsafe { anoma_tx_get_block_epoch() }) } + /// Get index of the current transaction + pub fn get_tx_index() -> TxIndex { + TxIndex(unsafe { anoma_tx_get_tx_index() }) + } + /// Log a string. The message will be printed at the `tracing::Level::Info`. pub fn log_string>(msg: T) { let msg = msg.as_ref(); @@ -350,6 +355,9 @@ pub mod tx { // Get the current block epoch fn anoma_tx_get_block_epoch() -> u64; + // Get the current transaction index + fn anoma_tx_get_tx_index() -> u32; + // Requires a node running with "Info" log level fn anoma_tx_log_string(str_ptr: u64, str_len: u64); } @@ -367,7 +375,7 @@ pub mod vp { use namada::types::internal::HostEnvResult; use namada::types::key::*; use namada::types::storage::{ - BlockHash, BlockHeight, Epoch, BLOCK_HASH_LENGTH, + BlockHash, BlockHeight, Epoch, TxIndex, BLOCK_HASH_LENGTH, }; pub struct PreKeyValIterator(pub u64, pub PhantomData); @@ -505,6 +513,11 @@ pub mod vp { BlockHeight(unsafe { anoma_vp_get_block_height() }) } + /// Get index of the current transaction + pub fn get_tx_index() -> TxIndex { + TxIndex(unsafe { anoma_vp_get_tx_index() }) + } + /// Get a block hash pub fn get_block_hash() -> BlockHash { let result = Vec::with_capacity(BLOCK_HASH_LENGTH); @@ -632,6 +645,9 @@ pub mod vp { // Get the chain ID fn anoma_vp_get_chain_id(result_ptr: u64); + // Get the current transaction index + fn anoma_vp_get_tx_index() -> u32; + // Get the current block height fn anoma_vp_get_block_height() -> u64; diff --git a/vm_env/src/proof_of_stake.rs b/vm_env/src/proof_of_stake.rs index 8e4bba4223..af275ae402 100644 --- a/vm_env/src/proof_of_stake.rs +++ b/vm_env/src/proof_of_stake.rs @@ -256,6 +256,6 @@ impl namada_proof_of_stake::PosActions for PoS { src: &Self::Address, dest: &Self::Address, ) { - crate::token::tx::transfer(src, dest, token, amount) + crate::token::tx::transfer(src, dest, token, amount, &None, &None) } } diff --git a/vm_env/src/token.rs b/vm_env/src/token.rs index 8a7367afb9..2f718ee033 100644 --- a/vm_env/src/token.rs +++ b/vm_env/src/token.rs @@ -1,7 +1,8 @@ use std::collections::BTreeSet; -use namada::types::address::{Address, InternalAddress}; -use namada::types::storage::Key; +use masp_primitives::transaction::Transaction; +use namada::types::address::{masp, Address, InternalAddress}; +use namada::types::storage::{Key, KeySeg}; use namada::types::token; /// Vp imports and functions. @@ -51,7 +52,7 @@ pub mod vp { change += this_change; // make sure that the spender approved the transaction if this_change < 0 { - return verifiers.contains(owner); + return verifiers.contains(owner) || *owner == masp(); } true } @@ -74,6 +75,8 @@ pub mod tx { dest: &Address, token: &Address, amount: Amount, + key: &Option, + shielded: &Option, ) { let src_key = token::balance_key(token, src); let dest_key = token::balance_key(token, dest); @@ -85,29 +88,75 @@ pub mod tx { unreachable!() } }); - src_bal.spend(&amount); let mut dest_bal: Amount = tx::read(&dest_key.to_string()).unwrap_or_default(); - dest_bal.receive(&amount); - match src { - Address::Internal(InternalAddress::IbcMint) => { - tx::write_temp(&src_key.to_string(), src_bal) + // Only make changes to transparent balances if asset is not being + // transferred to self + if src != dest { + src_bal.spend(&amount); + dest_bal.receive(&amount); + match src { + Address::Internal(InternalAddress::IbcMint) => { + tx::write_temp(&src_key.to_string(), src_bal) + } + Address::Internal(InternalAddress::IbcBurn) => { + tx::log_string("invalid transfer from the burn address"); + unreachable!() + } + _ => tx::write(&src_key.to_string(), src_bal), } - Address::Internal(InternalAddress::IbcBurn) => { - tx::log_string("invalid transfer from the burn address"); - unreachable!() + match dest { + Address::Internal(InternalAddress::IbcMint) => { + tx::log_string("invalid transfer to the mint address"); + unreachable!() + } + Address::Internal(InternalAddress::IbcBurn) => { + tx::write_temp(&dest_key.to_string(), dest_bal) + } + _ => tx::write(&dest_key.to_string(), dest_bal), } - _ => tx::write(&src_key.to_string(), src_bal), } - match dest { - Address::Internal(InternalAddress::IbcMint) => { - tx::log_string("invalid transfer to the mint address"); - unreachable!() - } - Address::Internal(InternalAddress::IbcBurn) => { - tx::write_temp(&dest_key.to_string(), dest_bal) + // If this transaction has a shielded component, then handle it + // separately + if let Some(shielded) = shielded { + let masp_addr = masp(); + tx::insert_verifier(&masp_addr); + let head_tx_key = Key::from(masp_addr.to_db_key()) + .push(&HEAD_TX_KEY.to_owned()) + .expect("Cannot obtain a storage key"); + let current_tx_idx: u64 = + tx::read(&head_tx_key.to_string()).unwrap_or(0); + let current_tx_key = Key::from(masp_addr.to_db_key()) + .push(&(TX_KEY_PREFIX.to_owned() + ¤t_tx_idx.to_string())) + .expect("Cannot obtain a storage key"); + // Save the Transfer object and its location within the blockchain + // so that clients do not have to separately look these + // up + let transfer = Transfer { + source: src.clone(), + target: dest.clone(), + token: token.clone(), + amount, + key: key.clone(), + shielded: Some(shielded.clone()), + }; + tx::write( + ¤t_tx_key.to_string(), + ( + tx::get_block_epoch(), + tx::get_block_height(), + tx::get_tx_index(), + transfer, + ), + ); + tx::write(&head_tx_key.to_string(), current_tx_idx + 1); + // If storage key has been supplied, then pin this transaction to it + if let Some(key) = key { + let pin_key = Key::from(masp_addr.to_db_key()) + .push(&(PIN_KEY_PREFIX.to_owned() + key)) + .expect("Cannot obtain a storage key"); + tx::write(&pin_key.to_string(), current_tx_idx); } - _ => tx::write(&dest_key.to_string(), dest_bal), } } } diff --git a/wasm/checksums.json b/wasm/checksums.json index 5d670022b7..c215e0d144 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,20 @@ { - "tx_bond.wasm": "tx_bond.71acaf1ff3a4ac1ce6550c4ce594cf70d684213769f45641be5cdb4b7321184e.wasm", - "tx_from_intent.wasm": "tx_from_intent.e18907b1fb0e1c4e4946228c0ec57838a783a5d88c6b3129d44935386693cee8.wasm", - "tx_ibc.wasm": "tx_ibc.38ee97db63b765f1639ee24dd3e7f264139f146a56f7c06edafde63eaecedade.wasm", - "tx_init_account.wasm": "tx_init_account.6be05f8ca240d9e978dfe6a1566b3860e07fa3aee334003fc5d85a5a1ae99cf7.wasm", - "tx_init_nft.wasm": "tx_init_nft.5f710b641c38cbe7822c2359bf7a764e864e952f4660ccff9b58b97fbaa4b3a2.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.25666fc8fbf251bd4ad478cb968f1d50a8100a0a7aae0e3ee5e50e28304e8e3e.wasm", - "tx_init_validator.wasm": "tx_init_validator.2b1a4c947242ddddf94a491b8c571185f649913606bfa7d37f668ddc86fe1049.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.9af8eea9219db09161b98405c18ea3c82db03b982f69a66ce1d117e64a715c3c.wasm", - "tx_transfer.wasm": "tx_transfer.87e4fba9d2476388e0f2b1d4bc00c396ac7b8b7b38d5079735fe53bc5984b583.wasm", - "tx_unbond.wasm": "tx_unbond.61f2ac46e7680ec35aba367ec908ac63aa43d6765cd07da932aa9ea45d77be46.wasm", - "tx_update_vp.wasm": "tx_update_vp.00d828bdf510d92d971ca59015945c8c00f285a802a655451cc5ee87c5ee99bc.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.92ed628aca7856e2b0fe461c0a0a65c6f7c1f585ac542c10feceef0e4b9ce611.wasm", - "tx_withdraw.wasm": "tx_withdraw.fb54d108a6fe7c6db16e573110c4a9f52158ec993e04e5a06e1425b8dd676c54.wasm", - "vp_nft.wasm": "vp_nft.3421840146f8d2e6c3ce3a0d94bbb0dad6ef296c50e9d00744179d290f32745b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.1b37ab6939280fbb54512918053362fae34e3db74f029ef31c9f08e767539afb.wasm", - "vp_token.wasm": "vp_token.08dcec2d3ecb0942d0a244bbd176f9a42424ec09544120db1b30c0d97ed7dbb1.wasm", - "vp_user.wasm": "vp_user.404f089f0c73fcf906f7d6059d74c6bab3e2e3d3a108394503bbc22588ff95b9.wasm" + "tx_bond.wasm": "tx_bond.b31cbea076223fa15ea276b8d528693812dfa0c48e08d0ce7c339501f003421f.wasm", + "tx_from_intent.wasm": "tx_from_intent.0f14418118b6773c8b8ac051679117dac215c3dd9f8209134f9713ee92509908.wasm", + "tx_ibc.wasm": "tx_ibc.968dcd1aa5c0b4ccc506730ba9cf9886069ebe62eea19595f212c4ecf313d37e.wasm", + "tx_init_account.wasm": "tx_init_account.6b769570e77c05b2e1bcbdefde00c3be7cc84dd7acae6591eb5ce0403ddc74fa.wasm", + "tx_init_nft.wasm": "tx_init_nft.482a0f2495db00caf15834a5a4006ab5f107ae2bc8b4c08aafb6850c47778f6a.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.be19156ef383ff718a5d5702e4cbaee2d929b6eb3b3d0ad0377f5b1ead0808c1.wasm", + "tx_init_validator.wasm": "tx_init_validator.34451b3d4e20df524fe9f12d930408ea6285c6d0b744e4bdb72a2fbb6b97531d.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.bd23761d2f92874c6359f39a815a34ba0cb3a994598dbcc23981816e4c06fbe7.wasm", + "tx_transfer.wasm": "tx_transfer.d89b51f7d46f1227d5877ef53e93e5fea668ff4c93d78b920c55d52ed8720bdd.wasm", + "tx_unbond.wasm": "tx_unbond.6a42553efa501959ccb81f4fe2bedd9552f9695ffb56ae6c845ab24373da58a5.wasm", + "tx_update_vp.wasm": "tx_update_vp.6804def33f5144e2f928c1bd0f6f9668cefcf09b9792b52933319e90212be7b9.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.98c2cce5c17bf1c52ba5a4f482ff1ad6e8dba79a9a966354de9ef1285671461e.wasm", + "tx_withdraw.wasm": "tx_withdraw.ddb7c3ec8b9bd993cd5068eac133692ed3ebab858dfe71ac3d45445e99d2803f.wasm", + "vp_masp.wasm": "vp_masp.49b17bd1edc9e85899a6e0a36f625d4405a7d41a8cbab067814725e11aa93f9f.wasm", + "vp_nft.wasm": "vp_nft.fe55d6ebfa3cf8a8a0c8605ffdceb31c9e75509e53148b0e4c77cd4b2c8176e9.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.f50aebb9bf029b496e2cdca2373e38fd1396ac6dd61bca43868c6d7d7b3e65d8.wasm", + "vp_token.wasm": "vp_token.c3030826fa2b96fabdce31a40f458a03bd99d7a98334fd3fd5859cc7f05c4793.wasm", + "vp_user.wasm": "vp_user.9e6ba1e5488e9a5212de43b73814d701eb7f78a9a39c3c851aa022ac59ab7f7b.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index bf9d222920..187d710d33 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -17,6 +17,27 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "opaque-debug", +] + [[package]] name = "ahash" version = "0.7.6" @@ -146,6 +167,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "arrayvec" version = "0.7.2" @@ -211,12 +238,63 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" + [[package]] name = "bech32" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +[[package]] +name = "bellman" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" +dependencies = [ + "bitvec", + "blake2s_simd 0.5.11", + "byteorder", + "crossbeam-channel", + "ff", + "group", + "lazy_static", + "log", + "num_cpus", + "pairing", + "rand_core 0.6.3", + "rayon", + "subtle", +] + +[[package]] +name = "bigint" +version = "4.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0e8c8a600052b52482eff2cf4d810e462fdff1f656ac1ecb6232132a1ed7def" +dependencies = [ + "byteorder", + "crunchy", +] + +[[package]] +name = "bip0039" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0830ae4cc96b0617cc912970c2b17e89456fecbf55e8eed53a956f37ab50c41" +dependencies = [ + "hmac", + "pbkdf2", + "rand", + "sha2 0.9.9", + "unicode-normalization", + "zeroize", +] + [[package]] name = "bit-set" version = "0.5.2" @@ -238,6 +316,62 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[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", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "constant_time_eq", +] + [[package]] name = "blake3" version = "1.3.0" @@ -245,7 +379,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "882e99e4a0cb2ae6cb6e442102e8e6b7131718d94110e64c3e6a34ea9b106f37" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.7.2", "cc", "cfg-if 1.0.0", "constant_time_eq", @@ -271,12 +405,35 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + [[package]] name = "block-padding" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "bls12_381" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" +dependencies = [ + "ff", + "group", + "pairing", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "borsh" version = "0.9.4" @@ -375,6 +532,31 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.19" @@ -388,6 +570,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "clru" version = "0.5.0" @@ -531,6 +722,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crunchy" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" + [[package]] name = "crypto-common" version = "0.1.3" @@ -541,6 +738,31 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930b6a026ce9d358a17f9c9046c55d90b14bb847f36b6ebb6b19365d4feffb8" +dependencies = [ + "crypto_api", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -644,6 +866,26 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dynasm" version = "1.2.1" @@ -753,6 +995,15 @@ dependencies = [ "syn", ] +[[package]] +name = "equihash" +version = "0.1.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "blake2b_simd 1.0.0", + "byteorder", +] + [[package]] name = "eyre" version = "0.6.7" @@ -791,6 +1042,17 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "bitvec", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -823,6 +1085,26 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fpe" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" +dependencies = [ + "block-modes", + "cipher", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "funty" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" + [[package]] name = "futures" version = "0.3.21" @@ -922,6 +1204,18 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "byteorder", + "ff", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "gumdrop" version = "0.8.0" @@ -961,6 +1255,20 @@ dependencies = [ "tracing", ] +[[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", + "rayon", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -994,6 +1302,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + [[package]] name = "http" version = "0.2.6" @@ -1137,6 +1455,15 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "incrementalmerkletree" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186fd3ab92aeac865d4b80b410de9a7b341d31ba8281373caed0b6d17b2b5e96" +dependencies = [ + "serde", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1187,6 +1514,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jubjub" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" +dependencies = [ + "bitvec", + "bls12_381", + "ff", + "group", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "keccak" version = "0.1.0" @@ -1221,6 +1562,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da83a57f3f5ba3680950aa3cbc806fc297bc0b289d42e8942ed528ace71b8145" + [[package]] name = "log" version = "0.4.14" @@ -1260,6 +1607,58 @@ dependencies = [ "libc", ] +[[package]] +name = "masp_primitives" +version = "0.5.0" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +dependencies = [ + "aes", + "bip0039", + "bitvec", + "blake2b_simd 1.0.0", + "blake2s_simd 1.0.0", + "bls12_381", + "borsh", + "byteorder", + "chacha20poly1305", + "crypto_api_chachapoly", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "rand", + "rand_core 0.6.3", + "serde", + "sha2 0.9.9", + "subtle", + "zcash_encoding", + "zcash_primitives", +] + +[[package]] +name = "masp_proofs" +version = "0.5.0" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +dependencies = [ + "bellman", + "blake2b_simd 1.0.0", + "bls12_381", + "byteorder", + "directories", + "ff", + "group", + "itertools", + "jubjub", + "lazy_static", + "masp_primitives", + "rand_core 0.6.3", + "zcash_primitives", + "zcash_proofs", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1305,6 +1704,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +[[package]] +name = "memuse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f69d25cd7528769ad3d897e99eb942774bff8b23165012af490351a44c5b583b" +dependencies = [ + "nonempty", +] + [[package]] name = "miniz_oxide" version = "0.4.4" @@ -1356,6 +1764,7 @@ dependencies = [ "ark-bls12-381", "ark-serialize", "bech32", + "bit-vec", "borsh", "chrono", "clru", @@ -1368,6 +1777,8 @@ dependencies = [ "ics23", "itertools", "loupe", + "masp_primitives", + "masp_proofs", "namada_proof_of_stake", "parity-wasm", "proptest", @@ -1376,6 +1787,7 @@ dependencies = [ "pwasm-utils", "rand", "rand_core 0.6.3", + "rayon", "rust_decimal", "serde", "serde_json", @@ -1445,10 +1857,18 @@ version = "0.7.0" dependencies = [ "borsh", "hex", + "masp_primitives", + "masp_proofs", "namada", "namada_macros", ] +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + [[package]] name = "ntapi" version = "0.3.7" @@ -1551,18 +1971,90 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[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", + "blake2b_simd 1.0.0", + "ff", + "fpe", + "group", + "halo2", + "incrementalmerkletree", + "lazy_static", + "memuse", + "nonempty", + "pasta_curves", + "rand", + "reddsa", + "serde", + "subtle", + "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pairing" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" +dependencies = [ + "group", +] + [[package]] name = "parity-wasm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +[[package]] +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.3", + "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", + "static_assertions", + "subtle", +] + [[package]] name = "paste" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +[[package]] +name = "pbkdf2" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" +dependencies = [ + "crypto-mac", + "password-hash", +] + [[package]] name = "peg" version = "0.7.0" @@ -1647,6 +2139,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -1819,6 +2322,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" + [[package]] name = "rand" version = "0.8.5" @@ -1889,6 +2398,24 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "reddsa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e2c94bca3445cae0d55dff7370e29c24885d2403a1665ce19c106c79455e6" +dependencies = [ + "blake2b_simd 0.5.11", + "byteorder", + "digest 0.9.0", + "group", + "jubjub", + "pasta_curves", + "rand_core 0.6.3", + "serde", + "thiserror", + "zeroize", +] + [[package]] name = "redox_syscall" version = "0.2.11" @@ -1898,6 +2425,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + [[package]] name = "regalloc" version = "0.0.31" @@ -2007,7 +2545,7 @@ version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d37baa70cf8662d2ba1c1868c5983dda16ef32b105cce41fb5c47e72936a90b3" dependencies = [ - "arrayvec", + "arrayvec 0.7.2", "num-traits", "serde", ] @@ -2296,6 +2834,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2346,6 +2890,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.2" @@ -2840,6 +3390,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "url" version = "2.2.2" @@ -3270,6 +3830,97 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129e027ad65ce1453680623c3fb5163cbf7107bfe1aa32257e7d0e63f9ced188" +dependencies = [ + "tap", +] + +[[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.3", + "subtle", +] + +[[package]] +name = "zcash_note_encryption" +version = "0.1.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "chacha20", + "chacha20poly1305", + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "zcash_primitives" +version = "0.5.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "aes", + "bip0039", + "bitvec", + "blake2b_simd 1.0.0", + "blake2s_simd 1.0.0", + "bls12_381", + "byteorder", + "chacha20poly1305", + "equihash", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "memuse", + "nonempty", + "orchard", + "rand", + "rand_core 0.6.3", + "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.0", + "bls12_381", + "byteorder", + "directories", + "ff", + "group", + "jubjub", + "lazy_static", + "rand_core 0.6.3", + "zcash_primitives", +] + [[package]] name = "zeroize" version = "1.5.3" diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 98f6c7f71c..08569db497 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -17,6 +17,27 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "opaque-debug", +] + [[package]] name = "ahash" version = "0.7.6" @@ -146,6 +167,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "arrayvec" version = "0.7.2" @@ -211,12 +238,63 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" + [[package]] name = "bech32" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +[[package]] +name = "bellman" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" +dependencies = [ + "bitvec", + "blake2s_simd 0.5.11", + "byteorder", + "crossbeam-channel", + "ff", + "group", + "lazy_static", + "log", + "num_cpus", + "pairing", + "rand_core 0.6.3", + "rayon", + "subtle", +] + +[[package]] +name = "bigint" +version = "4.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0e8c8a600052b52482eff2cf4d810e462fdff1f656ac1ecb6232132a1ed7def" +dependencies = [ + "byteorder", + "crunchy", +] + +[[package]] +name = "bip0039" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0830ae4cc96b0617cc912970c2b17e89456fecbf55e8eed53a956f37ab50c41" +dependencies = [ + "hmac", + "pbkdf2", + "rand", + "sha2 0.9.9", + "unicode-normalization", + "zeroize", +] + [[package]] name = "bit-set" version = "0.5.2" @@ -238,6 +316,62 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[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", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "constant_time_eq", +] + [[package]] name = "blake3" version = "1.3.0" @@ -245,7 +379,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "882e99e4a0cb2ae6cb6e442102e8e6b7131718d94110e64c3e6a34ea9b106f37" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.7.2", "cc", "cfg-if 1.0.0", "constant_time_eq", @@ -271,12 +405,35 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + [[package]] name = "block-padding" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "bls12_381" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" +dependencies = [ + "ff", + "group", + "pairing", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "borsh" version = "0.9.4" @@ -375,6 +532,31 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.19" @@ -388,6 +570,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "clru" version = "0.5.0" @@ -531,6 +722,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crunchy" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" + [[package]] name = "crypto-common" version = "0.1.3" @@ -541,6 +738,31 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930b6a026ce9d358a17f9c9046c55d90b14bb847f36b6ebb6b19365d4feffb8" +dependencies = [ + "crypto_api", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -644,6 +866,26 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dynasm" version = "1.2.1" @@ -753,6 +995,15 @@ dependencies = [ "syn", ] +[[package]] +name = "equihash" +version = "0.1.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "blake2b_simd 1.0.0", + "byteorder", +] + [[package]] name = "eyre" version = "0.6.7" @@ -791,6 +1042,17 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "bitvec", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -823,6 +1085,26 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fpe" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" +dependencies = [ + "block-modes", + "cipher", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "funty" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" + [[package]] name = "futures" version = "0.3.21" @@ -922,6 +1204,18 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "byteorder", + "ff", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "gumdrop" version = "0.8.0" @@ -961,6 +1255,20 @@ dependencies = [ "tracing", ] +[[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", + "rayon", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -994,6 +1302,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + [[package]] name = "http" version = "0.2.6" @@ -1137,6 +1455,15 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "incrementalmerkletree" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186fd3ab92aeac865d4b80b410de9a7b341d31ba8281373caed0b6d17b2b5e96" +dependencies = [ + "serde", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1187,6 +1514,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jubjub" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" +dependencies = [ + "bitvec", + "bls12_381", + "ff", + "group", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "keccak" version = "0.1.0" @@ -1221,6 +1562,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da83a57f3f5ba3680950aa3cbc806fc297bc0b289d42e8942ed528ace71b8145" + [[package]] name = "log" version = "0.4.14" @@ -1260,6 +1607,58 @@ dependencies = [ "libc", ] +[[package]] +name = "masp_primitives" +version = "0.5.0" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +dependencies = [ + "aes", + "bip0039", + "bitvec", + "blake2b_simd 1.0.0", + "blake2s_simd 1.0.0", + "bls12_381", + "borsh", + "byteorder", + "chacha20poly1305", + "crypto_api_chachapoly", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "rand", + "rand_core 0.6.3", + "serde", + "sha2 0.9.9", + "subtle", + "zcash_encoding", + "zcash_primitives", +] + +[[package]] +name = "masp_proofs" +version = "0.5.0" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +dependencies = [ + "bellman", + "blake2b_simd 1.0.0", + "bls12_381", + "byteorder", + "directories", + "ff", + "group", + "itertools", + "jubjub", + "lazy_static", + "masp_primitives", + "rand_core 0.6.3", + "zcash_primitives", + "zcash_proofs", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1305,6 +1704,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +[[package]] +name = "memuse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f69d25cd7528769ad3d897e99eb942774bff8b23165012af490351a44c5b583b" +dependencies = [ + "nonempty", +] + [[package]] name = "miniz_oxide" version = "0.4.4" @@ -1356,6 +1764,7 @@ dependencies = [ "ark-bls12-381", "ark-serialize", "bech32", + "bit-vec", "borsh", "chrono", "clru", @@ -1368,6 +1777,8 @@ dependencies = [ "ics23", "itertools", "loupe", + "masp_primitives", + "masp_proofs", "namada_proof_of_stake", "parity-wasm", "proptest", @@ -1376,6 +1787,7 @@ dependencies = [ "pwasm-utils", "rand", "rand_core 0.6.3", + "rayon", "rust_decimal", "serde", "serde_json", @@ -1437,6 +1849,8 @@ version = "0.7.0" dependencies = [ "borsh", "hex", + "masp_primitives", + "masp_proofs", "namada", "namada_macros", ] @@ -1449,6 +1863,12 @@ dependencies = [ "sha2 0.10.2", ] +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + [[package]] name = "ntapi" version = "0.3.7" @@ -1551,18 +1971,90 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[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", + "blake2b_simd 1.0.0", + "ff", + "fpe", + "group", + "halo2", + "incrementalmerkletree", + "lazy_static", + "memuse", + "nonempty", + "pasta_curves", + "rand", + "reddsa", + "serde", + "subtle", + "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pairing" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" +dependencies = [ + "group", +] + [[package]] name = "parity-wasm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +[[package]] +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.3", + "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", + "static_assertions", + "subtle", +] + [[package]] name = "paste" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +[[package]] +name = "pbkdf2" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" +dependencies = [ + "crypto-mac", + "password-hash", +] + [[package]] name = "peg" version = "0.7.0" @@ -1647,6 +2139,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -1819,6 +2322,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" + [[package]] name = "rand" version = "0.8.5" @@ -1889,6 +2398,24 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "reddsa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e2c94bca3445cae0d55dff7370e29c24885d2403a1665ce19c106c79455e6" +dependencies = [ + "blake2b_simd 0.5.11", + "byteorder", + "digest 0.9.0", + "group", + "jubjub", + "pasta_curves", + "rand_core 0.6.3", + "serde", + "thiserror", + "zeroize", +] + [[package]] name = "redox_syscall" version = "0.2.11" @@ -1898,6 +2425,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + [[package]] name = "regalloc" version = "0.0.31" @@ -2007,7 +2545,7 @@ version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d37baa70cf8662d2ba1c1868c5983dda16ef32b105cce41fb5c47e72936a90b3" dependencies = [ - "arrayvec", + "arrayvec 0.7.2", "num-traits", "serde", ] @@ -2296,6 +2834,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2346,6 +2890,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.2" @@ -2829,6 +3379,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "url" version = "2.2.2" @@ -3270,6 +3830,97 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129e027ad65ce1453680623c3fb5163cbf7107bfe1aa32257e7d0e63f9ced188" +dependencies = [ + "tap", +] + +[[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.3", + "subtle", +] + +[[package]] +name = "zcash_note_encryption" +version = "0.1.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "chacha20", + "chacha20poly1305", + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "zcash_primitives" +version = "0.5.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "aes", + "bip0039", + "bitvec", + "blake2b_simd 1.0.0", + "blake2s_simd 1.0.0", + "bls12_381", + "byteorder", + "chacha20poly1305", + "equihash", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "memuse", + "nonempty", + "orchard", + "rand", + "rand_core 0.6.3", + "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.0", + "bls12_381", + "byteorder", + "directories", + "ff", + "group", + "jubjub", + "lazy_static", + "rand_core 0.6.3", + "zcash_primitives", +] + [[package]] name = "zeroize" version = "1.5.3" diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index b7185cc110..f4723e1d76 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -17,6 +17,27 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "opaque-debug", +] + [[package]] name = "ahash" version = "0.7.6" @@ -146,6 +167,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "arrayvec" version = "0.7.2" @@ -211,12 +238,63 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" + [[package]] name = "bech32" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +[[package]] +name = "bellman" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" +dependencies = [ + "bitvec", + "blake2s_simd 0.5.11", + "byteorder", + "crossbeam-channel", + "ff", + "group", + "lazy_static", + "log", + "num_cpus", + "pairing", + "rand_core 0.6.3", + "rayon", + "subtle", +] + +[[package]] +name = "bigint" +version = "4.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0e8c8a600052b52482eff2cf4d810e462fdff1f656ac1ecb6232132a1ed7def" +dependencies = [ + "byteorder", + "crunchy", +] + +[[package]] +name = "bip0039" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0830ae4cc96b0617cc912970c2b17e89456fecbf55e8eed53a956f37ab50c41" +dependencies = [ + "hmac", + "pbkdf2", + "rand", + "sha2 0.9.9", + "unicode-normalization", + "zeroize", +] + [[package]] name = "bit-set" version = "0.5.2" @@ -238,6 +316,62 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[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", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "constant_time_eq", +] + [[package]] name = "blake3" version = "1.3.0" @@ -245,7 +379,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "882e99e4a0cb2ae6cb6e442102e8e6b7131718d94110e64c3e6a34ea9b106f37" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.7.2", "cc", "cfg-if 1.0.0", "constant_time_eq", @@ -271,12 +405,35 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + [[package]] name = "block-padding" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "bls12_381" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" +dependencies = [ + "ff", + "group", + "pairing", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "borsh" version = "0.9.4" @@ -375,6 +532,31 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.19" @@ -388,6 +570,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "clru" version = "0.5.0" @@ -531,6 +722,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crunchy" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" + [[package]] name = "crypto-common" version = "0.1.3" @@ -541,6 +738,31 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930b6a026ce9d358a17f9c9046c55d90b14bb847f36b6ebb6b19365d4feffb8" +dependencies = [ + "crypto_api", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -644,6 +866,26 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dynasm" version = "1.2.1" @@ -753,6 +995,15 @@ dependencies = [ "syn", ] +[[package]] +name = "equihash" +version = "0.1.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "blake2b_simd 1.0.0", + "byteorder", +] + [[package]] name = "eyre" version = "0.6.7" @@ -791,6 +1042,17 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "bitvec", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -823,6 +1085,26 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fpe" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" +dependencies = [ + "block-modes", + "cipher", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "funty" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" + [[package]] name = "futures" version = "0.3.21" @@ -922,6 +1204,18 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "byteorder", + "ff", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "gumdrop" version = "0.8.0" @@ -961,6 +1255,20 @@ dependencies = [ "tracing", ] +[[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", + "rayon", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -994,6 +1302,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + [[package]] name = "http" version = "0.2.6" @@ -1137,6 +1455,15 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "incrementalmerkletree" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186fd3ab92aeac865d4b80b410de9a7b341d31ba8281373caed0b6d17b2b5e96" +dependencies = [ + "serde", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1187,6 +1514,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jubjub" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" +dependencies = [ + "bitvec", + "bls12_381", + "ff", + "group", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "keccak" version = "0.1.0" @@ -1221,6 +1562,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da83a57f3f5ba3680950aa3cbc806fc297bc0b289d42e8942ed528ace71b8145" + [[package]] name = "log" version = "0.4.14" @@ -1260,6 +1607,58 @@ dependencies = [ "libc", ] +[[package]] +name = "masp_primitives" +version = "0.5.0" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +dependencies = [ + "aes", + "bip0039", + "bitvec", + "blake2b_simd 1.0.0", + "blake2s_simd 1.0.0", + "bls12_381", + "borsh", + "byteorder", + "chacha20poly1305", + "crypto_api_chachapoly", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "rand", + "rand_core 0.6.3", + "serde", + "sha2 0.9.9", + "subtle", + "zcash_encoding", + "zcash_primitives", +] + +[[package]] +name = "masp_proofs" +version = "0.5.0" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +dependencies = [ + "bellman", + "blake2b_simd 1.0.0", + "bls12_381", + "byteorder", + "directories", + "ff", + "group", + "itertools", + "jubjub", + "lazy_static", + "masp_primitives", + "rand_core 0.6.3", + "zcash_primitives", + "zcash_proofs", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1305,6 +1704,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +[[package]] +name = "memuse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f69d25cd7528769ad3d897e99eb942774bff8b23165012af490351a44c5b583b" +dependencies = [ + "nonempty", +] + [[package]] name = "miniz_oxide" version = "0.4.4" @@ -1356,6 +1764,7 @@ dependencies = [ "ark-bls12-381", "ark-serialize", "bech32", + "bit-vec", "borsh", "chrono", "clru", @@ -1368,6 +1777,8 @@ dependencies = [ "ics23", "itertools", "loupe", + "masp_primitives", + "masp_proofs", "namada_proof_of_stake", "parity-wasm", "proptest", @@ -1376,6 +1787,7 @@ dependencies = [ "pwasm-utils", "rand", "rand_core 0.6.3", + "rayon", "rust_decimal", "serde", "serde_json", @@ -1445,6 +1857,8 @@ version = "0.7.0" dependencies = [ "borsh", "hex", + "masp_primitives", + "masp_proofs", "namada", "namada_macros", ] @@ -1463,6 +1877,8 @@ version = "0.7.0" dependencies = [ "borsh", "getrandom", + "masp_primitives", + "masp_proofs", "namada", "namada_tests", "namada_tx_prelude", @@ -1475,6 +1891,12 @@ dependencies = [ "wee_alloc", ] +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + [[package]] name = "ntapi" version = "0.3.7" @@ -1577,18 +1999,90 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[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", + "blake2b_simd 1.0.0", + "ff", + "fpe", + "group", + "halo2", + "incrementalmerkletree", + "lazy_static", + "memuse", + "nonempty", + "pasta_curves", + "rand", + "reddsa", + "serde", + "subtle", + "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pairing" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" +dependencies = [ + "group", +] + [[package]] name = "parity-wasm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +[[package]] +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.3", + "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", + "static_assertions", + "subtle", +] + [[package]] name = "paste" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +[[package]] +name = "pbkdf2" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" +dependencies = [ + "crypto-mac", + "password-hash", +] + [[package]] name = "peg" version = "0.7.0" @@ -1673,6 +2167,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -1845,6 +2350,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" + [[package]] name = "rand" version = "0.8.5" @@ -1915,6 +2426,24 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "reddsa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e2c94bca3445cae0d55dff7370e29c24885d2403a1665ce19c106c79455e6" +dependencies = [ + "blake2b_simd 0.5.11", + "byteorder", + "digest 0.9.0", + "group", + "jubjub", + "pasta_curves", + "rand_core 0.6.3", + "serde", + "thiserror", + "zeroize", +] + [[package]] name = "redox_syscall" version = "0.2.11" @@ -1924,6 +2453,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + [[package]] name = "regalloc" version = "0.0.31" @@ -2033,7 +2573,7 @@ version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d37baa70cf8662d2ba1c1868c5983dda16ef32b105cce41fb5c47e72936a90b3" dependencies = [ - "arrayvec", + "arrayvec 0.7.2", "num-traits", "serde", ] @@ -2322,6 +2862,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2372,6 +2918,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.2" @@ -2855,6 +3407,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "url" version = "2.2.2" @@ -3285,6 +3847,97 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129e027ad65ce1453680623c3fb5163cbf7107bfe1aa32257e7d0e63f9ced188" +dependencies = [ + "tap", +] + +[[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.3", + "subtle", +] + +[[package]] +name = "zcash_note_encryption" +version = "0.1.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "chacha20", + "chacha20poly1305", + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "zcash_primitives" +version = "0.5.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "aes", + "bip0039", + "bitvec", + "blake2b_simd 1.0.0", + "blake2s_simd 1.0.0", + "bls12_381", + "byteorder", + "chacha20poly1305", + "equihash", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "memuse", + "nonempty", + "orchard", + "rand", + "rand_core 0.6.3", + "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.0", + "bls12_381", + "byteorder", + "directories", + "ff", + "group", + "jubjub", + "lazy_static", + "rand_core 0.6.3", + "zcash_primitives", +] + [[package]] name = "zeroize" version = "1.5.3" diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index ee2234928a..7aacd78377 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -25,6 +25,7 @@ tx_unbond = ["namada_tx_prelude"] tx_update_vp = ["namada_tx_prelude"] tx_vote_proposal = ["namada_tx_prelude"] tx_withdraw = ["namada_tx_prelude"] +vp_masp = ["namada_vp_prelude", "masp_proofs", "masp_primitives"] vp_nft = ["namada_vp_prelude"] vp_testnet_faucet = ["namada_vp_prelude", "once_cell"] vp_token = ["namada_vp_prelude"] @@ -38,6 +39,8 @@ once_cell = {version = "1.8.0", optional = true} rust_decimal = {version = "1.14.3", optional = true} wee_alloc = "0.4.5" getrandom = { version = "0.2", features = ["custom"] } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } [dev-dependencies] namada = {path = "../../shared"} diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index 39562a4a1e..4daa19d694 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -18,6 +18,7 @@ wasms += tx_transfer wasms += tx_unbond wasms += tx_update_vp wasms += tx_withdraw +wasms += vp_masp wasms += vp_nft wasms += vp_testnet_faucet wasms += vp_token diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index 3a674ad97b..c2eda5f667 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -24,6 +24,9 @@ pub mod tx_update_vp; pub mod tx_vote_proposal; #[cfg(feature = "tx_withdraw")] pub mod tx_withdraw; + +#[cfg(feature = "vp_masp")] +pub mod vp_masp; #[cfg(feature = "vp_nft")] pub mod vp_nft; #[cfg(feature = "vp_testnet_faucet")] diff --git a/wasm/wasm_source/src/tx_from_intent.rs b/wasm/wasm_source/src/tx_from_intent.rs index e39963fae7..8c56519bf4 100644 --- a/wasm/wasm_source/src/tx_from_intent.rs +++ b/wasm/wasm_source/src/tx_from_intent.rs @@ -21,9 +21,11 @@ fn apply_tx(tx_data: Vec) { target, token, amount, + key, + shielded, } in tx_data.matches.transfers { - token::transfer(&source, &target, &token, amount); + token::transfer(&source, &target, &token, amount, &key, &shielded); } tx_data diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index f0ab0ad2d0..79177c8d6f 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -15,6 +15,8 @@ fn apply_tx(tx_data: Vec) { target, token, amount, + key, + shielded, } = transfer; - token::transfer(&source, &target, &token, amount) + token::transfer(&source, &target, &token, amount, &key, &shielded) } diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs new file mode 100644 index 0000000000..1fe368a86a --- /dev/null +++ b/wasm/wasm_source/src/vp_masp.rs @@ -0,0 +1,79 @@ +use std::cmp::Ordering; + +use masp_primitives::asset_type::AssetType; +use masp_primitives::transaction::components::Amount; +/// Multi-asset shielded pool VP. +use namada_vp_prelude::address::masp; +use namada_vp_prelude::*; + +/// Convert Anoma amount and token type to MASP equivalents +fn convert_amount(token: &Address, val: token::Amount) -> (AssetType, Amount) { + let epoch = get_block_epoch(); + // Timestamp the chosen token with the current epoch + let token_bytes = (token, epoch.0) + .try_to_vec() + .expect("token should serialize"); + // Generate the unique asset identifier from the unique token address + let asset_type = AssetType::new(token_bytes.as_ref()) + .expect("unable to create asset type"); + // Combine the value and unit into one amount + let amount = Amount::from_nonnegative(asset_type, u64::from(val)) + .expect("invalid value or asset type for amount"); + (asset_type, amount) +} + +#[validity_predicate] +fn validate_tx( + tx_data: Vec, + addr: Address, + keys_changed: BTreeSet, + verifiers: BTreeSet
, +) -> bool { + debug_log!( + "vp_masp called with {} bytes data, address {}, keys_changed {:?}, \ + verifiers {:?}", + tx_data.len(), + addr, + keys_changed, + verifiers, + ); + + let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); + let transfer = + token::Transfer::try_from_slice(&signed.data.unwrap()[..]).unwrap(); + + if let Some(shielded_tx) = transfer.shielded { + let mut transparent_tx_pool = Amount::zero(); + // The Sapling value balance adds to the transparent tx pool + transparent_tx_pool += shielded_tx.value_balance.clone(); + // Note that the asset type is timestamped so shields/unshields + // where the shielded value has an incorrect timestamp + // are automatically rejected + let (_transp_asset, transp_amt) = + convert_amount(&transfer.token, transfer.amount); + if transfer.source != masp() { + // Non-masp sources add to transparent tx pool + transparent_tx_pool += transp_amt.clone(); + } + if transfer.target != masp() { + // Non-masp destinations subtract from transparent tx pool + transparent_tx_pool -= transp_amt; + } + + match transparent_tx_pool.partial_cmp(&Amount::zero()) { + None | Some(Ordering::Less) => { + debug_log!( + "Transparent transaction value pool must be nonnegative. \ + Violation may be caused by transaction being constructed \ + in previous epoch. Maybe try again." + ); + // Section 3.4: The remaining value in the transparent + // transaction value pool MUST be nonnegative. + return false; + } + _ => {} + } + } + + true +} diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 553288926e..e273a8282d 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -136,7 +136,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(&source, address, &token, amount); + tx_host_env::token::transfer( + &source, address, &token, amount, &None, &None, + ); }); let vp_env = vp_host_env::take(); @@ -251,7 +253,7 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer(address, &target, &token, amount, &None, &None); }); let vp_env = vp_host_env::take(); @@ -284,7 +286,7 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer(address, &target, &token, amount, &None, &None); }); let vp_env = vp_host_env::take(); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index a222a344ef..54d2d715c9 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -12,6 +12,7 @@ //! //! Any other storage key changes are allowed only with a valid signature. +use namada_vp_prelude::address::masp; use namada_vp_prelude::intent::{ Exchange, FungibleTokenIntent, IntentTransfers, }; @@ -26,6 +27,7 @@ enum KeyType<'a> { InvalidIntentSet(&'a Address), Nft(&'a Address), Vp(&'a Address), + Masp, GovernanceVote(&'a Address), Unknown, } @@ -49,6 +51,8 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { } } else if let Some(address) = key.is_validity_predicate() { Self::Vp(address) + } else if token::is_masp_key(key) { + Self::Masp } else { Self::Unknown } @@ -103,7 +107,10 @@ fn validate_tx( read_post(&key).unwrap_or_default(); let change = post.change() - pre.change(); // debit has to signed, credit doesn't - let valid = change >= 0 || *valid_sig || *valid_intent; + let valid = change >= 0 + || addr == masp() + || *valid_sig + || *valid_intent; debug_log!( "token key: {}, change: {}, valid_sig: {}, \ valid_intent: {}, valid modification: {}", @@ -198,6 +205,7 @@ fn validate_tx( return is_vp_whitelisted(&vp); } } + KeyType::Masp => true, KeyType::Unknown => { if key.segments.get(0) == Some(&addr.to_db_key()) { // Unknown changes to this address space require a valid @@ -412,7 +420,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(&source, address, &token, amount); + tx_host_env::token::transfer( + &source, address, &token, amount, &None, &None, + ); }); let vp_env = vp_host_env::take(); @@ -445,7 +455,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer( + address, &target, &token, amount, &None, &None, + ); }); let vp_env = vp_host_env::take(); @@ -482,7 +494,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer( + address, &target, &token, amount, &None, &None, + ); }); let mut vp_env = vp_host_env::take(); @@ -520,7 +534,9 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { tx_host_env::insert_verifier(address); // Apply transfer in a transaction - tx_host_env::token::transfer(&source, &target, &token, amount); + tx_host_env::token::transfer( + &source, &target, &token, amount, &None, &None, + ); }); let vp_env = vp_host_env::take(); diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 88c8ef0ada..53998f4428 100755 Binary files a/wasm_for_tests/tx_memory_limit.wasm and b/wasm_for_tests/tx_memory_limit.wasm differ diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index c9a102773e..1df89b2653 100755 Binary files a/wasm_for_tests/tx_mint_tokens.wasm and b/wasm_for_tests/tx_mint_tokens.wasm differ diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 3c753377ed..d3ee74e970 100755 Binary files a/wasm_for_tests/tx_proposal_code.wasm and b/wasm_for_tests/tx_proposal_code.wasm differ diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index b4266e6940..87375fe086 100755 Binary files a/wasm_for_tests/tx_read_storage_key.wasm and b/wasm_for_tests/tx_read_storage_key.wasm differ diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index 89b669f6af..e44271b2a6 100755 Binary files a/wasm_for_tests/tx_write_storage_key.wasm and b/wasm_for_tests/tx_write_storage_key.wasm differ diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 57b061685c..5f4e5e98c9 100755 Binary files a/wasm_for_tests/vp_always_false.wasm and b/wasm_for_tests/vp_always_false.wasm differ diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 8990ed651f..4ce17928ae 100755 Binary files a/wasm_for_tests/vp_always_true.wasm and b/wasm_for_tests/vp_always_true.wasm differ diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 15b9ad7d67..d5c32b829e 100755 Binary files a/wasm_for_tests/vp_eval.wasm and b/wasm_for_tests/vp_eval.wasm differ diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index 9d445b88fd..2fef1b5f5a 100755 Binary files a/wasm_for_tests/vp_memory_limit.wasm and b/wasm_for_tests/vp_memory_limit.wasm differ diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index fa5e6bcb3a..fd0160ca2d 100755 Binary files a/wasm_for_tests/vp_read_storage_key.wasm and b/wasm_for_tests/vp_read_storage_key.wasm differ diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 39f70c1787..2405154323 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -17,6 +17,27 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "opaque-debug", +] + [[package]] name = "ahash" version = "0.7.6" @@ -146,6 +167,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "arrayvec" version = "0.7.2" @@ -211,12 +238,63 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" + [[package]] name = "bech32" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +[[package]] +name = "bellman" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" +dependencies = [ + "bitvec", + "blake2s_simd 0.5.11", + "byteorder", + "crossbeam-channel", + "ff", + "group", + "lazy_static", + "log", + "num_cpus", + "pairing", + "rand_core 0.6.3", + "rayon", + "subtle", +] + +[[package]] +name = "bigint" +version = "4.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0e8c8a600052b52482eff2cf4d810e462fdff1f656ac1ecb6232132a1ed7def" +dependencies = [ + "byteorder", + "crunchy", +] + +[[package]] +name = "bip0039" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0830ae4cc96b0617cc912970c2b17e89456fecbf55e8eed53a956f37ab50c41" +dependencies = [ + "hmac", + "pbkdf2", + "rand", + "sha2 0.9.9", + "unicode-normalization", + "zeroize", +] + [[package]] name = "bit-set" version = "0.5.2" @@ -238,6 +316,62 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[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", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" +dependencies = [ + "arrayref", + "arrayvec 0.7.2", + "constant_time_eq", +] + [[package]] name = "blake3" version = "1.3.1" @@ -245,7 +379,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.7.2", "cc", "cfg-if 1.0.0", "constant_time_eq", @@ -271,12 +405,35 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + [[package]] name = "block-padding" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "bls12_381" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" +dependencies = [ + "ff", + "group", + "pairing", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "borsh" version = "0.9.4" @@ -375,6 +532,31 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.19" @@ -388,6 +570,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "clru" version = "0.5.0" @@ -532,6 +723,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crunchy" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" + [[package]] name = "crypto-common" version = "0.1.3" @@ -542,6 +739,31 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930b6a026ce9d358a17f9c9046c55d90b14bb847f36b6ebb6b19365d4feffb8" +dependencies = [ + "crypto_api", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -645,6 +867,26 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dynasm" version = "1.2.3" @@ -754,6 +996,15 @@ dependencies = [ "syn", ] +[[package]] +name = "equihash" +version = "0.1.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "blake2b_simd 1.0.0", + "byteorder", +] + [[package]] name = "eyre" version = "0.6.7" @@ -792,6 +1043,17 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "bitvec", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -824,6 +1086,26 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fpe" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" +dependencies = [ + "block-modes", + "cipher", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "funty" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" + [[package]] name = "futures" version = "0.3.21" @@ -923,6 +1205,18 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "byteorder", + "ff", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "gumdrop" version = "0.8.1" @@ -962,6 +1256,20 @@ dependencies = [ "tracing", ] +[[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", + "rayon", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -1004,6 +1312,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + [[package]] name = "http" version = "0.2.6" @@ -1147,6 +1465,15 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "incrementalmerkletree" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186fd3ab92aeac865d4b80b410de9a7b341d31ba8281373caed0b6d17b2b5e96" +dependencies = [ + "serde", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1197,6 +1524,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jubjub" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" +dependencies = [ + "bitvec", + "bls12_381", + "ff", + "group", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "keccak" version = "0.1.0" @@ -1231,6 +1572,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da83a57f3f5ba3680950aa3cbc806fc297bc0b289d42e8942ed528ace71b8145" + [[package]] name = "log" version = "0.4.16" @@ -1270,6 +1617,58 @@ dependencies = [ "libc", ] +[[package]] +name = "masp_primitives" +version = "0.5.0" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +dependencies = [ + "aes", + "bip0039", + "bitvec", + "blake2b_simd 1.0.0", + "blake2s_simd 1.0.0", + "bls12_381", + "borsh", + "byteorder", + "chacha20poly1305", + "crypto_api_chachapoly", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "rand", + "rand_core 0.6.3", + "serde", + "sha2 0.9.9", + "subtle", + "zcash_encoding", + "zcash_primitives", +] + +[[package]] +name = "masp_proofs" +version = "0.5.0" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +dependencies = [ + "bellman", + "blake2b_simd 1.0.0", + "bls12_381", + "byteorder", + "directories", + "ff", + "group", + "itertools", + "jubjub", + "lazy_static", + "masp_primitives", + "rand_core 0.6.3", + "zcash_primitives", + "zcash_proofs", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1315,6 +1714,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +[[package]] +name = "memuse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f69d25cd7528769ad3d897e99eb942774bff8b23165012af490351a44c5b583b" +dependencies = [ + "nonempty", +] + [[package]] name = "miniz_oxide" version = "0.4.4" @@ -1367,6 +1775,7 @@ dependencies = [ "ark-bls12-381", "ark-serialize", "bech32", + "bit-vec", "borsh", "chrono", "clru", @@ -1379,6 +1788,8 @@ dependencies = [ "ics23", "itertools", "loupe", + "masp_primitives", + "masp_proofs", "namada_proof_of_stake", "parity-wasm", "proptest", @@ -1387,6 +1798,7 @@ dependencies = [ "pwasm-utils", "rand", "rand_core 0.6.3", + "rayon", "rust_decimal", "serde", "serde_json", @@ -1456,6 +1868,8 @@ version = "0.7.0" dependencies = [ "borsh", "hex", + "masp_primitives", + "masp_proofs", "namada", "namada_macros", ] @@ -1481,6 +1895,12 @@ dependencies = [ "wee_alloc", ] +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + [[package]] name = "ntapi" version = "0.3.7" @@ -1583,18 +2003,90 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[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", + "blake2b_simd 1.0.0", + "ff", + "fpe", + "group", + "halo2", + "incrementalmerkletree", + "lazy_static", + "memuse", + "nonempty", + "pasta_curves", + "rand", + "reddsa", + "serde", + "subtle", + "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pairing" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" +dependencies = [ + "group", +] + [[package]] name = "parity-wasm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +[[package]] +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.3", + "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", + "static_assertions", + "subtle", +] + [[package]] name = "paste" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +[[package]] +name = "pbkdf2" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" +dependencies = [ + "crypto-mac", + "password-hash", +] + [[package]] name = "peg" version = "0.7.0" @@ -1679,6 +2171,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -1851,6 +2354,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" + [[package]] name = "rand" version = "0.8.5" @@ -1921,6 +2430,24 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "reddsa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e2c94bca3445cae0d55dff7370e29c24885d2403a1665ce19c106c79455e6" +dependencies = [ + "blake2b_simd 0.5.11", + "byteorder", + "digest 0.9.0", + "group", + "jubjub", + "pasta_curves", + "rand_core 0.6.3", + "serde", + "thiserror", + "zeroize", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -1930,6 +2457,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + [[package]] name = "regalloc" version = "0.0.31" @@ -2039,7 +2577,7 @@ version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22dc69eadbf0ee2110b8d20418c0c6edbaefec2811c4963dc17b6344e11fe0f8" dependencies = [ - "arrayvec", + "arrayvec 0.7.2", "num-traits", "serde", ] @@ -2328,6 +2866,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2378,6 +2922,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.3" @@ -2861,6 +3411,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "url" version = "2.2.2" @@ -3297,6 +3857,97 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129e027ad65ce1453680623c3fb5163cbf7107bfe1aa32257e7d0e63f9ced188" +dependencies = [ + "tap", +] + +[[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.3", + "subtle", +] + +[[package]] +name = "zcash_note_encryption" +version = "0.1.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "chacha20", + "chacha20poly1305", + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "zcash_primitives" +version = "0.5.0" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +dependencies = [ + "aes", + "bip0039", + "bitvec", + "blake2b_simd 1.0.0", + "blake2s_simd 1.0.0", + "bls12_381", + "byteorder", + "chacha20poly1305", + "equihash", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "memuse", + "nonempty", + "orchard", + "rand", + "rand_core 0.6.3", + "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.0", + "bls12_381", + "byteorder", + "directories", + "ff", + "group", + "jubjub", + "lazy_static", + "rand_core 0.6.3", + "zcash_primitives", +] + [[package]] name = "zeroize" version = "1.5.4" diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 674fb2a2d9..6f4e08b868 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -141,6 +141,8 @@ pub mod main { target, token, amount, + key: _, + shielded: _, } = transfer; let target_key = token::balance_key(&token, &target); let mut target_bal: token::Amount =