diff --git a/.cargo/config.offline b/.cargo/config.offline index ca6c2b3c3..ec5ae51cd 100644 --- a/.cargo/config.offline +++ b/.cargo/config.offline @@ -4,9 +4,24 @@ linker = "aarch64-linux-gnu-gcc" [source.crates-io] replace-with = "vendored-sources" +[source."https://github.com/zcash/halo2.git"] +git = "https://github.com/zcash/halo2.git" +rev = "0c33fa4e6e41464884765c8fb4cefebafd300ca2" +replace-with = "vendored-sources" + +[source."https://github.com/zcash/incrementalmerkletree.git"] +git = "https://github.com/zcash/incrementalmerkletree.git" +rev = "f23e3d89507849a24543121839eea6f40b141aff" +replace-with = "vendored-sources" + [source."https://github.com/zcash/librustzcash.git"] git = "https://github.com/zcash/librustzcash.git" -rev = "d14e7a707ce01cefcbc82651dad48f002185dded" +rev = "68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" +replace-with = "vendored-sources" + +[source."https://github.com/zcash/orchard.git"] +git = "https://github.com/zcash/orchard.git" +rev = "a30caec124aa6c6e7818b5100293204425c49de3" replace-with = "vendored-sources" [source."https://github.com/nuttycom/hdwallet.git"] diff --git a/Cargo.lock b/Cargo.lock index 18a1d68eb..d16b9e6f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,7 +139,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0944d18a9a37691b87733b39c9360c9950af9aa5f97e2455bc108d8eb64fc1c1" dependencies = [ - "bitvec", + "bitvec 0.22.3", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel", @@ -154,16 +154,6 @@ dependencies = [ "subtle", ] -[[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 = "bip0039" version = "0.9.0" @@ -190,10 +180,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", "tap", - "wyz", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", + "tap", + "wyz 0.5.0", ] [[package]] @@ -291,6 +293,12 @@ version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + [[package]] name = "byteorder" version = "1.4.3" @@ -428,12 +436,6 @@ dependencies = [ "lazy_static", ] -[[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" @@ -562,7 +564,7 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "equihash" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=d14e7a707ce01cefcbc82651dad48f002185dded#d14e7a707ce01cefcbc82651dad48f002185dded" +source = "git+https://github.com/zcash/librustzcash.git?rev=68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b#68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" dependencies = [ "blake2b_simd", "byteorder", @@ -571,7 +573,7 @@ dependencies = [ [[package]] name = "f4jumble" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=d14e7a707ce01cefcbc82651dad48f002185dded#d14e7a707ce01cefcbc82651dad48f002185dded" +source = "git+https://github.com/zcash/librustzcash.git?rev=68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b#68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" dependencies = [ "blake2b_simd", ] @@ -582,11 +584,23 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2958d04124b9f27f175eaeb9a9f383d026098aa837eadd8ba22c11f13a05b9e" dependencies = [ - "bitvec", + "bitvec 0.22.3", "rand_core 0.6.3", "subtle", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fnv" version = "1.0.7" @@ -613,6 +627,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures-channel" version = "0.3.21" @@ -719,11 +739,10 @@ dependencies = [ [[package]] name = "halo2_gadgets" version = "0.1.0-beta.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7524b798b8b3689a198cd87ee1d22fe3ca007a51d35c4093f32d75c0efc30abe" +source = "git+https://github.com/zcash/halo2.git?rev=0c33fa4e6e41464884765c8fb4cefebafd300ca2#0c33fa4e6e41464884765c8fb4cefebafd300ca2" dependencies = [ "arrayvec 0.7.2", - "bitvec", + "bitvec 0.22.3", "ff", "group", "halo2_proofs", @@ -737,11 +756,9 @@ dependencies = [ [[package]] name = "halo2_proofs" version = "0.1.0-beta.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0240b05b791cccfd6451b010b19711280e63b87f495bd84df0103f35c9139e7" +source = "git+https://github.com/zcash/halo2.git?rev=0c33fa4e6e41464884765c8fb4cefebafd300ca2#0c33fa4e6e41464884765c8fb4cefebafd300ca2" dependencies = [ "blake2b_simd", - "bumpalo", "ff", "group", "pasta_curves", @@ -851,11 +868,30 @@ dependencies = [ "want", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "incrementalmerkletree" version = "0.3.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5812f2cfa06a7694b842402e9a100529d80fdc3022ead65ad98ce0af0bcd3311" +source = "git+https://github.com/zcash/incrementalmerkletree.git?rev=f23e3d89507849a24543121839eea6f40b141aff#f23e3d89507849a24543121839eea6f40b141aff" dependencies = [ "serde", ] @@ -906,7 +942,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec", + "bitvec 0.22.3", "bls12_381", "ff", "group", @@ -1252,11 +1288,10 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "orchard" version = "0.1.0-beta.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bb8c5f5a0977683b5071650f1e7d91f296e344a796fda585086d5b4b0a74ea" +source = "git+https://github.com/zcash/orchard.git?rev=a30caec124aa6c6e7818b5100293204425c49de3#a30caec124aa6c6e7818b5100293204425c49de3" dependencies = [ "aes", - "bitvec", + "bitvec 0.22.3", "blake2b_simd", "ff", "fpe", @@ -1294,6 +1329,32 @@ dependencies = [ "group", ] +[[package]] +name = "parity-scale-codec" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b44461635bbb1a0300f100a841e571e7d919c81c73075ef5d152ffdb521066" +dependencies = [ + "arrayvec 0.7.2", + "bitvec 1.0.0", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c45ed1f39709f5a89338fab50e59816b2e8815f5bb58276e7ddf9afd495f73f8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parking_lot" version = "0.11.2" @@ -1422,6 +1483,27 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + [[package]] name = "proc-macro2" version = "1.0.37" @@ -1462,6 +1544,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "radix_trie" version = "0.2.1" @@ -1680,6 +1768,12 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1930,6 +2024,15 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + [[package]] name = "tower-service" version = "0.3.1" @@ -2011,12 +2114,12 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "uint" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" dependencies = [ "byteorder", - "crunchy 0.2.2", + "crunchy", "hex", "static_assertions", ] @@ -2198,10 +2301,19 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "zcash_address" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=d14e7a707ce01cefcbc82651dad48f002185dded#d14e7a707ce01cefcbc82651dad48f002185dded" +source = "git+https://github.com/zcash/librustzcash.git?rev=68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b#68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" dependencies = [ "bech32", "bs58", @@ -2212,7 +2324,7 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=d14e7a707ce01cefcbc82651dad48f002185dded#d14e7a707ce01cefcbc82651dad48f002185dded" +source = "git+https://github.com/zcash/librustzcash.git?rev=68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b#68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" dependencies = [ "byteorder", "nonempty", @@ -2221,17 +2333,17 @@ dependencies = [ [[package]] name = "zcash_history" version = "0.2.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=d14e7a707ce01cefcbc82651dad48f002185dded#d14e7a707ce01cefcbc82651dad48f002185dded" +source = "git+https://github.com/zcash/librustzcash.git?rev=68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b#68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" dependencies = [ - "bigint", "blake2b_simd", "byteorder", + "primitive-types", ] [[package]] name = "zcash_note_encryption" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=d14e7a707ce01cefcbc82651dad48f002185dded#d14e7a707ce01cefcbc82651dad48f002185dded" +source = "git+https://github.com/zcash/librustzcash.git?rev=68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b#68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" dependencies = [ "chacha20", "chacha20poly1305", @@ -2242,11 +2354,11 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=d14e7a707ce01cefcbc82651dad48f002185dded#d14e7a707ce01cefcbc82651dad48f002185dded" +source = "git+https://github.com/zcash/librustzcash.git?rev=68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b#68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd", "blake2s_simd 1.0.0", "bls12_381", @@ -2278,7 +2390,7 @@ dependencies = [ [[package]] name = "zcash_proofs" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=d14e7a707ce01cefcbc82651dad48f002185dded#d14e7a707ce01cefcbc82651dad48f002185dded" +source = "git+https://github.com/zcash/librustzcash.git?rev=68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b#68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" dependencies = [ "bellman", "blake2b_simd", diff --git a/Cargo.toml b/Cargo.toml index dca295334..4c1dc3274 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,10 +84,14 @@ panic = 'abort' codegen-units = 1 [patch.crates-io] +halo2_gadgets = { git = "https://github.com/zcash/halo2.git", rev = "0c33fa4e6e41464884765c8fb4cefebafd300ca2" } +halo2_proofs = { git = "https://github.com/zcash/halo2.git", rev = "0c33fa4e6e41464884765c8fb4cefebafd300ca2" } hdwallet = { git = "https://github.com/nuttycom/hdwallet.git", rev = "9b4c1bdbe0517e3a7a8f285d6048a37d472ba3bc" } -zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "d14e7a707ce01cefcbc82651dad48f002185dded" } -zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "d14e7a707ce01cefcbc82651dad48f002185dded" } -zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "d14e7a707ce01cefcbc82651dad48f002185dded" } -zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "d14e7a707ce01cefcbc82651dad48f002185dded" } -zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "d14e7a707ce01cefcbc82651dad48f002185dded" } -zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "d14e7a707ce01cefcbc82651dad48f002185dded" } +incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree.git", rev = "f23e3d89507849a24543121839eea6f40b141aff" } +orchard = { git = "https://github.com/zcash/orchard.git", rev = "a30caec124aa6c6e7818b5100293204425c49de3" } +zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" } +zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" } +zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" } +zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" } +zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" } +zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "68cbc2bb17b8967c7fb2f6034ac3d6174fc8f19b" } diff --git a/doc/release-notes.md b/doc/release-notes.md index 7229f33e6..a15f115e4 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -11,6 +11,15 @@ Option handling (provided that the wallet is enabled and pruning is disabled, and unless `-rescan=0` is specified explicitly). +RPC Interface +------------- + +- The default `minconf` value for `z_sendmany` is now 10 confirmations instead + of 1. +- When spending notes using `z_sendmany`, Orchard anchors are now selected at + a depth of 10 blocks instead of at the chain tip. If the `minconf` value is + less than 10, `minconf` is used for the anchor selection depth instead. + Build system ------------ diff --git a/qa/rpc-tests/mempool_limit.py b/qa/rpc-tests/mempool_limit.py index c16541658..f230d25b7 100755 --- a/qa/rpc-tests/mempool_limit.py +++ b/qa/rpc-tests/mempool_limit.py @@ -89,9 +89,9 @@ class MempoolLimit(BitcoinTestFramework): print("Checking mempool size reset after block mined...") self.check_mempool_sizes(0) zaddr4 = self.nodes[0].z_getnewaddress('sapling') - opid4 = self.nodes[0].z_sendmany(zaddr1, [{"address": zaddr4, "amount": Decimal('10.0') - 2*DEFAULT_FEE}]) + opid4 = self.nodes[0].z_sendmany(zaddr1, [{"address": zaddr4, "amount": Decimal('10.0') - 2*DEFAULT_FEE}], 1) wait_and_assert_operationid_status(self.nodes[0], opid4) - opid5 = self.nodes[0].z_sendmany(zaddr2, [{"address": zaddr4, "amount": Decimal('10.0') - 2*DEFAULT_FEE}]) + opid5 = self.nodes[0].z_sendmany(zaddr2, [{"address": zaddr4, "amount": Decimal('10.0') - 2*DEFAULT_FEE}], 1) wait_and_assert_operationid_status(self.nodes[0], opid5) self.sync_all() diff --git a/qa/rpc-tests/mempool_tx_expiry.py b/qa/rpc-tests/mempool_tx_expiry.py index 53129713e..4e4aec95d 100755 --- a/qa/rpc-tests/mempool_tx_expiry.py +++ b/qa/rpc-tests/mempool_tx_expiry.py @@ -93,7 +93,7 @@ class MempoolTxExpiryTest(BitcoinTestFramework): zsendamount = Decimal('1.0') - DEFAULT_FEE recipients = [] recipients.append({"address": z_bob, "amount": zsendamount}) - myopid = self.nodes[0].z_sendmany(z_alice, recipients) + myopid = self.nodes[0].z_sendmany(z_alice, recipients, 1) persist_shielded = wait_and_assert_operationid_status(self.nodes[0], myopid) persist_transparent = self.nodes[0].sendtoaddress(bob, 0.01) # Verify transparent transaction is version 4 intended for Sapling branch @@ -157,7 +157,7 @@ class MempoolTxExpiryTest(BitcoinTestFramework): self.split_network() print("\nBlockheight advances to equal expiry block height. After reorg, txs should persist in mempool") - myopid = self.nodes[0].z_sendmany(z_alice, recipients) + myopid = self.nodes[0].z_sendmany(z_alice, recipients, 1) persist_shielded_2 = wait_and_assert_operationid_status(self.nodes[0], myopid) persist_transparent_2 = self.nodes[0].sendtoaddress(bob, 0.01) rawtx_trans = self.nodes[0].getrawtransaction(persist_transparent_2, 1) @@ -192,7 +192,7 @@ class MempoolTxExpiryTest(BitcoinTestFramework): print("\nBlockheight advances to greater than expiry block height. After reorg, txs should expire from mempool") print("Balance before expire_shielded is sent: ", self.nodes[0].z_gettotalbalance()) - myopid = self.nodes[0].z_sendmany(z_alice, recipients) + myopid = self.nodes[0].z_sendmany(z_alice, recipients, 1) expire_shielded = wait_and_assert_operationid_status(self.nodes[0], myopid) expire_transparent = self.nodes[0].sendtoaddress(bob, 0.01) print("Blockheight node 0 at expire_transparent creation:", self.nodes[0].getblockchaininfo()['blocks']) @@ -223,7 +223,7 @@ class MempoolTxExpiryTest(BitcoinTestFramework): print("\nBlockheight advances to just before expiring soon threshold. Txs should be rejected from entering mempool.") print("Balance before expire_shielded is sent: ", self.nodes[0].z_gettotalbalance()) - myopid = self.nodes[0].z_sendmany(z_alice, recipients) + myopid = self.nodes[0].z_sendmany(z_alice, recipients, 1) expire_shielded = wait_and_assert_operationid_status(self.nodes[0], myopid) expire_transparent = self.nodes[0].sendtoaddress(bob, 0.01) print("Blockheight node 0 at expire_transparent creation:", self.nodes[0].getblockchaininfo()['blocks']) diff --git a/qa/rpc-tests/regtest_signrawtransaction.py b/qa/rpc-tests/regtest_signrawtransaction.py index a8e0a52b6..f2b1f0429 100755 --- a/qa/rpc-tests/regtest_signrawtransaction.py +++ b/qa/rpc-tests/regtest_signrawtransaction.py @@ -20,8 +20,7 @@ class RegtestSignrawtransactionTest (BitcoinTestFramework): # Create and sign Sapling transaction. # If the incorrect consensus branch id is selected, there will be a signing error. - opid = self.nodes[1].z_sendmany(taddr, - [{'address': zaddr1, 'amount': 1}]) + opid = self.nodes[1].z_sendmany(taddr, [{'address': zaddr1, 'amount': 1}], 1) wait_and_assert_operationid_status(self.nodes[1], opid) if __name__ == '__main__': diff --git a/qa/rpc-tests/wallet_listreceived.py b/qa/rpc-tests/wallet_listreceived.py index fcbe54b33..4df19ff50 100755 --- a/qa/rpc-tests/wallet_listreceived.py +++ b/qa/rpc-tests/wallet_listreceived.py @@ -142,7 +142,7 @@ class ListReceivedTest (BitcoinTestFramework): taddr = self.nodes[1].getnewaddress() # Generate some change by sending part of zaddr1 back to taddr - opid = self.nodes[1].z_sendmany(zaddr1, [{'address': taddr, 'amount': 0.6}]) + opid = self.nodes[1].z_sendmany(zaddr1, [{'address': taddr, 'amount': 0.6}], 1) txid = wait_and_assert_operationid_status(self.nodes[1], opid) self.generate_and_sync(height+4) @@ -220,7 +220,7 @@ class ListReceivedTest (BitcoinTestFramework): opid = self.nodes[1].z_sendmany(taddr, [ {'address': zaddr1, 'amount': 1, 'memo': my_memo}, {'address': zaddrExt, 'amount': 2}, - ]) + ], 1) txid = wait_and_assert_operationid_status(self.nodes[1], opid) self.sync_all() @@ -287,8 +287,7 @@ class ListReceivedTest (BitcoinTestFramework): # Generate some change by sending part of zaddr1 to zaddr2 txidPrev = txid zaddr2 = self.nodes[1].z_getnewaddress('sapling') - opid = self.nodes[1].z_sendmany(zaddr1, - [{'address': zaddr2, 'amount': 0.6}]) + opid = self.nodes[1].z_sendmany(zaddr1, [{'address': zaddr2, 'amount': 0.6}], 1) txid = wait_and_assert_operationid_status(self.nodes[1], opid) self.sync_all() self.generate_and_sync(height+4) @@ -404,7 +403,7 @@ class ListReceivedTest (BitcoinTestFramework): assert_equal(len(r), 0, "unified_addr should have received zero notes") # Create a note in this UA on node1 - opid = node.z_sendmany(zaddr1, [{'address': unified_addr, 'amount': 0.1}]) + opid = node.z_sendmany(zaddr1, [{'address': unified_addr, 'amount': 0.1}], 1) txid_sapling = wait_and_assert_operationid_status(node, opid) self.generate_and_sync(height+5) @@ -499,7 +498,7 @@ class ListReceivedTest (BitcoinTestFramework): opid = self.nodes[1].z_sendmany(uao, [ {'address': uaso, 'amount': Decimal('0.3')}, {'address': ua_node0, 'amount': Decimal('0.2')} - ]) + ], 1) txid1 = wait_and_assert_operationid_status(self.nodes[1], opid) self.sync_all() diff --git a/qa/rpc-tests/wallet_nullifiers.py b/qa/rpc-tests/wallet_nullifiers.py index 9b45b21d6..e9dc49b1e 100755 --- a/qa/rpc-tests/wallet_nullifiers.py +++ b/qa/rpc-tests/wallet_nullifiers.py @@ -53,7 +53,7 @@ class WalletNullifiersTest (BitcoinTestFramework): recipients = [] recipients.append({"address":myzaddr, "amount":7.0}) - wait_and_assert_operationid_status(self.nodes[0], self.nodes[0].z_sendmany(myzaddr0, recipients), timeout=120) + wait_and_assert_operationid_status(self.nodes[0], self.nodes[0].z_sendmany(myzaddr0, recipients, 1), timeout=120) self.sync_all() self.nodes[0].generate(1) @@ -71,7 +71,7 @@ class WalletNullifiersTest (BitcoinTestFramework): recipients = [] recipients.append({"address":myzaddr3, "amount":2.0}) - wait_and_assert_operationid_status(self.nodes[2], self.nodes[2].z_sendmany(myzaddr, recipients), timeout=120) + wait_and_assert_operationid_status(self.nodes[2], self.nodes[2].z_sendmany(myzaddr, recipients, 1), timeout=120) self.sync_all() self.nodes[2].generate(1) @@ -93,7 +93,7 @@ class WalletNullifiersTest (BitcoinTestFramework): recipients = [] recipients.append({"address":mytaddr1, "amount":1.0}) - wait_and_assert_operationid_status(self.nodes[1], self.nodes[1].z_sendmany(myzaddr, recipients), timeout=120) + wait_and_assert_operationid_status(self.nodes[1], self.nodes[1].z_sendmany(myzaddr, recipients, 1), timeout=120) self.sync_all() self.nodes[1].generate(1) diff --git a/qa/rpc-tests/wallet_sendmany_any_taddr.py b/qa/rpc-tests/wallet_sendmany_any_taddr.py index 0b44c3877..79846ccb8 100755 --- a/qa/rpc-tests/wallet_sendmany_any_taddr.py +++ b/qa/rpc-tests/wallet_sendmany_any_taddr.py @@ -53,6 +53,7 @@ class WalletSendManyAnyTaddr(BitcoinTestFramework): {'address': node3taddr1, 'amount': 60}, {'address': node3taddr2, 'amount': 75}, ], + 1 ), ) self.sync_all() @@ -67,7 +68,7 @@ class WalletSendManyAnyTaddr(BitcoinTestFramework): # We should be able to spend multiple UTXOs at once wait_and_assert_operationid_status( self.nodes[3], - self.nodes[3].z_sendmany('ANY_TADDR', [{'address': recipient, 'amount': 100}]), + self.nodes[3].z_sendmany('ANY_TADDR', [{'address': recipient, 'amount': 100}], 1), ) self.sync_all() @@ -84,7 +85,7 @@ class WalletSendManyAnyTaddr(BitcoinTestFramework): # Send from a change t-address. wait_and_assert_operationid_status( self.nodes[3], - self.nodes[3].z_sendmany('ANY_TADDR', [{'address': recipient, 'amount': 20}]), + self.nodes[3].z_sendmany('ANY_TADDR', [{'address': recipient, 'amount': 20}], 1), ) self.sync_all() @@ -95,7 +96,7 @@ class WalletSendManyAnyTaddr(BitcoinTestFramework): assert_equal(self.nodes[1].z_getbalance(recipient), 120) # Check that ANY_TADDR note selection doesn't attempt a double-spend - myopid = self.nodes[3].z_sendmany('ANY_TADDR', [{'address': recipient, 'amount': 20}]) + myopid = self.nodes[3].z_sendmany('ANY_TADDR', [{'address': recipient, 'amount': 20}], 1) wait_and_assert_operationid_status(self.nodes[3], myopid, "failed", "Insufficient funds: have 14.99998, need 20.00001") # Create an expired transaction on node 3. diff --git a/qa/rpc-tests/wallet_shieldingcoinbase.py b/qa/rpc-tests/wallet_shieldingcoinbase.py index f8a3b9ed7..c3261993a 100755 --- a/qa/rpc-tests/wallet_shieldingcoinbase.py +++ b/qa/rpc-tests/wallet_shieldingcoinbase.py @@ -107,7 +107,7 @@ class WalletShieldingCoinbaseTest (BitcoinTestFramework): assert_equal(error_result["method"], "z_sendmany") params = error_result["params"] assert_equal(params["fee"], DEFAULT_FEE) # default - assert_equal(params["minconf"], Decimal('1')) # default + assert_equal(params["minconf"], Decimal('10')) # default assert_equal(params["fromaddress"], mytaddr) assert_equal(params["amounts"][0]["address"], myzaddr) assert_equal(params["amounts"][0]["amount"], Decimal('1.23456789')) @@ -198,7 +198,7 @@ class WalletShieldingCoinbaseTest (BitcoinTestFramework): unshieldvalue = Decimal('10.0') recipients = [] recipients.append({"address":mytaddr, "amount": unshieldvalue}) - myopid = self.nodes[0].z_sendmany(myzaddr, recipients) + myopid = self.nodes[0].z_sendmany(myzaddr, recipients, 1) mytxid = wait_and_assert_operationid_status(self.nodes[0], myopid) assert(mytxid is not None) self.sync_all() @@ -224,7 +224,7 @@ class WalletShieldingCoinbaseTest (BitcoinTestFramework): recipients = [] amount = Decimal('10.0') - DEFAULT_FEE - Decimal('0.00000001') # this leaves change at 1 zatoshi less than dust threshold recipients.append({"address":self.nodes[0].getnewaddress(), "amount":amount }) - myopid = self.nodes[0].z_sendmany(mytaddr, recipients) + myopid = self.nodes[0].z_sendmany(mytaddr, recipients, 1) wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "Insufficient funds: have 10.00, need 0.00000053 more to avoid creating invalid change output 0.00000001 (dust threshold is 0.00000054)") # Send will fail because send amount is too big, even when including coinbase utxos @@ -238,9 +238,9 @@ class WalletShieldingCoinbaseTest (BitcoinTestFramework): # z_sendmany will fail because of insufficient funds recipients = [] recipients.append({"address":self.nodes[1].getnewaddress(), "amount":Decimal('10000.0')}) - myopid = self.nodes[0].z_sendmany(mytaddr, recipients) + myopid = self.nodes[0].z_sendmany(mytaddr, recipients, 1) wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "Insufficient funds: have 10.00, need 10000.00001; note that coinbase outputs will not be selected if you specify ANY_TADDR or if any transparent recipients are included.") - myopid = self.nodes[0].z_sendmany(myzaddr, recipients) + myopid = self.nodes[0].z_sendmany(myzaddr, recipients, 1) wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "Insufficient funds: have 9.99998, need 10000.00001; note that coinbase outputs will not be selected if you specify ANY_TADDR or if any transparent recipients are included.") # Send will fail because of insufficient funds unless sender uses coinbase utxos @@ -276,7 +276,7 @@ class WalletShieldingCoinbaseTest (BitcoinTestFramework): self.nodes[0].getinfo() # Issue #2263 Workaround END - myopid = self.nodes[0].z_sendmany(myzaddr, recipients) + myopid = self.nodes[0].z_sendmany(myzaddr, recipients, 1) try: wait_and_assert_operationid_status(self.nodes[0], myopid) except JSONRPCException as e: diff --git a/qa/rpc-tests/wallet_treestate.py b/qa/rpc-tests/wallet_treestate.py index f344f2e9d..c51097bb9 100755 --- a/qa/rpc-tests/wallet_treestate.py +++ b/qa/rpc-tests/wallet_treestate.py @@ -75,7 +75,7 @@ class WalletTreeStateTest (BitcoinTestFramework): recipients = [] recipients.append({"address": self.nodes[2].z_getnewaddress(), "amount": Decimal('18.0')}) recipients.append({"address": self.nodes[2].z_getnewaddress(), "amount": Decimal('12.0') - 4*DEFAULT_FEE}) - myopid = self.nodes[0].z_sendmany(myzaddr, recipients) + myopid = self.nodes[0].z_sendmany(myzaddr, recipients, 1) # Wait for Tx 2 to begin executing... for x in range(1, 60): diff --git a/qa/rpc-tests/wallet_z_sendmany.py b/qa/rpc-tests/wallet_z_sendmany.py index 09cad54c7..95efebcd3 100755 --- a/qa/rpc-tests/wallet_z_sendmany.py +++ b/qa/rpc-tests/wallet_z_sendmany.py @@ -23,7 +23,7 @@ from decimal import Decimal class WalletZSendmanyTest(BitcoinTestFramework): def setup_network(self, split=False): self.nodes = start_nodes(3, self.options.tmpdir, [[ - nuparams(NU5_BRANCH_ID, 220), + nuparams(NU5_BRANCH_ID, 240), ]] * self.num_nodes) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) @@ -91,7 +91,7 @@ class WalletZSendmanyTest(BitcoinTestFramework): # send from node 0 to node 2 taddr mytxid = self.nodes[0].sendtoaddress(mytaddr, 10.0) self.sync_all() - self.nodes[0].generate(1) + self.nodes[0].generate(10) self.sync_all() # send node 2 taddr to zaddr @@ -112,7 +112,7 @@ class WalletZSendmanyTest(BitcoinTestFramework): assert_equal(Decimal(wallet_info["shielded_unconfirmed_balance"]), zsendmanynotevalue) assert_equal(Decimal(wallet_info["shielded_balance"]), Decimal('0.0')) - self.nodes[2].generate(1) + self.nodes[2].generate(10) self.sync_all() assert_equal(self.nodes[2].getbalance(), node2utxobalance) diff --git a/src/rust/include/rust/orchard/wallet.h b/src/rust/include/rust/orchard/wallet.h index dae875dab..a08f10ef6 100644 --- a/src/rust/include/rust/orchard/wallet.h +++ b/src/rust/include/rust/orchard/wallet.h @@ -362,7 +362,8 @@ void orchard_wallet_get_potential_spends_from_nullifier( OrchardSpendInfoPtr* orchard_wallet_get_spend_info( const OrchardWalletPtr* wallet, const unsigned char txid[32], - uint32_t action_idx); + uint32_t action_idx, + const unsigned char as_of_root[32]); /** * Run the garbage collection operation on the wallet's note commitment diff --git a/src/rust/src/incremental_merkle_tree.rs b/src/rust/src/incremental_merkle_tree.rs index 29d780122..83c6e335c 100644 --- a/src/rust/src/incremental_merkle_tree.rs +++ b/src/rust/src/incremental_merkle_tree.rs @@ -1,22 +1,26 @@ use byteorder::{ReadBytesExt, WriteBytesExt}; +use std::collections::{BTreeMap, BTreeSet}; use std::io::{self, Read, Write}; use incrementalmerkletree::{ bridgetree::{BridgeTree, Checkpoint}, - Hashable, + Hashable, Position, }; use zcash_encoding::{Optional, Vector}; use zcash_primitives::merkle_tree::{ incremental::{ read_bridge_v1, read_leu64_usize, read_position, write_bridge_v1, write_position, - write_usize_leu64, SER_V1, + write_usize_leu64, SER_V1, SER_V2, }, HashSer, }; -pub fn write_checkpoint_v1(mut writer: W, checkpoint: &Checkpoint) -> io::Result<()> { +pub fn write_checkpoint_v2(mut writer: W, checkpoint: &Checkpoint) -> io::Result<()> { write_usize_leu64(&mut writer, checkpoint.bridges_len())?; writer.write_u8(if checkpoint.is_witnessed() { 1 } else { 0 })?; + Vector::write_sized(&mut writer, checkpoint.witnessed().iter(), |w, p| { + write_position(w, *p) + })?; Vector::write_sized( &mut writer, checkpoint.forgotten().iter(), @@ -29,25 +33,54 @@ pub fn write_checkpoint_v1(mut writer: W, checkpoint: &Checkpoint) -> Ok(()) } -pub fn read_checkpoint_v1(mut reader: R) -> io::Result { +pub fn read_checkpoint_v1( + mut reader: R, + save_positions: &mut BTreeMap, +) -> io::Result { + let bridges_len = read_leu64_usize(&mut reader)?; + let is_witnessed = reader.read_u8()? == 1; + let forgotten = Vector::read_collected(&mut reader, |mut r| { + Ok((read_position(&mut r)?, read_leu64_usize(&mut r)?)) + })?; + + // v1 checkpoints lacked information about the witnesses that were made during their + // span of blocks, so we assume that a witness was made in the first checkpoint + // with a corresponding bridge index. + let witnessed = if let Some(position) = save_positions.remove(&bridges_len) { + BTreeSet::from([position]) + } else { + BTreeSet::new() + }; + + Ok(Checkpoint::from_parts( + bridges_len, + is_witnessed, + witnessed, + forgotten, + )) +} + +pub fn read_checkpoint_v2(mut reader: R) -> io::Result { Ok(Checkpoint::from_parts( read_leu64_usize(&mut reader)?, reader.read_u8()? == 1, + Vector::read_collected(&mut reader, |r| read_position(r))?, Vector::read_collected(&mut reader, |mut r| { Ok((read_position(&mut r)?, read_leu64_usize(&mut r)?)) })?, )) } -pub fn write_tree_v1( +pub fn write_tree( mut writer: W, tree: &BridgeTree, ) -> io::Result<()> { - Vector::write(&mut writer, tree.prior_bridges(), |w, b| { - write_bridge_v1(w, b) + writer.write_u8(SER_V2)?; + Vector::write(&mut writer, tree.prior_bridges(), |mut w, b| { + write_bridge_v1(&mut w, b) })?; - Optional::write(&mut writer, tree.current_bridge().as_ref(), |w, b| { - write_bridge_v1(w, b) + Optional::write(&mut writer, tree.current_bridge().as_ref(), |mut w, b| { + write_bridge_v1(&mut w, b) })?; Vector::write_sized( &mut writer, @@ -58,7 +91,7 @@ pub fn write_tree_v1( }, )?; Vector::write(&mut writer, tree.checkpoints(), |w, c| { - write_checkpoint_v1(w, c) + write_checkpoint_v2(w, c) })?; write_usize_leu64(&mut writer, tree.max_checkpoints())?; @@ -66,17 +99,35 @@ pub fn write_tree_v1( } #[allow(clippy::redundant_closure)] -pub fn read_tree_v1( +pub fn read_tree( mut reader: R, ) -> io::Result> { + let ser_version = reader.read_u8()?; + let prior_bridges = Vector::read(&mut reader, |r| read_bridge_v1(r))?; + let current_bridge = Optional::read(&mut reader, |r| read_bridge_v1(r))?; + let saved: BTreeMap = Vector::read_collected(&mut reader, |mut r| { + Ok((read_position(&mut r)?, read_leu64_usize(&mut r)?)) + })?; + + let checkpoints = match ser_version { + SER_V1 => { + let mut save_positions = saved.iter().map(|(p, i)| (*i, *p)).collect(); + Vector::read_collected_mut(&mut reader, |r| read_checkpoint_v1(r, &mut save_positions)) + } + SER_V2 => Vector::read_collected(&mut reader, |r| read_checkpoint_v2(r)), + flag => Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("Unrecognized tree serialization version: {:?}", flag), + )), + }?; + let max_checkpoints = read_leu64_usize(&mut reader)?; + BridgeTree::from_parts( - Vector::read(&mut reader, |r| read_bridge_v1(r))?, - Optional::read(&mut reader, |r| read_bridge_v1(r))?, - Vector::read_collected(&mut reader, |mut r| { - Ok((read_position(&mut r)?, read_leu64_usize(&mut r)?)) - })?, - Vector::read(&mut reader, |r| read_checkpoint_v1(r))?, - read_leu64_usize(&mut reader)?, + prior_bridges, + current_bridge, + saved, + checkpoints, + max_checkpoints, ) .map_err(|err| { io::Error::new( @@ -88,23 +139,3 @@ pub fn read_tree_v1( ) }) } - -pub fn write_tree( - mut writer: W, - tree: &BridgeTree, -) -> io::Result<()> { - writer.write_u8(SER_V1)?; - write_tree_v1(&mut writer, tree) -} - -pub fn read_tree( - mut reader: R, -) -> io::Result> { - match reader.read_u8()? { - SER_V1 => read_tree_v1(&mut reader), - flag => Err(io::Error::new( - io::ErrorKind::InvalidInput, - format!("Unrecognized tree serialization version: {:?}", flag), - )), - } -} diff --git a/src/rust/src/wallet.rs b/src/rust/src/wallet.rs index 45197421f..708888cd1 100644 --- a/src/rust/src/wallet.rs +++ b/src/rust/src/wallet.rs @@ -1,5 +1,5 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use incrementalmerkletree::{bridgetree, bridgetree::BridgeTree, Frontier, Position, Tree}; +use incrementalmerkletree::{bridgetree, bridgetree::BridgeTree, Position, Tree}; use libc::c_uchar; use std::collections::{BTreeMap, BTreeSet}; use std::convert::TryInto; @@ -201,6 +201,8 @@ pub enum SpendRetrievalError { NoIvkForRecipient(Address), FvkNotFound(IncomingViewingKey), NoteNotPositioned(OutPoint), + InvalidMerkleRoot, + WitnessNotAvailableAtRoot(MerkleHashOrchard), } /// A struct used to return metadata about how a bundle was determined @@ -686,12 +688,12 @@ impl Wallet { .collect() } - pub fn note_commitment_tree_root(&self) -> MerkleHashOrchard { - self.witness_tree.root() + pub fn note_commitment_tree_root(&self, checkpoint_depth: usize) -> Option { + self.witness_tree.root(checkpoint_depth) } /// Fetches the information necessary to spend the note at the given `OutPoint`, - /// relative to the current root known to the wallet of the Orchard commitment tree. + /// relative to the specified root of the Orchard note commitment tree. /// /// Returns `None` if the `OutPoint` is not known to the wallet, or the Orchard bundle /// containing the note has not been passed to `Wallet::append_bundle_commitments`. @@ -699,6 +701,7 @@ impl Wallet { pub fn get_spend_info( &self, outpoint: OutPoint, + as_of_root: MerkleHashOrchard, ) -> Result { // TODO: Take `confirmations` parameter and obtain the Merkle path to the root at // that checkpoint, not the latest root. @@ -738,8 +741,8 @@ impl Wallet { let path = self .witness_tree - .authentication_path(*position) - .expect("wallet always has paths to positioned notes"); + .authentication_path(*position, &as_of_root) + .ok_or(SpendRetrievalError::WitnessNotAvailableAtRoot(as_of_root))?; Ok(OrchardSpendInfo::from_parts( fvk.clone(), @@ -939,7 +942,9 @@ pub extern "C" fn orchard_wallet_commitment_tree_root( let wallet = unsafe { wallet.as_ref() }.expect("Wallet pointer may not be null"); let root_ret = unsafe { root_ret.as_mut() }.expect("Cannot return to the null pointer."); - *root_ret = wallet.note_commitment_tree_root().to_bytes(); + // there is always a valid note commitment tree root at depth 0 + // (it may be the empty root) + *root_ret = wallet.note_commitment_tree_root(0).unwrap().to_bytes(); } #[no_mangle] @@ -1213,13 +1218,23 @@ pub extern "C" fn orchard_wallet_get_spend_info( wallet: *const Wallet, txid: *const [c_uchar; 32], action_idx: usize, + as_of_root: *const [c_uchar; 32], ) -> *mut OrchardSpendInfo { let wallet = unsafe { wallet.as_ref() }.expect("Wallet pointer may not be null."); let txid = TxId::from_bytes(*unsafe { txid.as_ref() }.expect("txid may not be null.")); + let as_of_root = MerkleHashOrchard::from_bytes( + unsafe { as_of_root.as_ref() }.expect("note commitment tree root may not be null."), + ); let outpoint = OutPoint { txid, action_idx }; - match wallet.get_spend_info(outpoint) { + let as_of_root = if as_of_root.is_some().into() { + Ok(as_of_root.unwrap()) + } else { + Err(SpendRetrievalError::InvalidMerkleRoot) + }; + + match as_of_root.and_then(|root| wallet.get_spend_info(outpoint, root)) { Ok(ret) => Box::into_raw(Box::new(ret)), Err(e) => { tracing::error!( diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp index 2d434b9ea..a308c88b0 100644 --- a/src/transaction_builder.cpp +++ b/src/transaction_builder.cpp @@ -259,7 +259,8 @@ TransactionBuilder::TransactionBuilder( nHeight(nHeight), keystore(keystore), coinsView(coinsView), - cs_coinsView(cs_coinsView) + cs_coinsView(cs_coinsView), + orchardAnchor(orchardAnchor) { mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight); @@ -292,6 +293,11 @@ bool TransactionBuilder::SupportsOrchard() const { return orchardBuilder.has_value(); } +std::optional TransactionBuilder::GetOrchardAnchor() const { + return orchardAnchor; +} + + bool TransactionBuilder::AddOrchardSpend( libzcash::OrchardSpendingKey sk, orchard::SpendInfo spendInfo) diff --git a/src/transaction_builder.h b/src/transaction_builder.h index a80ee3b3d..b8e8bdf75 100644 --- a/src/transaction_builder.h +++ b/src/transaction_builder.h @@ -260,6 +260,7 @@ private: CCriticalSection* cs_coinsView; CMutableTransaction mtx; CAmount fee = 10000; + std::optional orchardAnchor; std::optional orchardBuilder; CAmount valueBalanceOrchard = 0; @@ -297,6 +298,7 @@ public: cs_coinsView(std::move(builder.cs_coinsView)), mtx(std::move(builder.mtx)), fee(std::move(builder.fee)), + orchardAnchor(std::move(builder.orchardAnchor)), orchardBuilder(std::move(builder.orchardBuilder)), valueBalanceOrchard(std::move(builder.valueBalanceOrchard)), spends(std::move(builder.spends)), @@ -337,6 +339,8 @@ public: bool SupportsOrchard() const; + std::optional GetOrchardAnchor() const; + bool AddOrchardSpend( libzcash::OrchardSpendingKey sk, orchard::SpendInfo spendInfo); diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 6c67e403a..fded51bc6 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -497,7 +497,9 @@ uint256 AsyncRPCOperation_sendmany::main_impl() { { LOCK2(cs_main, pwalletMain->cs_wallet); pwalletMain->GetSaplingNoteWitnesses(saplingOutPoints, witnesses, anchor); - orchardSpendInfo = pwalletMain->GetOrchardSpendInfo(spendable.orchardNoteMetadata); + if (builder_.GetOrchardAnchor().has_value()) { + orchardSpendInfo = pwalletMain->GetOrchardSpendInfo(spendable.orchardNoteMetadata, builder_.GetOrchardAnchor().value()); + } } // Add Orchard spends diff --git a/src/wallet/gtest/test_orchard_wallet.cpp b/src/wallet/gtest/test_orchard_wallet.cpp index 8d6c693f0..c07a43ec1 100644 --- a/src/wallet/gtest/test_orchard_wallet.cpp +++ b/src/wallet/gtest/test_orchard_wallet.cpp @@ -110,7 +110,7 @@ TEST(TransactionBuilder, OrchardToOrchard) { // If we attempt to get spend info now, it will fail because the note hasn't // been witnessed in the Orchard commitment tree. - EXPECT_THROW(wallet.GetSpendInfo(notes), std::logic_error); + EXPECT_THROW(wallet.GetSpendInfo(notes, wallet.GetLatestAnchor()), std::logic_error); // Append the bundle to the wallet's commitment tree. CBlock fakeBlock; @@ -119,7 +119,7 @@ TEST(TransactionBuilder, OrchardToOrchard) { ASSERT_TRUE(wallet.AppendNoteCommitments(2, fakeBlock)); // Now we can get spend info for the note. - auto spendInfo = wallet.GetSpendInfo(notes); + auto spendInfo = wallet.GetSpendInfo(notes, wallet.GetLatestAnchor()); EXPECT_EQ(spendInfo[0].second.Value(), 40000); // Get the root of the commitment tree. diff --git a/src/wallet/gtest/test_wallet.cpp b/src/wallet/gtest/test_wallet.cpp index fd9a36817..983aec159 100644 --- a/src/wallet/gtest/test_wallet.cpp +++ b/src/wallet/gtest/test_wallet.cpp @@ -884,8 +884,8 @@ TEST(WalletTests, GetConflictedOrchardNotes) { auto recipient2 = ivk.Address(j2); // Generate tx to spend note A - auto noteToSpend = std::move(wallet.GetOrchardSpendInfo(orchardEntries)[0]); auto builder2 = TransactionBuilder(consensusParams, 2, orchardTree.root()); + auto noteToSpend = std::move(wallet.GetOrchardSpendInfo(orchardEntries, orchardTree.root())[0]); builder2.AddOrchardSpend(std::move(noteToSpend.first), std::move(noteToSpend.second)); auto maybeTx2 = builder2.Build(); EXPECT_TRUE(maybeTx2.IsTx()); @@ -897,7 +897,7 @@ TEST(WalletTests, GetConflictedOrchardNotes) { CWalletTx wtx2 {&wallet, tx2}; // Generate conflicting tx to spend note A - auto noteToSpend2 = std::move(wallet.GetOrchardSpendInfo(orchardEntries)[0]); + auto noteToSpend2 = std::move(wallet.GetOrchardSpendInfo(orchardEntries, orchardTree.root())[0]); auto builder3 = TransactionBuilder(consensusParams, 2, orchardTree.root()); builder3.AddOrchardSpend(std::move(noteToSpend2.first), std::move(noteToSpend2.second)); auto maybeTx3 = builder3.Build(); diff --git a/src/wallet/orchard.cpp b/src/wallet/orchard.cpp index 38b6fe7d0..75413ca24 100644 --- a/src/wallet/orchard.cpp +++ b/src/wallet/orchard.cpp @@ -13,14 +13,16 @@ std::optional OrchardWallet::GetSpendingKeyForAddr } std::vector> OrchardWallet::GetSpendInfo( - const std::vector& noteMetadata) const + const std::vector& noteMetadata, + uint256 anchor) const { std::vector> result; for (const auto& note : noteMetadata) { auto pSpendInfo = orchard_wallet_get_spend_info( inner.get(), note.GetOutPoint().hash.begin(), - note.GetOutPoint().n); + note.GetOutPoint().n, + anchor.begin()); if (pSpendInfo == nullptr) { throw std::logic_error("Called OrchardWallet::GetSpendInfo with unknown outpoint"); } else { diff --git a/src/wallet/orchard.h b/src/wallet/orchard.h index a846a95c3..a48129b4f 100644 --- a/src/wallet/orchard.h +++ b/src/wallet/orchard.h @@ -442,7 +442,8 @@ public: } std::vector> GetSpendInfo( - const std::vector& noteMetadata) const; + const std::vector& noteMetadata, + uint256 anchor) const; void GarbageCollect() { orchard_wallet_gc_note_commitment_tree(inner.get()); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 26d718039..fc7e1a67a 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -47,6 +47,7 @@ #include +#include #include #include #include @@ -1042,7 +1043,7 @@ UniValue sendmany(const UniValue& params, bool fHelp) " \"address\":amount (numeric) The Zcash address is the key, the numeric amount in " + CURRENCY_UNIT + " is the value\n" " ,...\n" " }\n" - "3. minconf (numeric, optional, default=1) Only use the balance confirmed at least this many times.\n" + "3. minconf (numeric, optional, default=10) Only use the balance confirmed at least this many times.\n" "4. \"comment\" (string, optional) A comment\n" "5. subtractfeefromamount (string, optional) A json array with addresses.\n" " The fee will be equally deducted from the amount of each selected address.\n" @@ -4857,6 +4858,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp) if (fHelp || params.size() < 2 || params.size() > 5) throw runtime_error( + strprintf( "z_sendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf ) ( fee ) ( privacyPolicy )\n" "\nSend multiple times. Amounts are decimal numbers with at most 8 digits of precision." "\nChange generated from one or more transparent addresses flows to a new transparent" @@ -4877,7 +4879,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp) " \"amount\":amount (numeric, required) The numeric amount in " + CURRENCY_UNIT + " is the value\n" " \"memo\":memo (string, optional) If the address is a zaddr, raw data represented in hexadecimal string format\n" " }, ... ]\n" - "3. minconf (numeric, optional, default=1) Only use funds confirmed at least this many times.\n" + "3. minconf (numeric, optional, default=%u) Only use funds confirmed at least this many times.\n" "4. fee (numeric, optional, default=" + strprintf("%s", FormatMoney(DEFAULT_FEE)) + ") The fee amount to attach to this transaction.\n" "5. privacyPolicy (string, optional, default=\"LegacyCompat\") Policy for what information leakage is acceptable.\n" " One of the following strings:\n" @@ -4905,6 +4907,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp) + HelpExampleCli("z_sendmany", "\"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" '[{\"address\": \"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\", \"amount\": 5.0}]'") + HelpExampleCli("z_sendmany", "\"ANY_TADDR\" '[{\"address\": \"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", \"amount\": 2.0}]'") + HelpExampleRpc("z_sendmany", "\"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", [{\"address\": \"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\", \"amount\": 5.0}]") + , nOrchardAnchorConfirmations) ); LOCK2(cs_main, pwalletMain->cs_wallet); @@ -5089,7 +5092,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp) } // Minimum confirmations - int nMinDepth = 1; + int nMinDepth = nOrchardAnchorConfirmations; if (params.size() > 2) { nMinDepth = params[2].get_int(); } @@ -5134,11 +5137,11 @@ UniValue z_sendmany(const UniValue& params, bool fHelp) UniValue contextInfo = o; std::optional orchardAnchor; - if (!ztxoSelector.SelectsSprout()) { + if (!ztxoSelector.SelectsSprout() && nMinDepth > 0) { // Allow Orchard recipients by setting an Orchard anchor. // TODO: Add an orchardAnchorHeight field to ZTXOSelector so we can ensure the // same anchor is used for witnesses of any selected Orchard note. - auto orchardAnchorHeight = nextBlockHeight - nOrchardAnchorConfirmations; + auto orchardAnchorHeight = nextBlockHeight - std::min((unsigned int) nMinDepth, nOrchardAnchorConfirmations); orchardAnchor = chainActive[orchardAnchorHeight]->hashFinalOrchardRoot; } TransactionBuilder builder(chainparams.GetConsensus(), nextBlockHeight, orchardAnchor, pwalletMain); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 56b665409..c7cbb1100 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3584,10 +3584,11 @@ void CWallet::GetSaplingNoteWitnesses(const std::vector& notes, } std::vector> CWallet::GetOrchardSpendInfo( - const std::vector& orchardNoteMetadata) const + const std::vector& orchardNoteMetadata, + uint256 anchor) const { AssertLockHeld(cs_wallet); - return orchardWallet.GetSpendInfo(orchardNoteMetadata); + return orchardWallet.GetSpendInfo(orchardNoteMetadata, anchor); } isminetype CWallet::IsMine(const CTxIn &txin) const diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index c54dd75a4..05c578707 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -79,7 +79,7 @@ static const unsigned int WITNESS_CACHE_SIZE = MAX_REORG_LENGTH + 1; //! Amount of entropy used in generation of the mnemonic seed, in bytes. static const size_t WALLET_MNEMONIC_ENTROPY_LENGTH = 32; //! -orchardanchorconfirmations default -static const unsigned int DEFAULT_ORCHARD_ANCHOR_CONFIRMATIONS = 1; +static const unsigned int DEFAULT_ORCHARD_ANCHOR_CONFIRMATIONS = 10; extern const char * DEFAULT_WALLET_DAT; @@ -1787,7 +1787,8 @@ public: std::vector>& witnesses, uint256 &final_anchor); std::vector> GetOrchardSpendInfo( - const std::vector& orchardNoteMetadata) const; + const std::vector& orchardNoteMetadata, + uint256 anchor) const; isminetype IsMine(const CTxIn& txin) const; CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const;