Compare commits
288 Commits
3be05964d7
...
e5210d50c9
Author | SHA1 | Date |
---|---|---|
Jack Grigg | e5210d50c9 | |
Jack Grigg | d9c1b05a83 | |
Jack Grigg | 85b5595519 | |
Jack Grigg | 9e9f58b26f | |
str4d | bd1feab2db | |
Jack Grigg | 77a971fbc1 | |
sasha | 5cfaea5dd4 | |
sasha | c85fae833e | |
str4d | d38da460eb | |
Jack Grigg | 1f3c145e4a | |
Jack Grigg | 5ea2aaff0d | |
Jack Grigg | 96edba2e41 | |
str4d | 584e8e56a8 | |
Jack Grigg | e646905f1b | |
Jack Grigg | 479b10364b | |
Charlie O'Keefe | 7b7ac148ac | |
Jack Grigg | c21890af8d | |
Charlie O'Keefe | b62d4917be | |
Jack Grigg | d4e47e4720 | |
Jack Grigg | 24089109b2 | |
Jack Grigg | e576f6616f | |
Larry Ruane | e575e0f217 | |
Kris Nuttycombe | 17e078f068 | |
Kris Nuttycombe | 151074cbb1 | |
Kris Nuttycombe | a8bae004b1 | |
Kris Nuttycombe | 758852b281 | |
Kris Nuttycombe | 9220ebb0de | |
Charlie O'Keefe | 11bd9463c1 | |
Kris Nuttycombe | 87cc0d22db | |
Kris Nuttycombe | 15bb06f452 | |
Charlie O'Keefe | 8c0e76e12b | |
Charlie O'Keefe | 612f3e7e81 | |
Kris Nuttycombe | ba3594f9ee | |
Charlie O'Keefe | 9974be75fb | |
Kris Nuttycombe | b32c8d3f0f | |
Kris Nuttycombe | b73ca36c45 | |
Kris Nuttycombe | 4392375404 | |
Kris Nuttycombe | fabff30d88 | |
Kris Nuttycombe | d724c81289 | |
Jack Grigg | 78fea33cde | |
Jonas Schnelli | eaf8eb40f4 | |
Kris Nuttycombe | a1bd440f2b | |
Kris Nuttycombe | 4df6c028f4 | |
Kris Nuttycombe | 7c3afad111 | |
Kris Nuttycombe | 2d673ca704 | |
Kris Nuttycombe | 0c977076bc | |
Steven Smith | e1e480e9c4 | |
Steven Smith | 445f4de0f4 | |
Kris Nuttycombe | 28210791a1 | |
Kris Nuttycombe | 315392ce3b | |
Steven Smith | 9993d948ef | |
Daira Hopwood | 6f4b283a3d | |
Jack Grigg | 562f5add87 | |
Jack Grigg | 65cb79e023 | |
Jack Grigg | 1505121877 | |
Kris Nuttycombe | cf84e1e79d | |
Kris Nuttycombe | 80324bc653 | |
Kris Nuttycombe | 7cd4462b20 | |
Kris Nuttycombe | b708b8ef60 | |
Jack Grigg | 22c6bf88a3 | |
Kris Nuttycombe | a783de43e3 | |
Kris Nuttycombe | 9582a2c1f1 | |
Kris Nuttycombe | 9d8c20ed78 | |
str4d | 2e77745c4e | |
str4d | 12b37d5a11 | |
str4d | 454e7640f0 | |
Kris Nuttycombe | b9b471bfa6 | |
Kris Nuttycombe | 5c9b7ec4df | |
Kris Nuttycombe | 860fc8886a | |
Kris Nuttycombe | 482f0e7e44 | |
ying tong | 1cd92e579c | |
therealyingtong | c1cd8a7e2d | |
therealyingtong | 6caec7aef1 | |
Kris Nuttycombe | 235767ba6e | |
Kris Nuttycombe | 24ce54f3ed | |
Kris Nuttycombe | 5d65952f54 | |
Kris Nuttycombe | 1b1495b7d7 | |
sasha | 2dcaac5fcb | |
Jack Grigg | 381b626a78 | |
Jack Grigg | c2220f4eb9 | |
Kris Nuttycombe | 7ba239e605 | |
Jack Grigg | d49c8a2865 | |
Jack Grigg | d4eee99060 | |
Jack Grigg | f2ae807891 | |
Jack Grigg | 920de99c85 | |
Jack Grigg | 959b068468 | |
Jack Grigg | 63437e4c18 | |
Jack Grigg | f56a04ded2 | |
Kris Nuttycombe | c3c5236cee | |
sasha | 3a957618b6 | |
sasha | 7071b41c55 | |
Kris Nuttycombe | 5a98080677 | |
sasha | f37d186e7e | |
ying tong | 4d7892bdde | |
Kris Nuttycombe | e335ff2325 | |
Kris Nuttycombe | d420e2efab | |
Daira Hopwood | 4611493c9d | |
Daira Hopwood | 340840894a | |
Daira Hopwood | 6ee6692c83 | |
Daira Hopwood | 1d31d4bc7c | |
Daira Hopwood | 55d14faebf | |
Kris Nuttycombe | 19bce27321 | |
Kris Nuttycombe | c9938d0348 | |
Daira Hopwood | ccada66324 | |
Kris Nuttycombe | 4eda33f184 | |
Kris Nuttycombe | 30dd633de6 | |
Kris Nuttycombe | 5d17e53c5c | |
Kris Nuttycombe | f5b23b3336 | |
Kris Nuttycombe | 1e1521f101 | |
str4d | 0f59c5b926 | |
therealyingtong | 3c0e1d9aa8 | |
Kris Nuttycombe | 0c38ad9200 | |
Jack Grigg | 93797d185b | |
Daira Hopwood | 577f695af5 | |
Daira Hopwood | 3e36252d9a | |
Daira Hopwood | d5b6e226f0 | |
Daira Hopwood | 7a98644ff2 | |
Daira Hopwood | cb2c89eedf | |
Jack Grigg | 7921ef1c55 | |
Jack Grigg | 6a7213b5d5 | |
str4d | 8964763bef | |
Daira Hopwood | f6a8461047 | |
Daira Hopwood | 799b46b90a | |
Daira Hopwood | 999b395360 | |
Kris Nuttycombe | 880cbe0dc4 | |
Kris Nuttycombe | 8bf99eaacc | |
Kris Nuttycombe | b21ca34d44 | |
Kris Nuttycombe | f057ebb375 | |
Kris Nuttycombe | e63d52896a | |
Kris Nuttycombe | ca34a97a37 | |
Daira Hopwood | d4405feddf | |
Kris Nuttycombe | 98ee3bd733 | |
Kris Nuttycombe | dc1609094d | |
Kris Nuttycombe | 0149451217 | |
Kris Nuttycombe | ecd0d24baa | |
Kris Nuttycombe | 46c33c68e0 | |
therealyingtong | 26b8ec5562 | |
therealyingtong | 663f8d434a | |
Kris Nuttycombe | 38f4afa80c | |
Kris Nuttycombe | 3b5fc4e583 | |
sasha | ced825edec | |
Kris Nuttycombe | 19ce19155c | |
Kris Nuttycombe | 9284c5552b | |
str4d | 7129827509 | |
Kris Nuttycombe | 51cb37e541 | |
Charlie O'Keefe | 4f04c96f7b | |
Kris Nuttycombe | c3cf141936 | |
Kris Nuttycombe | c9ef5cd45a | |
Kris Nuttycombe | 97895a6f6d | |
Kris Nuttycombe | faffe2e084 | |
Kris Nuttycombe | 6171f27b02 | |
Kris Nuttycombe | 9336c5e7f0 | |
Kris Nuttycombe | 23a7314607 | |
Kris Nuttycombe | 6b8ecc8a31 | |
Kris Nuttycombe | b78c2732ab | |
Kris Nuttycombe | 5c049b6b13 | |
Kris Nuttycombe | 8bc4c2aad8 | |
Kris Nuttycombe | f7006e9a33 | |
Kris Nuttycombe | 237384cf2b | |
Kris Nuttycombe | 5a6ef3467e | |
Daira Hopwood | 9b20105413 | |
Daira Hopwood | 6fb943d0f6 | |
Daira Hopwood | e940829313 | |
Daira Hopwood | 6f5efcbb0f | |
Daira Hopwood | 25792cba93 | |
Daira Hopwood | 8ec0d854b9 | |
Daira Hopwood | de933c1cb5 | |
Daira Hopwood | a36fceca70 | |
Steven Smith | 7f7d9e6e1f | |
sasha | f1d652fc78 | |
sasha | 8f68166c24 | |
sasha | 0e6b004a64 | |
str4d | 13351ff31d | |
Jack Grigg | fdb5709e7e | |
Jack Grigg | 9d5d781a15 | |
str4d | 414e74f70a | |
Kris Nuttycombe | 6540694845 | |
Kris Nuttycombe | cf4d0f3d2d | |
Charlie O'Keefe | e33178615c | |
Charlie O'Keefe | c2b5bf0c42 | |
str4d | d4a019e89b | |
Jack Grigg | 8b90d32972 | |
Kris Nuttycombe | 8b44cc7072 | |
Kris Nuttycombe | ceff068e20 | |
Jack Grigg | 613534bbb3 | |
Jack Grigg | 3fa58149b0 | |
Jack Grigg | 1c31a1c7d8 | |
Kris Nuttycombe | 1732a50957 | |
Daira Hopwood | 5990853de3 | |
Kris Nuttycombe | fd2f5bdc28 | |
Kris Nuttycombe | 65b5b7509c | |
Jack Grigg | 35b8c8648f | |
Jack Grigg | 9a60cdeed9 | |
Jack Grigg | ae3d2a3525 | |
Jack Grigg | 2230ba912f | |
therealyingtong | dbb7e027c8 | |
Sean Bowe | bd137561f2 | |
Kris Nuttycombe | 681505e9d6 | |
sasha | 2e52c1623d | |
sasha | 902187368e | |
sasha | 0e13aba1b7 | |
Sean Bowe | c5b87ba163 | |
therealyingtong | cedf2b5ecb | |
therealyingtong | 454c1eed46 | |
therealyingtong | 98cd4bab0b | |
str4d | 1c38c9833c | |
Sean Bowe | 3665f69a48 | |
Sean Bowe | f7824e8cec | |
Sean Bowe | 49ce03ed79 | |
Kris Nuttycombe | 5141c2971c | |
Kris Nuttycombe | 68d6c3ddc3 | |
Kris Nuttycombe | 1389eea5c2 | |
Larry Ruane | e170c3abd6 | |
Pieter Wuille | c079a518c0 | |
Pieter Wuille | 4693f8165f | |
Kris Nuttycombe | 783e269a7b | |
Kris Nuttycombe | bd50fbbb94 | |
therealyingtong | 4a8bdabb2f | |
sasha | 0f06774e94 | |
sasha | 3a7b4eca87 | |
sasha | 489c025f81 | |
Jack Grigg | b5ce94d16c | |
Jack Grigg | 7b7dddba0c | |
Jack Grigg | 2f1fbcc81f | |
Jack Grigg | 21430b13ac | |
Jack Grigg | adb7d074d5 | |
Jack Grigg | 4c49af7750 | |
Jack Grigg | 9f5f36e94c | |
Kris Nuttycombe | 18f443db1c | |
Kris Nuttycombe | 399ffa7f5c | |
sasha | 077f5550d4 | |
Kris Nuttycombe | f9380572c1 | |
Kris Nuttycombe | 044d2de463 | |
Kris Nuttycombe | 41ed311afa | |
Kris Nuttycombe | d9cddac773 | |
Jack Grigg | 701a7875a7 | |
Jack Grigg | d4078c8d25 | |
Jack Grigg | 85a8784b38 | |
Jack Grigg | bb4e792c84 | |
Taylor Hornby | 7d9dda4b7e | |
str4d | 48bf0241e6 | |
Steven | 82c33596b9 | |
Jack Grigg | b0769e3f1d | |
Jack Grigg | 235fde5193 | |
Steven Smith | a0740650c3 | |
Kris Nuttycombe | 49065bee59 | |
John Newbery | 4942020624 | |
Jack Grigg | 3179e45467 | |
Jack Grigg | d099e469e1 | |
Jack Grigg | c2a47b8d97 | |
Kris Nuttycombe | 3c84ed604e | |
Kris Nuttycombe | 225c78f3ab | |
Kris Nuttycombe | 258f0fc72f | |
sasha | eab7efcae5 | |
Kris Nuttycombe | bb91c9fbc3 | |
sasha | 2d6dcd4750 | |
therealyingtong | 1dae16a41c | |
ying tong | 2943e13c8b | |
Kris Nuttycombe | 1a1522a4f1 | |
Kris Nuttycombe | 3e3ee1bf3b | |
Kris Nuttycombe | 98ab4bcadb | |
Kris Nuttycombe | 1969add228 | |
Kris Nuttycombe | 66890866d6 | |
therealyingtong | 108e9d4658 | |
therealyingtong | 99a69182cd | |
therealyingtong | 4c4ee558d6 | |
str4d | 6cd5b8792b | |
Kris Nuttycombe | d08c992b5e | |
Kris Nuttycombe | dcf7f46260 | |
Kris Nuttycombe | 4c53499f11 | |
Kris Nuttycombe | 6587b2ed86 | |
Larry Ruane | ba9e020fcd | |
Kris Nuttycombe | 73a75a37fe | |
Steven Smith | 59c6b028f7 | |
therealyingtong | 79c0514919 | |
Steven | ffdc11c378 | |
Daira Hopwood | 816649db2a | |
Steven Smith | 512c862a5f | |
str4d | d0d6d47aa7 | |
Kris Nuttycombe | f6a3c68b09 | |
Jack Grigg | da755c2ae8 | |
therealyingtong | 94ab8e4c77 | |
Jack Grigg | 2da0856e6f | |
Taylor Hornby | a2c647d4bf | |
Larry Ruane | 698f7ba770 | |
therealyingtong | c7346e802e | |
therealyingtong | 6436230562 | |
Marius Kjærstad | bf1cc206c0 |
|
@ -1,9 +1,17 @@
|
||||||
|
[target.aarch64-unknown-linux-gnu]
|
||||||
|
linker = "aarch64-linux-gnu-gcc"
|
||||||
|
|
||||||
[source.crates-io]
|
[source.crates-io]
|
||||||
replace-with = "vendored-sources"
|
replace-with = "vendored-sources"
|
||||||
|
|
||||||
[source."https://github.com/zcash/librustzcash.git"]
|
[source."https://github.com/zcash/librustzcash.git"]
|
||||||
git = "https://github.com/zcash/librustzcash.git"
|
git = "https://github.com/zcash/librustzcash.git"
|
||||||
rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7"
|
rev = "9c1ed86c5aa8ae3b6d6dcc1478f2d6ba1264488f"
|
||||||
|
replace-with = "vendored-sources"
|
||||||
|
|
||||||
|
[source."https://github.com/nuttycom/hdwallet.git"]
|
||||||
|
git = "https://github.com/nuttycom/hdwallet.git"
|
||||||
|
rev = "576683b9f2865f1118c309017ff36e01f84420c9"
|
||||||
replace-with = "vendored-sources"
|
replace-with = "vendored-sources"
|
||||||
|
|
||||||
[source.vendored-sources]
|
[source.vendored-sources]
|
||||||
|
|
|
@ -70,19 +70,19 @@ jobs:
|
||||||
if: always()
|
if: always()
|
||||||
|
|
||||||
rust-clippy:
|
rust-clippy:
|
||||||
name: Clippy (1.54.0)
|
name: Clippy (1.59.0)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: 1.54.0
|
toolchain: 1.59.0
|
||||||
components: clippy
|
components: clippy
|
||||||
override: true
|
override: true
|
||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
uses: actions-rs/clippy-check@v1
|
uses: actions-rs/clippy-check@v1
|
||||||
with:
|
with:
|
||||||
name: Clippy (1.54.0)
|
name: Clippy (1.59.0)
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
args: --all-features --all-targets -- -D warnings
|
args: --all-features --all-targets -- -D warnings
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ jobs:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: 1.54.0
|
toolchain: 1.59.0
|
||||||
override: true
|
override: true
|
||||||
- run: rustup component add rustfmt
|
- run: rustup component add rustfmt
|
||||||
- uses: actions-rs/cargo@v1
|
- uses: actions-rs/cargo@v1
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*.exe
|
*.exe
|
||||||
src/bitcoin
|
src/bitcoin
|
||||||
src/zcashd
|
src/zcashd
|
||||||
|
src/zcashd-wallet-tool
|
||||||
src/zcash-cli
|
src/zcash-cli
|
||||||
src/zcash-gtest
|
src/zcash-gtest
|
||||||
src/zcash-tx
|
src/zcash-tx
|
||||||
|
|
6
COPYING
6
COPYING
|
@ -1,6 +1,6 @@
|
||||||
Copyright (c) 2016-2021 The Zcash developers
|
Copyright (c) 2016-2022 The Zcash developers
|
||||||
Copyright (c) 2009-2021 The Bitcoin Core developers
|
Copyright (c) 2009-2022 The Bitcoin Core developers
|
||||||
Copyright (c) 2009-2021 Bitcoin Developers
|
Copyright (c) 2009-2022 Bitcoin Developers
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
34
Cargo.toml
34
Cargo.toml
|
@ -18,13 +18,17 @@ repository = "https://github.com/zcash/zcash"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
rust-version = "1.56"
|
rust-version = "1.59"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "rustzcash"
|
name = "rustzcash"
|
||||||
path = "src/rust/src/rustzcash.rs"
|
path = "src/rust/src/rustzcash.rs"
|
||||||
crate-type = ["staticlib"]
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "zcashd-wallet-tool"
|
||||||
|
path = "src/rust/bin/wallet_tool.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bellman = "0.11"
|
bellman = "0.11"
|
||||||
blake2b_simd = "1"
|
blake2b_simd = "1"
|
||||||
|
@ -32,12 +36,12 @@ blake2s_simd = "1"
|
||||||
bls12_381 = "0.6"
|
bls12_381 = "0.6"
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
group = "0.11"
|
group = "0.11"
|
||||||
incrementalmerkletree = "0.2"
|
incrementalmerkletree = "=0.3.0-beta.1"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
jubjub = "0.8"
|
jubjub = "0.8"
|
||||||
memuse = "0.2"
|
memuse = "0.2"
|
||||||
nonempty = "0.7"
|
nonempty = "0.7"
|
||||||
orchard = "=0.1.0-beta.1"
|
orchard = "=0.1.0-beta.2"
|
||||||
secp256k1 = "0.20"
|
secp256k1 = "0.20"
|
||||||
subtle = "2.2"
|
subtle = "2.2"
|
||||||
rand_core = "0.6"
|
rand_core = "0.6"
|
||||||
|
@ -61,10 +65,20 @@ metrics-exporter-prometheus = "0.6"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
tokio = { version = "1.0", features = ["rt", "net", "time", "macros"] }
|
tokio = { version = "1.0", features = ["rt", "net", "time", "macros"] }
|
||||||
|
|
||||||
|
# Wallet tool
|
||||||
|
# (also depends on thiserror, tracing, and tracing-subscriber with "env-filter" and "fmt" features)
|
||||||
|
anyhow = "1.0"
|
||||||
|
backtrace = "0.3"
|
||||||
|
clearscreen = "1.0"
|
||||||
|
gumdrop = "0.8"
|
||||||
|
rand = "0.8"
|
||||||
|
secrecy = "0.8"
|
||||||
|
time = { version = "0.3", features = ["formatting", "macros"] }
|
||||||
|
|
||||||
[dependencies.tracing-subscriber]
|
[dependencies.tracing-subscriber]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["ansi", "env-filter", "time"]
|
features = ["ansi", "env-filter", "fmt", "time"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
@ -73,9 +87,9 @@ codegen-units = 1
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" }
|
hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" }
|
||||||
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
|
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "9c1ed86c5aa8ae3b6d6dcc1478f2d6ba1264488f" }
|
||||||
zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
|
zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "9c1ed86c5aa8ae3b6d6dcc1478f2d6ba1264488f" }
|
||||||
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
|
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "9c1ed86c5aa8ae3b6d6dcc1478f2d6ba1264488f" }
|
||||||
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
|
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "9c1ed86c5aa8ae3b6d6dcc1478f2d6ba1264488f" }
|
||||||
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
|
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "9c1ed86c5aa8ae3b6d6dcc1478f2d6ba1264488f" }
|
||||||
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
|
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "9c1ed86c5aa8ae3b6d6dcc1478f2d6ba1264488f" }
|
||||||
|
|
|
@ -48,6 +48,12 @@ $(BITCOIND_BIN): FORCE
|
||||||
$(BITCOIN_CLI_BIN): FORCE
|
$(BITCOIN_CLI_BIN): FORCE
|
||||||
$(MAKE) -C src $(@F)
|
$(MAKE) -C src $(@F)
|
||||||
|
|
||||||
|
check-security: FORCE
|
||||||
|
$(MAKE) -C src check-security
|
||||||
|
|
||||||
|
check-symbols: FORCE
|
||||||
|
$(MAKE) -C src check-symbols
|
||||||
|
|
||||||
if USE_LCOV
|
if USE_LCOV
|
||||||
|
|
||||||
baseline.info:
|
baseline.info:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Zcash 4.6.0-1
|
Zcash 4.7.0-rc1
|
||||||
<img align="right" width="120" height="80" src="doc/imgs/logo.png">
|
<img align="right" width="120" height="80" src="doc/imgs/logo.png">
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N)
|
dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N)
|
||||||
AC_PREREQ([2.60])
|
AC_PREREQ([2.60])
|
||||||
define(_CLIENT_VERSION_MAJOR, 4)
|
define(_CLIENT_VERSION_MAJOR, 4)
|
||||||
define(_CLIENT_VERSION_MINOR, 6)
|
define(_CLIENT_VERSION_MINOR, 7)
|
||||||
define(_CLIENT_VERSION_REVISION, 0)
|
define(_CLIENT_VERSION_REVISION, 0)
|
||||||
define(_CLIENT_VERSION_BUILD, 51)
|
define(_CLIENT_VERSION_BUILD, 25)
|
||||||
define(_ZC_BUILD_VAL, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, m4_incr(_CLIENT_VERSION_BUILD), m4_eval(_CLIENT_VERSION_BUILD < 50), 1, m4_eval(_CLIENT_VERSION_BUILD - 24), m4_eval(_CLIENT_VERSION_BUILD == 50), 1, , m4_eval(_CLIENT_VERSION_BUILD - 50)))
|
define(_ZC_BUILD_VAL, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, m4_incr(_CLIENT_VERSION_BUILD), m4_eval(_CLIENT_VERSION_BUILD < 50), 1, m4_eval(_CLIENT_VERSION_BUILD - 24), m4_eval(_CLIENT_VERSION_BUILD == 50), 1, , m4_eval(_CLIENT_VERSION_BUILD - 50)))
|
||||||
define(_CLIENT_VERSION_SUFFIX, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, _CLIENT_VERSION_REVISION-beta$1, m4_eval(_CLIENT_VERSION_BUILD < 50), 1, _CLIENT_VERSION_REVISION-rc$1, m4_eval(_CLIENT_VERSION_BUILD == 50), 1, _CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION-$1)))
|
define(_CLIENT_VERSION_SUFFIX, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, _CLIENT_VERSION_REVISION-beta$1, m4_eval(_CLIENT_VERSION_BUILD < 50), 1, _CLIENT_VERSION_REVISION-rc$1, m4_eval(_CLIENT_VERSION_BUILD == 50), 1, _CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION-$1)))
|
||||||
define(_CLIENT_VERSION_IS_RELEASE, true)
|
define(_CLIENT_VERSION_IS_RELEASE, true)
|
||||||
define(_COPYRIGHT_YEAR, 2021)
|
define(_COPYRIGHT_YEAR, 2022)
|
||||||
AC_INIT([Zcash],[_CLIENT_VERSION_MAJOR._CLIENT_VERSION_MINOR._CLIENT_VERSION_SUFFIX(_ZC_BUILD_VAL)],[https://github.com/zcash/zcash/issues],[zcash])
|
AC_INIT([Zcash],[_CLIENT_VERSION_MAJOR._CLIENT_VERSION_MINOR._CLIENT_VERSION_SUFFIX(_ZC_BUILD_VAL)],[https://github.com/zcash/zcash/issues],[zcash])
|
||||||
AC_CONFIG_SRCDIR([src/main.cpp])
|
AC_CONFIG_SRCDIR([src/main.cpp])
|
||||||
AC_CONFIG_HEADERS([src/config/bitcoin-config.h])
|
AC_CONFIG_HEADERS([src/config/bitcoin-config.h])
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
zcash (4.7.0~rc1) stable; urgency=medium
|
||||||
|
|
||||||
|
* 4.7.0-rc1 release.
|
||||||
|
|
||||||
|
-- Electric Coin Company <team@electriccoin.co> Tue, 22 Mar 2022 21:14:02 +0000
|
||||||
|
|
||||||
zcash (4.6.0+1) stable; urgency=medium
|
zcash (4.6.0+1) stable; urgency=medium
|
||||||
|
|
||||||
* 4.6.0-1 release.
|
* 4.6.0-1 release.
|
||||||
|
|
|
@ -4,9 +4,9 @@ Upstream-Contact: Electric Coin Company <team@electriccoin.co>
|
||||||
Source: https://github.com/zcash/zcash
|
Source: https://github.com/zcash/zcash
|
||||||
|
|
||||||
Files: *
|
Files: *
|
||||||
Copyright: 2016-2021, The Zcash developers
|
Copyright: 2016-2022, The Zcash developers
|
||||||
2009-2021, Bitcoin Core developers
|
2009-2022, Bitcoin Core developers
|
||||||
2009-2021, Bitcoin Developers
|
2009-2022, Bitcoin Developers
|
||||||
License: Expat
|
License: Expat
|
||||||
Comment: The Bitcoin Core developers encompasses the current developers listed on
|
Comment: The Bitcoin Core developers encompasses the current developers listed on
|
||||||
bitcoin.org, as well as the numerous contributors to the project.
|
bitcoin.org, as well as the numerous contributors to the project.
|
||||||
|
|
|
@ -189,8 +189,12 @@ def identify_executable(executable):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
args = sys.argv[1:]
|
||||||
|
allow_no_canary = "--allow-no-canary" in args
|
||||||
|
files = [arg for arg in args if not arg.startswith("--")]
|
||||||
|
|
||||||
retval = 0
|
retval = 0
|
||||||
for filename in sys.argv[1:]:
|
for filename in files:
|
||||||
try:
|
try:
|
||||||
etype = identify_executable(filename)
|
etype = identify_executable(filename)
|
||||||
if etype is None:
|
if etype is None:
|
||||||
|
@ -201,6 +205,8 @@ if __name__ == '__main__':
|
||||||
failed = []
|
failed = []
|
||||||
warning = []
|
warning = []
|
||||||
for (name, func) in CHECKS[etype]:
|
for (name, func) in CHECKS[etype]:
|
||||||
|
if name == "Canary" and allow_no_canary:
|
||||||
|
continue
|
||||||
if not func(filename):
|
if not func(filename):
|
||||||
if name in NONFATAL:
|
if name in NONFATAL:
|
||||||
warning.append(name)
|
warning.append(name)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
---
|
---
|
||||||
name: "zcash-4.6.0-1"
|
name: "zcash-4.7.0-rc1"
|
||||||
enable_cache: true
|
enable_cache: true
|
||||||
distro: "debian"
|
distro: "debian"
|
||||||
suites:
|
suites:
|
||||||
- "stretch"
|
|
||||||
- "buster"
|
- "buster"
|
||||||
|
- "bullseye"
|
||||||
architectures:
|
architectures:
|
||||||
- "amd64"
|
- "amd64"
|
||||||
packages:
|
packages:
|
||||||
|
|
|
@ -8,10 +8,10 @@ ifneq ($(host_os),mingw32)
|
||||||
$(package)_download_path=$(native_clang_download_path)
|
$(package)_download_path=$(native_clang_download_path)
|
||||||
$(package)_download_file_aarch64_linux=clang+llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
|
$(package)_download_file_aarch64_linux=clang+llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
|
||||||
$(package)_file_name_aarch64_linux=clang-llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
|
$(package)_file_name_aarch64_linux=clang-llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
|
||||||
$(package)_sha256_hash_aarch64_linux=968d65d2593850ee9b37fcda074fb7641529bd45d2f976af6c8197de3c22612f
|
$(package)_sha256_hash_aarch64_linux=15ff2db12683e69e552b6668f7ca49edaa01ce32cb1cbc8f8ed2e887ab291069
|
||||||
$(package)_download_file_linux=clang+llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-16.04.tar.xz
|
$(package)_download_file_linux=clang+llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-18.04.tar.xz
|
||||||
$(package)_file_name_linux=clang-llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-16.04.tar.xz
|
$(package)_file_name_linux=clang-llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-18.04.tar.xz
|
||||||
$(package)_sha256_hash_linux=76d0bf002ede7a893f69d9ad2c4e101d15a8f4186fbfe24e74856c8449acd7c1
|
$(package)_sha256_hash_linux=84a54c69781ad90615d1b0276a83ff87daaeded99fbc64457c350679df7b4ff0
|
||||||
|
|
||||||
define $(package)_stage_cmds
|
define $(package)_stage_cmds
|
||||||
mkdir -p $($(package)_staging_prefix_dir)/lib && \
|
mkdir -p $($(package)_staging_prefix_dir)/lib && \
|
||||||
|
@ -21,6 +21,7 @@ endef
|
||||||
|
|
||||||
else
|
else
|
||||||
# For Windows cross-compilation, use the MSYS2 binaries.
|
# For Windows cross-compilation, use the MSYS2 binaries.
|
||||||
|
# Using 13.0.0-3 because 13.0.1-1 has missing `new` and `delete` symbols.
|
||||||
$(package)_download_path=https://repo.msys2.org/mingw/x86_64
|
$(package)_download_path=https://repo.msys2.org/mingw/x86_64
|
||||||
$(package)_download_file=mingw-w64-x86_64-libc++-13.0.0-3-any.pkg.tar.zst
|
$(package)_download_file=mingw-w64-x86_64-libc++-13.0.0-3-any.pkg.tar.zst
|
||||||
$(package)_file_name=mingw-w64-x86_64-libcxx-13.0.0-3-any.pkg.tar.zst
|
$(package)_file_name=mingw-w64-x86_64-libcxx-13.0.0-3-any.pkg.tar.zst
|
||||||
|
|
|
@ -1,22 +1,26 @@
|
||||||
package=native_clang
|
package=native_clang
|
||||||
$(package)_major_version=13
|
$(package)_major_version=13
|
||||||
$(package)_version=13.0.0
|
$(package)_version=13.0.1
|
||||||
$(package)_download_path=https://github.com/llvm/llvm-project/releases/download/llvmorg-$($(package)_version)
|
$(package)_download_path=https://github.com/llvm/llvm-project/releases/download/llvmorg-$($(package)_version)
|
||||||
$(package)_download_path_linux=https://github.com/llvm/llvm-project/releases/download/llvmorg-$($(package)_version)
|
$(package)_download_path_linux=https://github.com/llvm/llvm-project/releases/download/llvmorg-$($(package)_version)
|
||||||
$(package)_download_file_linux=clang+llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-16.04.tar.xz
|
$(package)_download_file_linux=clang+llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-18.04.tar.xz
|
||||||
$(package)_file_name_linux=clang-llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-16.04.tar.xz
|
$(package)_file_name_linux=clang-llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-18.04.tar.xz
|
||||||
$(package)_sha256_hash_linux=76d0bf002ede7a893f69d9ad2c4e101d15a8f4186fbfe24e74856c8449acd7c1
|
$(package)_sha256_hash_linux=84a54c69781ad90615d1b0276a83ff87daaeded99fbc64457c350679df7b4ff0
|
||||||
$(package)_download_path_darwin=https://github.com/llvm/llvm-project/releases/download/llvmorg-$($(package)_major_version).0.0
|
$(package)_download_path_darwin=https://github.com/llvm/llvm-project/releases/download/llvmorg-$($(package)_version)
|
||||||
$(package)_download_file_darwin=clang+llvm-$($(package)_major_version).0.0-x86_64-apple-darwin.tar.xz
|
$(package)_download_file_darwin=clang+llvm-$($(package)_version)-x86_64-apple-darwin.tar.xz
|
||||||
$(package)_file_name_darwin=clang-llvm-$($(package)_major_version).0.0-x86_64-apple-darwin.tar.xz
|
$(package)_file_name_darwin=clang-llvm-$($(package)_version)-x86_64-apple-darwin.tar.xz
|
||||||
$(package)_sha256_hash_darwin=d051234eca1db1f5e4bc08c64937c879c7098900f7a0370f3ceb7544816a8b09
|
$(package)_sha256_hash_darwin=dec02d17698514d0fc7ace8869c38937851c542b02adf102c4e898f027145a4d
|
||||||
$(package)_download_path_freebsd=https://github.com/llvm/llvm-project/releases/download/llvmorg-$($(package)_version)
|
$(package)_download_path_freebsd=https://github.com/llvm/llvm-project/releases/download/llvmorg-$($(package)_version)
|
||||||
$(package)_download_file_freebsd=clang+llvm-$($(package)_version)-amd64-unknown-freebsd12.tar.xz
|
$(package)_download_file_freebsd=clang+llvm-$($(package)_version)-amd64-unknown-freebsd12.tar.xz
|
||||||
$(package)_file_name_freebsd=clang-llvm-$($(package)_version)-amd64-unknown-freebsd12.tar.xz
|
$(package)_file_name_freebsd=clang-llvm-$($(package)_version)-amd64-unknown-freebsd12.tar.xz
|
||||||
$(package)_sha256_hash_freebsd=e579747a36ff78aa0a5533fe43bc1ed1f8ed449c9bfec43c358d953ffbbdcf76
|
$(package)_sha256_hash_freebsd=8101c8d3a920bf930b33987ada5373f43537c5de8c194be0ea10530fd0ad5617
|
||||||
$(package)_download_file_aarch64_linux=clang+llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
|
$(package)_download_file_aarch64_linux=clang+llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
|
||||||
$(package)_file_name_aarch64_linux=clang-llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
|
$(package)_file_name_aarch64_linux=clang-llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
|
||||||
$(package)_sha256_hash_aarch64_linux=968d65d2593850ee9b37fcda074fb7641529bd45d2f976af6c8197de3c22612f
|
$(package)_sha256_hash_aarch64_linux=15ff2db12683e69e552b6668f7ca49edaa01ce32cb1cbc8f8ed2e887ab291069
|
||||||
|
|
||||||
|
ifneq (,$(wildcard /etc/arch-release))
|
||||||
|
$(package)_dependencies=native_libtinfo
|
||||||
|
endif
|
||||||
|
|
||||||
# Ensure we have clang native to the builder, not the target host
|
# Ensure we have clang native to the builder, not the target host
|
||||||
ifneq ($(canonical_host),$(build))
|
ifneq ($(canonical_host),$(build))
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package=native_tinfo
|
||||||
|
$(package)_version=5.6.0
|
||||||
|
$(package)_download_path_linux=http://ftp.debian.org/debian/pool/main/n/ncurses/
|
||||||
|
$(package)_download_file_linux=libtinfo5_6.0+20161126-1+deb9u2_amd64.deb
|
||||||
|
$(package)_file_name_linux=libtinfo5_6.0+20161126-1+deb9u2_amd64.deb
|
||||||
|
$(package)_sha256_hash_linux=1d249a3193568b5ef785ad8993b9ba6d6fdca0eb359204c2355532b82d25e9f5
|
||||||
|
|
||||||
|
define $(package)_extract_cmds
|
||||||
|
mkdir -p $($(package)_extract_dir) && \
|
||||||
|
echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \
|
||||||
|
$(build_SHA256SUM) -c $($(package)_extract_dir)/.$($(package)_file_name).hash && \
|
||||||
|
mkdir -p libtinfo5 && \
|
||||||
|
ar x --output libtinfo5 $($(package)_source_dir)/$($(package)_file_name) && \
|
||||||
|
cd libtinfo5 && \
|
||||||
|
tar xf data.tar.xz
|
||||||
|
endef
|
||||||
|
|
||||||
|
define $(package)_stage_cmds
|
||||||
|
pwd && \
|
||||||
|
mkdir -p $($(package)_staging_prefix_dir)/lib && \
|
||||||
|
cp libtinfo5/lib/x86_64-linux-gnu/libtinfo.so.5.9 $($(package)_staging_prefix_dir)/lib/libtinfo.so.5
|
||||||
|
endef
|
|
@ -1,14 +1,14 @@
|
||||||
package=native_rust
|
package=native_rust
|
||||||
$(package)_version=1.57.0
|
$(package)_version=1.59.0
|
||||||
$(package)_download_path=https://static.rust-lang.org/dist
|
$(package)_download_path=https://static.rust-lang.org/dist
|
||||||
$(package)_file_name_linux=rust-$($(package)_version)-x86_64-unknown-linux-gnu.tar.gz
|
$(package)_file_name_linux=rust-$($(package)_version)-x86_64-unknown-linux-gnu.tar.gz
|
||||||
$(package)_sha256_hash_linux=ea0253784b2e5c22659ff148d492a68d2e11da734491714ebc61cc93896efcda
|
$(package)_sha256_hash_linux=0c1c2da3fa26372e5178123aa5bb0fdcd4933fbad9bfb268ffbd71807182ecae
|
||||||
$(package)_file_name_darwin=rust-$($(package)_version)-x86_64-apple-darwin.tar.gz
|
$(package)_file_name_darwin=rust-$($(package)_version)-x86_64-apple-darwin.tar.gz
|
||||||
$(package)_sha256_hash_darwin=15ceffc4743434c19d08f73fb4edd6642b7fd8162ed7101d3e6ca2c691fcb699
|
$(package)_sha256_hash_darwin=d82204f536af0c7bfd2ea2213dc46b99911860cfc5517f7321244412ae96f159
|
||||||
$(package)_file_name_freebsd=rust-$($(package)_version)-x86_64-unknown-freebsd.tar.gz
|
$(package)_file_name_freebsd=rust-$($(package)_version)-x86_64-unknown-freebsd.tar.gz
|
||||||
$(package)_sha256_hash_freebsd=ebe96fa1f15e8d70c91e81aab7e0c341717b909225029f37d52fbdfa506e3fab
|
$(package)_sha256_hash_freebsd=83f9c49b6e9025b712fc5d65e49f1b6ad959966534cd39c8dc2ce2c85a6ca484
|
||||||
$(package)_file_name_aarch64_linux=rust-$($(package)_version)-aarch64-unknown-linux-gnu.tar.gz
|
$(package)_file_name_aarch64_linux=rust-$($(package)_version)-aarch64-unknown-linux-gnu.tar.gz
|
||||||
$(package)_sha256_hash_aarch64_linux=d66847f7cf7b548ecb328c400ac4f691ee2aea6ff5cd9286ad8733239569556c
|
$(package)_sha256_hash_aarch64_linux=ab5da30a3de5433e26cbc74c56b9d97b569769fc2e456fc54378adc8baaee4f0
|
||||||
|
|
||||||
# Mapping from GCC canonical hosts to Rust targets
|
# Mapping from GCC canonical hosts to Rust targets
|
||||||
# If a mapping is not present, we assume they are identical, unless $host_os is
|
# If a mapping is not present, we assume they are identical, unless $host_os is
|
||||||
|
@ -17,10 +17,10 @@ $(package)_rust_target_x86_64-pc-linux-gnu=x86_64-unknown-linux-gnu
|
||||||
$(package)_rust_target_x86_64-w64-mingw32=x86_64-pc-windows-gnu
|
$(package)_rust_target_x86_64-w64-mingw32=x86_64-pc-windows-gnu
|
||||||
|
|
||||||
# Mapping from Rust targets to SHA-256 hashes
|
# Mapping from Rust targets to SHA-256 hashes
|
||||||
$(package)_rust_std_sha256_hash_aarch64-unknown-linux-gnu=4c70901d1cbddec9ea99fbd62b20f454d30e1ffbb48a21169ac823b3f02a1fbc
|
$(package)_rust_std_sha256_hash_aarch64-unknown-linux-gnu=81dbd37919f631f962ac0798111803eb8f06ffde608f0e5dd3682d701cf5566d
|
||||||
$(package)_rust_std_sha256_hash_x86_64-apple-darwin=c1eb892ddb50ebeed288b7aa8171ad46d62362bb26b2d82d2b463dfd45606dc2
|
$(package)_rust_std_sha256_hash_x86_64-apple-darwin=959af8bafbc9f3916a1d1111d7378fdd7aa459410cdd2d3bbfc2d9d9a6db0683
|
||||||
$(package)_rust_std_sha256_hash_x86_64-pc-windows-gnu=75c910899ed36a90b155e3a01c21b863000675867efc56f2b68c44edd4b7e18c
|
$(package)_rust_std_sha256_hash_x86_64-pc-windows-gnu=9a67ae84e9e75efb57eeeab617e32379a555de336a30bb74a476e575cd38f63a
|
||||||
$(package)_rust_std_sha256_hash_x86_64-unknown-freebsd=1528a4bc7e3ba42da164bcc7b952dfa73048333c5b9254ce2d03db6bab6081e8
|
$(package)_rust_std_sha256_hash_x86_64-unknown-freebsd=cf5e4303dd7c3b70a738a2336097c9f2189c8b702a89a8c453d83ac0dee4602c
|
||||||
|
|
||||||
define rust_target
|
define rust_target
|
||||||
$(if $($(1)_rust_target_$(2)),$($(1)_rust_target_$(2)),$(if $(findstring darwin,$(3)),x86_64-apple-darwin,$(if $(findstring freebsd,$(3)),x86_64-unknown-freebsd,$(2))))
|
$(if $($(1)_rust_target_$(2)),$($(1)_rust_target_$(2)),$(if $(findstring darwin,$(3)),x86_64-apple-darwin,$(if $(findstring freebsd,$(3)),x86_64-unknown-freebsd,$(2))))
|
||||||
|
|
|
@ -2,6 +2,10 @@ zcash_packages := libsodium utfcpp
|
||||||
packages := boost libevent zeromq $(zcash_packages) googletest
|
packages := boost libevent zeromq $(zcash_packages) googletest
|
||||||
native_packages := native_clang native_ccache native_rust
|
native_packages := native_clang native_ccache native_rust
|
||||||
|
|
||||||
|
ifneq (,$(wildcard /etc/arch-release))
|
||||||
|
native_packages += native_libtinfo
|
||||||
|
endif
|
||||||
|
|
||||||
wallet_packages=bdb
|
wallet_packages=bdb
|
||||||
|
|
||||||
$(host_arch)_$(host_os)_native_packages += native_b2
|
$(host_arch)_$(host_os)_native_packages += native_b2
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||||
.TH ZCASH-CLI "1" "January 2022" "zcash-cli v4.6.0-1" "User Commands"
|
.TH ZCASH-CLI "1" "March 2022" "zcash-cli v4.7.0-rc1" "User Commands"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
zcash-cli \- manual page for zcash-cli v4.6.0-1
|
zcash-cli \- manual page for zcash-cli v4.7.0-rc1
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Zcash RPC client version v4.6.0\-1
|
Zcash RPC client version v4.7.0\-rc1
|
||||||
.PP
|
.PP
|
||||||
In order to ensure you are adequately protecting your privacy when using Zcash,
|
In order to ensure you are adequately protecting your privacy when using Zcash,
|
||||||
please see <https://z.cash/support/security/>.
|
please see <https://z.cash/support/security/>.
|
||||||
|
@ -29,7 +29,7 @@ Specify configuration file (default: zcash.conf)
|
||||||
.HP
|
.HP
|
||||||
\fB\-datadir=\fR<dir>
|
\fB\-datadir=\fR<dir>
|
||||||
.IP
|
.IP
|
||||||
Specify data directory
|
Specify data directory (this path cannot use '~')
|
||||||
.HP
|
.HP
|
||||||
\fB\-stdin\fR
|
\fB\-stdin\fR
|
||||||
.IP
|
.IP
|
||||||
|
@ -79,8 +79,8 @@ Timeout in seconds during HTTP requests, or 0 for no timeout. (default:
|
||||||
In order to ensure you are adequately protecting your privacy when using Zcash,
|
In order to ensure you are adequately protecting your privacy when using Zcash,
|
||||||
please see <https://z.cash/support/security/>.
|
please see <https://z.cash/support/security/>.
|
||||||
|
|
||||||
Copyright (C) 2009-2021 The Bitcoin Core Developers
|
Copyright (C) 2009-2022 The Bitcoin Core Developers
|
||||||
Copyright (C) 2015-2021 The Zcash Developers
|
Copyright (C) 2015-2022 The Zcash Developers
|
||||||
|
|
||||||
This is experimental software.
|
This is experimental software.
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||||
.TH ZCASH-TX "1" "January 2022" "zcash-tx v4.6.0-1" "User Commands"
|
.TH ZCASH-TX "1" "March 2022" "zcash-tx v4.7.0-rc1" "User Commands"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
zcash-tx \- manual page for zcash-tx v4.6.0-1
|
zcash-tx \- manual page for zcash-tx v4.7.0-rc1
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Zcash zcash\-tx utility version v4.6.0\-1
|
Zcash zcash\-tx utility version v4.7.0\-rc1
|
||||||
.SS "Usage:"
|
.SS "Usage:"
|
||||||
.TP
|
.TP
|
||||||
zcash\-tx [options] <hex\-tx> [commands]
|
zcash\-tx [options] <hex\-tx> [commands]
|
||||||
|
@ -91,8 +91,8 @@ Set register NAME to given JSON\-STRING
|
||||||
In order to ensure you are adequately protecting your privacy when using Zcash,
|
In order to ensure you are adequately protecting your privacy when using Zcash,
|
||||||
please see <https://z.cash/support/security/>.
|
please see <https://z.cash/support/security/>.
|
||||||
|
|
||||||
Copyright (C) 2009-2021 The Bitcoin Core Developers
|
Copyright (C) 2009-2022 The Bitcoin Core Developers
|
||||||
Copyright (C) 2015-2021 The Zcash Developers
|
Copyright (C) 2015-2022 The Zcash Developers
|
||||||
|
|
||||||
This is experimental software.
|
This is experimental software.
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||||
.TH ZCASHD "1" "January 2022" "zcashd v4.6.0-1" "User Commands"
|
.TH ZCASHD "1" "March 2022" "zcashd v4.7.0-rc1" "User Commands"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
zcashd \- manual page for zcashd v4.6.0-1
|
zcashd \- manual page for zcashd v4.7.0-rc1
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Zcash Daemon version v4.6.0\-1
|
Zcash Daemon version v4.7.0\-rc1
|
||||||
.PP
|
.PP
|
||||||
In order to ensure you are adequately protecting your privacy when using Zcash,
|
In order to ensure you are adequately protecting your privacy when using Zcash,
|
||||||
please see <https://z.cash/support/security/>.
|
please see <https://z.cash/support/security/>.
|
||||||
|
@ -49,7 +49,7 @@ Run in the background as a daemon and accept commands
|
||||||
.HP
|
.HP
|
||||||
\fB\-datadir=\fR<dir>
|
\fB\-datadir=\fR<dir>
|
||||||
.IP
|
.IP
|
||||||
Specify data directory
|
Specify data directory (this path cannot use '~')
|
||||||
.HP
|
.HP
|
||||||
\fB\-paramsdir=\fR<dir>
|
\fB\-paramsdir=\fR<dir>
|
||||||
.IP
|
.IP
|
||||||
|
@ -352,6 +352,14 @@ by TxID)
|
||||||
Delete all wallet transactions and only recover those parts of the
|
Delete all wallet transactions and only recover those parts of the
|
||||||
blockchain through \fB\-rescan\fR on startup (1 = keep tx meta data e.g.
|
blockchain through \fB\-rescan\fR on startup (1 = keep tx meta data e.g.
|
||||||
account owner and payment request information, 2 = drop tx meta data)
|
account owner and payment request information, 2 = drop tx meta data)
|
||||||
|
.HP
|
||||||
|
\fB\-walletrequirebackup=\fR<bool>
|
||||||
|
.IP
|
||||||
|
By default, the wallet will not allow generation of new spending keys &
|
||||||
|
addresses from the mnemonic seed until the backup of that seed has been
|
||||||
|
confirmed with the `zcashd\-wallet\-tool` utility. A user may start zcashd
|
||||||
|
with `\-walletrequirebackup=false` to allow generation of spending keys
|
||||||
|
even if the backup has not yet been confirmed.
|
||||||
.PP
|
.PP
|
||||||
ZeroMQ notification options:
|
ZeroMQ notification options:
|
||||||
.HP
|
.HP
|
||||||
|
@ -558,8 +566,8 @@ console, 600 otherwise)
|
||||||
In order to ensure you are adequately protecting your privacy when using Zcash,
|
In order to ensure you are adequately protecting your privacy when using Zcash,
|
||||||
please see <https://z.cash/support/security/>.
|
please see <https://z.cash/support/security/>.
|
||||||
|
|
||||||
Copyright (C) 2009-2021 The Bitcoin Core Developers
|
Copyright (C) 2009-2022 The Bitcoin Core Developers
|
||||||
Copyright (C) 2015-2021 The Zcash Developers
|
Copyright (C) 2015-2022 The Zcash Developers
|
||||||
|
|
||||||
This is experimental software.
|
This is experimental software.
|
||||||
|
|
||||||
|
|
|
@ -13,23 +13,23 @@ be generated on load of the wallet, or the first time the wallet is unlocked,
|
||||||
and is available via the `z_exportwallet` RPC call. All new addresses produced
|
and is available via the `z_exportwallet` RPC call. All new addresses produced
|
||||||
by the wallet are now derived from this seed using the HD wallet functionality
|
by the wallet are now derived from this seed using the HD wallet functionality
|
||||||
described in ZIP 32 and ZIP 316. For users upgrading an existing Zcashd wallet,
|
described in ZIP 32 and ZIP 316. For users upgrading an existing Zcashd wallet,
|
||||||
it is recommended that the wallet be backed up prior to upgrading to the 4.5.2
|
it is recommended that the wallet be backed up prior to upgrading to the 4.7.0
|
||||||
Zcashd release.
|
Zcashd release.
|
||||||
|
|
||||||
Following the upgrade to 4.5.2, Zcashd will require that the user confirm that
|
Following the upgrade to 4.7.0, Zcashd will require that the user confirm that
|
||||||
they have backed up their new emergency recovery phrase, which may be obtained
|
they have backed up their new emergency recovery phrase, which may be obtained
|
||||||
from the output of the `z_exportwallet` RPC call. This confirmation can be
|
from the output of the `z_exportwallet` RPC call. This confirmation can be
|
||||||
performed manually using the `zcashd-wallet-tool` utility that is supplied with
|
performed manually using the `zcashd-wallet-tool` utility that is supplied
|
||||||
this release. The wallet will not allow the generation of new addresses until
|
with this release (built or installed in the same directory as `zcashd`).
|
||||||
this confirmation has been performed. It is recommended that after this
|
The wallet will not allow the generation of new addresses until this
|
||||||
upgrade, that funds tied to preexisting addresses be migrated to newly
|
confirmation has been performed. It is recommended that after this upgrade,
|
||||||
generated addresses so that all wallet funds are recoverable using the
|
that funds tied to preexisting addresses be migrated to newly generated
|
||||||
emergency recovery phrase going forward. If you choose not to migrate funds in
|
addresses so that all wallet funds are recoverable using the emergency
|
||||||
this fashion, you will continue to need to securely back up the entire
|
recovery phrase going forward. If you choose not to migrate funds in this
|
||||||
`wallet.dat` file to ensure that you do not lose access to existing funds;
|
fashion, you will continue to need to securely back up the entire `wallet.dat`
|
||||||
EXISTING FUNDS WILL NOT BE RECOVERABLE USING THE EMERGENCY RECOVERY PHRASE
|
file to ensure that you do not lose access to existing funds; EXISTING FUNDS
|
||||||
UNLESS THEY HAVE BEEN MOVED TO A NEWLY GENERATED ADDRESS FOLLOWING THE 4.5.2
|
WILL NOT BE RECOVERABLE USING THE EMERGENCY RECOVERY PHRASE UNLESS THEY HAVE
|
||||||
UPGRADE.
|
BEEN MOVED TO A NEWLY GENERATED ADDRESS FOLLOWING THE 4.7.0 UPGRADE.
|
||||||
|
|
||||||
New RPC Methods
|
New RPC Methods
|
||||||
---------------
|
---------------
|
||||||
|
@ -81,11 +81,15 @@ Wallet
|
||||||
pool boundaries, they must be explicitly enabled via a parameter to
|
pool boundaries, they must be explicitly enabled via a parameter to
|
||||||
the 'z_sendmany' call.
|
the 'z_sendmany' call.
|
||||||
|
|
||||||
- A new boolean parameter, `allowRevealedAmounts`, has been added to the
|
- A new string parameter, `privacyPolicy`, has been added to the list of
|
||||||
list of arguments accepted by 'z_sendmany'. This parameter defaults to
|
arguments accepted by `z_sendmany`. This parameter enables the caller to
|
||||||
`false` and is only required when the transaction being constructed
|
control what kind of information they permit `zcashd` to reveal when creating
|
||||||
would reveal transaction amounts as a consequence of ZEC value crossing
|
the transaction. If the transaction can only be created by revealing more
|
||||||
shielded pool boundaries via the turnstile.
|
information than the given strategy permits, `z_sendmany` will return an
|
||||||
|
error. The parameter defaults to `LegacyCompat`, which applies the most
|
||||||
|
restrictive strategy `FullPrivacy` when a Unified Address is present as the
|
||||||
|
sender or a recipient, and otherwise preserves existing behaviour (which
|
||||||
|
corresponds to the `AllowFullyTransparent` policy).
|
||||||
|
|
||||||
- Since Sprout outputs are no longer created (with the exception of change)
|
- Since Sprout outputs are no longer created (with the exception of change)
|
||||||
'z_sendmany' no longer generates payment disclosures (which were only
|
'z_sendmany' no longer generates payment disclosures (which were only
|
||||||
|
|
|
@ -0,0 +1,538 @@
|
||||||
|
Notable changes
|
||||||
|
===============
|
||||||
|
|
||||||
|
Mnemonic Recovery Phrases
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
The zcashd wallet has been modified to support BIP 39, which describes how to
|
||||||
|
derive the wallet's HD seed from a mnemonic phrase. The mnemonic phrase will
|
||||||
|
be generated on load of the wallet, or the first time the wallet is unlocked,
|
||||||
|
and is available via the `z_exportwallet` RPC call. All new addresses produced
|
||||||
|
by the wallet are now derived from this seed using the HD wallet functionality
|
||||||
|
described in ZIP 32 and ZIP 316. For users upgrading an existing Zcashd wallet,
|
||||||
|
it is recommended that the wallet be backed up prior to upgrading to the 4.7.0
|
||||||
|
Zcashd release.
|
||||||
|
|
||||||
|
Following the upgrade to 4.7.0, Zcashd will require that the user confirm that
|
||||||
|
they have backed up their new emergency recovery phrase, which may be obtained
|
||||||
|
from the output of the `z_exportwallet` RPC call. This confirmation can be
|
||||||
|
performed manually using the `zcashd-wallet-tool` utility that is supplied
|
||||||
|
with this release (built or installed in the same directory as `zcashd`).
|
||||||
|
The wallet will not allow the generation of new addresses until this
|
||||||
|
confirmation has been performed. It is recommended that after this upgrade,
|
||||||
|
that funds tied to preexisting addresses be migrated to newly generated
|
||||||
|
addresses so that all wallet funds are recoverable using the emergency
|
||||||
|
recovery phrase going forward. If you choose not to migrate funds in this
|
||||||
|
fashion, you will continue to need to securely back up the entire `wallet.dat`
|
||||||
|
file to ensure that you do not lose access to existing funds; EXISTING FUNDS
|
||||||
|
WILL NOT BE RECOVERABLE USING THE EMERGENCY RECOVERY PHRASE UNLESS THEY HAVE
|
||||||
|
BEEN MOVED TO A NEWLY GENERATED ADDRESS FOLLOWING THE 4.7.0 UPGRADE.
|
||||||
|
|
||||||
|
New RPC Methods
|
||||||
|
---------------
|
||||||
|
|
||||||
|
- 'walletconfirmbackup' This newly created API checks a provided emergency
|
||||||
|
recovery phrase against the wallet's emergency recovery phrase; if the phrases
|
||||||
|
match then it updates the wallet state to allow the generation of new addresses.
|
||||||
|
This backup confirmation workflow can be disabled by starting zcashd with
|
||||||
|
`-requirewalletbackup=false` but this is not recommended unless you know what
|
||||||
|
you're doing (and have otherwise backed up the wallet's recovery phrase anyway).
|
||||||
|
For security reasons, this RPC method is not intended for use via zcash-cli
|
||||||
|
but is provided to enable `zcashd-wallet-tool` and other third-party wallet
|
||||||
|
interfaces to satisfy the backup confirmation requirement. Use of the
|
||||||
|
`walletconfirmbackup` API via zcash-cli would risk that the recovery phrase
|
||||||
|
being confirmed might be leaked via the user's shell history or the system
|
||||||
|
process table; `zcashd-wallet-tool` is specifically provided to avoid this
|
||||||
|
problem.
|
||||||
|
- 'z_getbalanceforviewingkey' This newly created API allows a user to obtain
|
||||||
|
balance information for funds visible to a Sapling or Unified full
|
||||||
|
viewing key; if a Sprout viewing key is provided, this method allows
|
||||||
|
retrieval of the balance only in the case that the wallet controls the
|
||||||
|
corresponding spending key.
|
||||||
|
|
||||||
|
RPC Changes
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- The results of the 'dumpwallet' and 'z_exportwallet' RPC methods have been modified
|
||||||
|
to now include the wallet's newly generated emergency recovery phrase as part of the
|
||||||
|
exported data.
|
||||||
|
|
||||||
|
- The results of the 'getwalletinfo' RPC have been modified to return two new fields:
|
||||||
|
`mnemonic_seedfp` and `legacy_seedfp`, the latter of which replaces the field that
|
||||||
|
was previously named `seedfp`.
|
||||||
|
|
||||||
|
Wallet
|
||||||
|
------
|
||||||
|
|
||||||
|
'z_sendmany'
|
||||||
|
------------
|
||||||
|
|
||||||
|
- The 'z_sendmany' RPC call no longer permits Sprout recipients in the
|
||||||
|
list of recipient addresses. Transactions spending Sprout funds will
|
||||||
|
still result in change being sent back into the Sprout pool, but no
|
||||||
|
other `Sprout->Sprout` transactions will be constructed by the Zcashd
|
||||||
|
wallet.
|
||||||
|
|
||||||
|
- The restriction that prohibited `Sprout->Sapling` transactions has been
|
||||||
|
lifted; however, since such transactions reveal the amount crossing
|
||||||
|
pool boundaries, they must be explicitly enabled via a parameter to
|
||||||
|
the 'z_sendmany' call.
|
||||||
|
|
||||||
|
- A new string parameter, `privacyPolicy`, has been added to the list of
|
||||||
|
arguments accepted by `z_sendmany`. This parameter enables the caller to
|
||||||
|
control what kind of information they permit `zcashd` to reveal when creating
|
||||||
|
the transaction. If the transaction can only be created by revealing more
|
||||||
|
information than the given strategy permits, `z_sendmany` will return an
|
||||||
|
error. The parameter defaults to `LegacyCompat`, which applies the most
|
||||||
|
restrictive strategy `FullPrivacy` when a Unified Address is present as the
|
||||||
|
sender or a recipient, and otherwise preserves existing behaviour (which
|
||||||
|
corresponds to the `AllowFullyTransparent` policy).
|
||||||
|
|
||||||
|
- Since Sprout outputs are no longer created (with the exception of change)
|
||||||
|
'z_sendmany' no longer generates payment disclosures (which were only
|
||||||
|
available for Sprout outputs) when the `-paymentdisclosure` experimental
|
||||||
|
feature flag is set.
|
||||||
|
|
||||||
|
Changelog
|
||||||
|
=========
|
||||||
|
|
||||||
|
Charlie O'Keefe (2):
|
||||||
|
Update base image used by Dockerfile from debian 10 to debian 11
|
||||||
|
Remove stretch (debian 9), add bullseye (debian 11) in gitian descriptor
|
||||||
|
|
||||||
|
Conrado Gouvea (1):
|
||||||
|
Add comment to zcash_script_new_precomputed_tx about references to input buffers
|
||||||
|
|
||||||
|
Daira Hopwood (25):
|
||||||
|
Avoid a warning by explicitly calling drop.
|
||||||
|
Replace call to drop with zeroization.
|
||||||
|
contrib/devtools/rust-deps-graph.sh: allow overriding the arguments to `cargo deps`.
|
||||||
|
Cosmetic whitespace change
|
||||||
|
Make a zcashd-wallet-tool executable.
|
||||||
|
Attempt to fix linking problem on ARM.
|
||||||
|
Add some text about choosing location of the physical backup. Add TODO for better handling of file not found and permission errors.
|
||||||
|
Move `wallet_tool.rs` from `src/rust/src` into `src/rust/bin`. Also add a brief description of `zcashd-wallet-tool` to `src/rust/README.md`.
|
||||||
|
Improved error handling.
|
||||||
|
The recovery phrase confirmation and `zcashd-wallet-tool` are being introduced in zcashd v4.7.0.
|
||||||
|
Refactor use of `export_path` as suggested.
|
||||||
|
Tweak the wording of the fallback messages when the terminal cannot be automatically cleared.
|
||||||
|
Simplify extraction of recovery phrase.
|
||||||
|
Improve memory hygiene, and use -stdin to pass the recovery phrase to the child zcash-cli process.
|
||||||
|
Cleanups to error handling for the first invocation of zcash-cli.
|
||||||
|
Use the tracing crate for debugging.
|
||||||
|
Improve error message when the config file cannot be found, taking into account -conf and -datadir.
|
||||||
|
Ensure the buffer used in `prompt` is zeroized even on error.
|
||||||
|
Document that '~' cannot be used in `-datadir` (see #5661).
|
||||||
|
Set `meta` for `-datadir` option.
|
||||||
|
Simplify debug tracing.
|
||||||
|
Correct the fallback instruction for how to clear the terminal on macOS: pressing Command + K also clears scrollback.
|
||||||
|
Include $(bin_SCRIPTS) in `check-symbols`, `check-security`, and `clean` targets. Checking for stack canaries in `check-security` is disabled for Rust executables (Rust does support `-Z stack-protector=all` but only for the nightly compiler).
|
||||||
|
qa/zcash/full_test_suite.py: enable `test_rpath_runpath` for Rust binaries, and reenable `test_fortify_source` for C++ binaries.
|
||||||
|
Tweaks to message text.
|
||||||
|
|
||||||
|
Dimitris Apostolou (1):
|
||||||
|
Fix typos
|
||||||
|
|
||||||
|
Jack Grigg (65):
|
||||||
|
wallet: Implement `z_getnewaccount`
|
||||||
|
wallet: Implement `z_getaddressforaccount`
|
||||||
|
wallet: Implement `z_listunifiedreceivers`
|
||||||
|
wallet: Show UAs owned by the wallet in `z_viewtransaction`
|
||||||
|
wallet: Reverse order of arguments to z_getaddressforaccount
|
||||||
|
rust: Add missing Orchard receiver check in UA parsing
|
||||||
|
rust: Add missing checks for shielded UFVK items
|
||||||
|
rust: Add missing checks for transparent UFVK items
|
||||||
|
wallet: Implement `z_getbalanceforaccount`
|
||||||
|
wallet: Fix account generation bug
|
||||||
|
wallet: Implement `z_getbalanceforaddress`
|
||||||
|
wallet: Don't show Sapling receivers from UAs in `z_listaddresses`
|
||||||
|
wallet: Show UAs instead of Sapling receivers in `z_listunspent`
|
||||||
|
wallet: Remove `CWallet::GetKeyFromPool`
|
||||||
|
wallet: Store internal transparent keys in the keypool
|
||||||
|
wallet: Separate counters for external and internal transparent keys
|
||||||
|
Add Orchard recipient support to the transaction builder
|
||||||
|
Make `TransactionBuilder::AddOrchardOutput` memo optional
|
||||||
|
Return UFVK from `CWallet::GenerateNewUnifiedSpendingKey`
|
||||||
|
Rename to `ZcashdUnifiedSpendingKey::GetSaplingKey` for consistency
|
||||||
|
Trial-decrypt candidate Sapling receivers with all wallet UFVKs
|
||||||
|
Add mappings from Orchard receivers to IVKs to the wallet
|
||||||
|
Add Orchard to default UA receiver types
|
||||||
|
Fix semantic merge conflicts
|
||||||
|
Select Orchard receivers preferentially from UAs
|
||||||
|
Regenerate `TxDigests` after rebuilding tx with Orchard bundle
|
||||||
|
qa: Bump all postponed dependencies
|
||||||
|
qa: Postpone recent CCache release
|
||||||
|
depends: Update Rust to 1.59.0
|
||||||
|
depends: Update Clang / libcxx to LLVM 13.0.1
|
||||||
|
cargo update
|
||||||
|
rust: Fix clippy lint
|
||||||
|
Ensure the view's best Orchard anchor matches the previous block
|
||||||
|
Add missing `view.PopAnchor(_, ORCHARD)` in `DisconnectBlock`
|
||||||
|
Add RPC test for the Orchard commitment tree bug on first NU5 testnet
|
||||||
|
Use `std::optional` in `CValidationInterface::GetAddressForMining`
|
||||||
|
Select Orchard receivers from UA miner addresses once NU5 activates
|
||||||
|
miner: Manually add dummy Orchard output with Orchard miner address
|
||||||
|
rpc: Handle keypool exhaustion separately in `generate` RPC
|
||||||
|
depends: Revert to `libc++ 13.0.0-3` for Windows cross-compile
|
||||||
|
Add Orchard spend support to the transaction builder
|
||||||
|
wallet: Alter `OrchardWallet::GetSpendInfo` to return the spending key
|
||||||
|
Improve FFI interface documentation
|
||||||
|
Refactor `CWallet::GenerateChangeAddressForAccount`
|
||||||
|
Add unit tests for `SpendableInputs::LimitToAmount`
|
||||||
|
Fix bug in `SpendableInputs::LimitToAmount`
|
||||||
|
Select spendable inputs based on recipient and change pool types
|
||||||
|
Implement opportunistic shielding in `SpendableInputs::LimitToAmount`
|
||||||
|
Add Orchard cases to note selection logic
|
||||||
|
Add Orchard to change address generation
|
||||||
|
Add support for sending Orchard funds in `z_sendmany`
|
||||||
|
Set default Orchard anchor confirmations to 1
|
||||||
|
build: Fix `zcash/address/orchard.hpp` filename in `src/Makefile.am`
|
||||||
|
z_sendmany: Replace `allowRevealedAmount` with `privacyStrategy`
|
||||||
|
z_sendmany: Expand `privacyPolicy` cases
|
||||||
|
build: Add missing `util/match.h` to `src/Makefile.am`
|
||||||
|
wallet: Add seedfp to transparent keys in dumpwallet / z_exportwallet
|
||||||
|
Fix bugs in wallet_addresses RPC test
|
||||||
|
wallet: Fix bugs in `listaddresses`
|
||||||
|
wallet: Fix Sapling address bug in `listaddresses`
|
||||||
|
wallet: Fix expected `listaddresses` sources in `rpc_wallet_tests`
|
||||||
|
qa: Exclude `native_libtinfo` from dependency update checks
|
||||||
|
cargo update
|
||||||
|
make-release.py: Versioning changes for 4.7.0-rc1.
|
||||||
|
make-release.py: Updated manpages for 4.7.0-rc1.
|
||||||
|
|
||||||
|
John Newbery (1):
|
||||||
|
Log calls to getblocktemplate
|
||||||
|
|
||||||
|
Jonas Schnelli (1):
|
||||||
|
[Wallet] add HD xpriv to dumpwallet
|
||||||
|
|
||||||
|
Kris Nuttycombe (243):
|
||||||
|
Derive random HD seeds from ZIP-339 seed phrases.
|
||||||
|
Add support for externally searching for valid Sapling diversifiers.
|
||||||
|
Adds basic unified spending key derivation.
|
||||||
|
Add unified full viewing keys and Zcash-internal unified addresses.
|
||||||
|
Use the default UA-based Sapling address for the saplingmigration tool.
|
||||||
|
Fix tests for wallet operations on legacy Sapling keys.
|
||||||
|
Remove unused forward declaration.
|
||||||
|
Update librustzcash dependency version.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Derive transparent keys from mnemonic seed.
|
||||||
|
Generate legacy Sapling addresses from the mnemonic seed.
|
||||||
|
Replace account ID with ZIP-0032 key path in listaddresses output.
|
||||||
|
Use legacy address for Sapling migration until UA functionality is available to the RPC tests.
|
||||||
|
Revert change to the type of GenerateNewKey
|
||||||
|
Fix wallet import/export test
|
||||||
|
Use 0x7FFFFFFF for the legacy account ID.
|
||||||
|
Require backup of the emergency recovery phrase.
|
||||||
|
Use hardened derivation for the legacy Sapling key at the address index level.
|
||||||
|
Address comments from code review.
|
||||||
|
Restore legacy HD seed storage & retrieval tests.
|
||||||
|
Fix spurious test passage.
|
||||||
|
Move CKeyMetadata back to wallet.h
|
||||||
|
Clean up format of recovery information in the wallet dump.
|
||||||
|
Use SecureString for mnemonic phrase.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Fix diversifier_index_t less than operator.
|
||||||
|
Restore FindAddress comment
|
||||||
|
Fix transparent BIP-44 keypaths.
|
||||||
|
Fix naming of unified spending & full viewing keys
|
||||||
|
Fix max transparent diversifier index.
|
||||||
|
Clean up handling of mnemonic seed metadata.
|
||||||
|
Restore legacy HDSeed encryption tests.
|
||||||
|
Style fix in BIP 32 path account parsing test.
|
||||||
|
Do not strip quotes when verifying mnemonic seed.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Fix name of menmonic entropy length constant.
|
||||||
|
Fix polymorphism of string serialization.
|
||||||
|
Document mnemonic-seed-related RPC method changes & update changelog.
|
||||||
|
Minor cleanup of the code that searches for a valid transparent key.
|
||||||
|
Generalize keypath parsing over account id.
|
||||||
|
Update documentation for GenerateNewSeed.
|
||||||
|
Use MnemonicSeed instead of HDSeed where appropriate.
|
||||||
|
Add diversifier_index_t::ToTransparentChildIndex
|
||||||
|
Only maintain CKeyID for the transparent part of ZcashdUnifiedAddress
|
||||||
|
Minor naming fixes
|
||||||
|
Parameterize HD keypath parsing by coin type.
|
||||||
|
Fix error message text to refer to zcashd-wallet-tool rather than the RPC method.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Minor textual fixes to release notes.
|
||||||
|
Improve documentation of the `-walletrequirebackup` zcashd wallet configuration option.
|
||||||
|
Add libzcash::AccountId type.
|
||||||
|
Adds Orchard Address, IncomingViewingKey, FullViewingKey, and SpendingKey types.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Update orchard & librustzcash dependency versions.
|
||||||
|
Remove incorrect FFI method documentation.
|
||||||
|
Remove Orchard spending key equality implementation.
|
||||||
|
Refine structure of Zcash address generation.
|
||||||
|
Use CKeyID and CScriptID instead of new P2PKH/P2SHAddress classes.
|
||||||
|
Remove ZcashdUnifiedAddress in favor of UnifiedAddress
|
||||||
|
Update to ufvk zcash_address build.
|
||||||
|
Adds SaplingDiversifiableFullViewingKey
|
||||||
|
Add Rust FFI components for unified full viewing keys.
|
||||||
|
Add UnifiedFullViewingKey type.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Add tests for ufvk roundtrip serialization.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Apply suggestions from code review
|
||||||
|
Apply suggestions from code review
|
||||||
|
Add functions for generating BIP-44 and ZIP-32 keypaths
|
||||||
|
Check the output of zip339_phrase_to_seed in MnemonicSeed initialization.
|
||||||
|
Compute key id for UFVKs.
|
||||||
|
Add ZcashdUnifiedKeyMetadata and libzcash::ReceiverType
|
||||||
|
Add unified key components to the transparent & Sapling wallet parts.
|
||||||
|
Store ufvks to the wallet database.
|
||||||
|
Add unified address tracking to KeyStore
|
||||||
|
Load unified full viewing keys from the walletdb.
|
||||||
|
Add key metadata to walletdb.
|
||||||
|
Add unified address generation.
|
||||||
|
AddTransparentSecretKey does not need to take a CExtKey
|
||||||
|
Add newly generated transparent UA receivers to the wallet.
|
||||||
|
Add CWallet::GetUnifiedForReceiver
|
||||||
|
Add tests for keystore storage and retrieval of UFVKs.
|
||||||
|
Add test for wallet UA generation & detection.
|
||||||
|
Add test for CKeyStore::AddUnifiedAddress
|
||||||
|
Fix handling of unified full viewing key metadata.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Only derive ZcashdUnifiedFullViewingKey from UnifiedFullViewingKey
|
||||||
|
Rename `ZcashdUnifiedSpendingKeyMetadata` -> `ZcashdUnifiedAccount`
|
||||||
|
Remove unused ufvkid argument from AddTransparentSecretKey
|
||||||
|
Ensure that unified address metadata is always correctly populated.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Make `FindAddress` correctly check for maximum transparent child index.
|
||||||
|
Use Bip44TransparentAccountKeyPath() for Bip44AccountChains keypath construction.
|
||||||
|
Improve documentation of UFVK/UA metadata storage semantics.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Fix encoding order of unified addresses.
|
||||||
|
Remove the `InvalidEncoding` type from key & address variants.
|
||||||
|
Use CKeyID and CScriptID instead of new P2PKH/P2SHAddress classes.
|
||||||
|
Remove spurious uses of HaveSpendingKeyForPaymentAddress
|
||||||
|
Add raw transparent address types to PaymentAddress
|
||||||
|
Remove spurious variant from asyncrpcoperation_sendmany
|
||||||
|
Remove `RawAddress`
|
||||||
|
Replace `DecodeDestination` in `GetMinerAddress` with `DecodePaymentAddress`
|
||||||
|
Remove uses of KeyIO::DecodeDestination
|
||||||
|
Remove a use of KeyIO::DecodeDestination in z_shieldcoinbase
|
||||||
|
Use libzcash::PaymentAddress instead of std::string in mergetoaddress
|
||||||
|
Improve error messages in the case of taddr decoding failure.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Apply suggestions from code review
|
||||||
|
Use transaction builder for asyncrpcoperation_sendmany.
|
||||||
|
Transaction builder must not set consensus branch ID for V4 transactions.
|
||||||
|
Fix conditions around dust thresholds.
|
||||||
|
Require an explicit flag to allow cross-pool transfers in z_sendmany.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Return z_sendmany errors synchronously when possible.
|
||||||
|
Update release notes to reflect z_sendmany changes
|
||||||
|
Move FindSpendableInputs to CWallet
|
||||||
|
Replace the badly-named `PaymentSource` with `ZTXOSelector`
|
||||||
|
Add support for unified addresses to CWallet::ToZTXOSelector
|
||||||
|
Replace `HaveSpendingKeyForAddress` with checks at ZTXOSelector construction.
|
||||||
|
Modify CWallet::FindSpendableInputs to use ZTXOSelector
|
||||||
|
Add CWallet::FindAccountForSelector
|
||||||
|
Add RecipientAddress type.
|
||||||
|
Use libzcash::RecipientAddress for z_sendmany recipients.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Rename ZTXOSelector::SpendingKeysAvailable -> RequireSpendingKeys
|
||||||
|
Make `FindSpendableInputs` respect `ZTXOSelector::RequireSpendingKeys()`
|
||||||
|
Clarify documentation of CWallet::FindAccountForSelector
|
||||||
|
Use `ZTXOSelector::IncludesSapling` rather than selector internals.
|
||||||
|
Add documentation for `UnifiedAddress::GetPreferredRecipientAddress`
|
||||||
|
Add correct selection of change addresses to z_sendmany
|
||||||
|
Add a first-class type for transparent full viewing keys.
|
||||||
|
Implement OVK selection for z_sendmany.
|
||||||
|
Implement derivation of the internal Sapling spending key.
|
||||||
|
Use a BIP 44 change address for change when sending from legacy t-addrs.
|
||||||
|
Add a check for internal vs. external outputs to wallet_listreceived test.
|
||||||
|
Fix z_sendmany handling of transparent OVK derivation in the ANY_TADDR case.
|
||||||
|
Simplify determination of valid change types.
|
||||||
|
Add failing tests for z_sendmany ANY_TADDR -> UA and UA -> <legacy_taddr>
|
||||||
|
Add gtest for change address derivation.
|
||||||
|
Fix variable shadowing in change address derivation & add change IVK to the keystore.
|
||||||
|
GenerateLegacySaplingZKey only needs to return an address, not an extfvk.
|
||||||
|
Rename AddSaplingIncomingViewingKey -> AddSaplingPaymentAddress
|
||||||
|
Do not add Sapling addresses to the wallet by default when adding extfvks.
|
||||||
|
Fix a bug in the generation of addresses from UFVKs
|
||||||
|
Add accessor method for Sapling IVKs to SaplingDiversifiableFullViewingKey
|
||||||
|
Address TODOs in rpc-tests/wallet-accounts.py
|
||||||
|
Add a few additional cases to z_sendmany RPC tests.
|
||||||
|
Update librustzcash dependency.
|
||||||
|
Fix nondeterministic test error (checking for the wrong error case).
|
||||||
|
Use z_shieldcoinbase for Sprout funds in wallet_listreceived tests.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Apply suggestions from code review.
|
||||||
|
Fix nondeterministic test failure in wallet_listreceivedby.py
|
||||||
|
Fix locking in z_getbalanceforaddress and z_getbalanceforaccount
|
||||||
|
Replace z_getbalanceforaddress with z_getbalanceforviewingkey
|
||||||
|
Clarify documentation of z_getbalanceforviewingkey for Sprout viewing keys.
|
||||||
|
Implement PaymentAddressBelongsToWallet for unified addresses.
|
||||||
|
Add unified address support to GetSourceForPaymentAddress
|
||||||
|
Address comments from review.
|
||||||
|
Add change field to z_listreceivedbyaddress for transparent addrs.
|
||||||
|
Fix missing std::variant header that was breaking Darwin builds.
|
||||||
|
Add test vectors for UFVK derivation
|
||||||
|
Rename sapling-specific zip32 FFI methods.
|
||||||
|
Make SaveRecipientMappings polymorphic in RecipientMapping type.
|
||||||
|
Add Rust backend for Orchard components of the wallet.
|
||||||
|
Add GetFilteredNotes to Orchard wallet.
|
||||||
|
Add test for Orchard wallet note detection.
|
||||||
|
Move parsing of unified addresses to UnifiedAddress.
|
||||||
|
Remove txid field from TxNotes
|
||||||
|
Apply suggestions from code review
|
||||||
|
Add various bits of documentation
|
||||||
|
Add Orchard components to unified address
|
||||||
|
Add Orchard components to unified full viewing keys
|
||||||
|
Add Orchard components to unified spending keys
|
||||||
|
Remove OrchardSpendingKey serialization code
|
||||||
|
Select Orchard notes in FindSpendableInputs
|
||||||
|
GenerateNewKey must be guarded by a cs_wallet lock
|
||||||
|
Filter returned Orchard notes by minimum confirmations.
|
||||||
|
Log outpoint for failed Sapling witness lookup.
|
||||||
|
Add a roundtrip test for Orchard merkle frontier serialization from the C++ side.
|
||||||
|
Add test for Orchard contribution to z_gettotalbalance
|
||||||
|
Respect minDepth argument for Orchard notes in GetFilteredNotes
|
||||||
|
Update MSRV for lints.
|
||||||
|
Update incrementalmerkletree version
|
||||||
|
Split LoadWalletTx from AddToWallet
|
||||||
|
Fix missing locks for GenerateNewUnifiedSpendingKey tests.
|
||||||
|
Record when notes are detected as being spent in the Orchard wallet.
|
||||||
|
Reset Orchard wallet state when rescanning from below NU5 activation.
|
||||||
|
Check wallet latest anchor against hashFinalOrchardRoot in ChainTip.
|
||||||
|
Remove assertions that require Orchard wallet persistence to satisfy.
|
||||||
|
Add a test for Orchard note detection.
|
||||||
|
Assert we never attempt to checkpoint the Orchard wallet at a negative block height.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Add an `InPoint` type to the Orchard wallet to fix incorrect conflict data.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Fix missing update to `last_checkpoint` on rewind.
|
||||||
|
Track mined-ness instead of spent-ness of notes in the Orchard wallet.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Respect maxDepth for Orchard notes in GetFilteredNotes
|
||||||
|
Set number of confirmations for Orchard notes returned by FindSpendableInputs
|
||||||
|
Update walletTx with decrypted Orchard action metadata.
|
||||||
|
Persist Orchard action index/IVK mappings in CWalletTx
|
||||||
|
Restore decrypted notes to the wallet.
|
||||||
|
Update tests with rollback checks.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Restore mined block heights when restoring decrypted notes.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Return std::optional<CExtKey> from CExtKey::Master
|
||||||
|
Ensure that Orchard spentness information is repopulated by LoadUnifiedCaches.
|
||||||
|
Modify `join_network` in tests to skip mempool resync.
|
||||||
|
Address suggestions from code review on #5637
|
||||||
|
Apply suggestions from code review
|
||||||
|
Serialize the Orchard note commitment tree to the wallet.
|
||||||
|
Update orchard_wallet_add_notes_from_bundle documentation.
|
||||||
|
Derive the new mnemonic seed from the legacy HD seed, if one is available.
|
||||||
|
Fix indentation.
|
||||||
|
Apply suggestions from code review
|
||||||
|
Explicitly specify the change address in the Sapling migration.
|
||||||
|
Add TODO for Orchard z_listunspent integration.
|
||||||
|
Document that z_getnewaccount and z_getaddressforaccount replace z_getnewaddress.
|
||||||
|
Add orchard support to z_getnotescount
|
||||||
|
Document that Orchard keys are not supported in z_importkey help
|
||||||
|
Return the correct error for `z_getbalance` if a UA does not correspond to an account in the wallet..
|
||||||
|
Update documentation of z_importviewingkey and z_exportviewingkey to address unified keys.
|
||||||
|
Add UnifiedAddress variant to ZTXOSelector
|
||||||
|
Eliminate redundancy between the wallet and the keystore.
|
||||||
|
Simplify retrieval of unified account by address.
|
||||||
|
Add OrchardWallet::GetTxActions
|
||||||
|
Apply suggestions from code review
|
||||||
|
Update z_viewtransaction documentation to correctly represent field names.
|
||||||
|
Add debug printing for receivers and recipient addresses.
|
||||||
|
Correctly report change outputs in z_viewtransaction.
|
||||||
|
Return the default unified address if we have the UFVK but no address metadata.
|
||||||
|
Fix missing AllowRevealedSenders flag in test.
|
||||||
|
Uncomment addtional tests that depend on z_viewtransaction.
|
||||||
|
Minor rename & documentation improvement.
|
||||||
|
Add RecipientType to GetPaymentAddressForRecipient result.
|
||||||
|
Make CWallet::DefaultReceiverTypes height-dependent.
|
||||||
|
Return failure rather than asserting on WriteRecipientMapping failure.
|
||||||
|
Lock cs_main for accesses to chainActive in GetPaymentAddressForRecipient.
|
||||||
|
Fix legacy address handling in CWallet::GetPaymentAddressForRecipient
|
||||||
|
Documentation fix for UnifiedAddressForReciever (+ spelling corrections.)
|
||||||
|
|
||||||
|
Larry Ruane (12):
|
||||||
|
add ParseArbitraryInt() for diversifier index
|
||||||
|
add -orchardwallet experimental feature flag
|
||||||
|
Add new and modify existing Orchard RPCs, non-functional
|
||||||
|
mining: submitblock: log detailed equihash solution error
|
||||||
|
allow UA as z_shieldcoinbase destination
|
||||||
|
fix minconf parsing for z_getbalanceforaccount and z_getbalanceforaddress
|
||||||
|
Update z_listreceivedbyaddress to support unified addresses (5467)
|
||||||
|
fix wallet_listreceived.py, add blockdata to taddr output
|
||||||
|
z_listreceivedbyaddress: reject UA component addr (#5537)
|
||||||
|
add functional test
|
||||||
|
document global variables
|
||||||
|
update listaddresses RPC for UAs, Orchard
|
||||||
|
|
||||||
|
Marius Kjærstad (1):
|
||||||
|
Update copyright year to 2022
|
||||||
|
|
||||||
|
Pieter Wuille (2):
|
||||||
|
Fix csBestBlock/cvBlockChange waiting in rpc/mining
|
||||||
|
Modernize best block mutex/cv/hash variable naming
|
||||||
|
|
||||||
|
Sean Bowe (5):
|
||||||
|
wallet: consolidate unified key/address/account map reconstruction
|
||||||
|
wallet: restore Orchard secret keys from mnemonic seed
|
||||||
|
wallet: restore orchard address to IVK mappings during wallet loading
|
||||||
|
wallet: rather than assert, error in case of inconsistency between FVK and address
|
||||||
|
wallet: add logging for failure cases in unified cache loading
|
||||||
|
|
||||||
|
Steven Smith (8):
|
||||||
|
Lock cs_main prior to calling blockToJSON
|
||||||
|
Mark z_gettotalbalance and dumpwallet as deprecated
|
||||||
|
Add Orchard support to the z_gettreestate RPC
|
||||||
|
Update transaction size estimation to include V5 transactions
|
||||||
|
Extend uniqueness check in z_sendmany to UA receivers
|
||||||
|
Load previously persisted sent transaction recipient metadata back into the wallet.
|
||||||
|
Add Orchard & unified address support to z_viewtransaction.
|
||||||
|
Ensure z_viewtransaction returns Orchard details
|
||||||
|
|
||||||
|
Taylor Hornby (1):
|
||||||
|
Untested, not working yet, use libtinfo from the debian packages
|
||||||
|
|
||||||
|
sasha (12):
|
||||||
|
on Arch only, use Debian's libtinfo5_6.0 to satisfy clang
|
||||||
|
explain the 0x0f0f[..]0f0f powLimit constant for regtest
|
||||||
|
remove superfluous space at end of native_packages line
|
||||||
|
gtests ordering: change wallet filename in WriteZkeyDirectToDb
|
||||||
|
gtests ordering: ContextualCheckBlockTest's TearDown deactivates Blossom
|
||||||
|
gtests ordering: CheckBlock.VersionTooLow calls SelectParams(MAIN)
|
||||||
|
implement AtomicTimer::zeroize() that resets start_time and total_time
|
||||||
|
gtests ordering: make Metrics.GetLocalSolPS idempotent
|
||||||
|
gtests ordering: clean up wallet files before each WalletZkeysTest
|
||||||
|
make librustzcash_init_zksnark_params idempotent
|
||||||
|
move proof parameter loading out of gtest/main.cpp and into utiltest.cpp
|
||||||
|
Call LoadProofParameters() in gtests that need proofs
|
||||||
|
|
||||||
|
Ying Tong Lai (18):
|
||||||
|
Move SendManyRecipient to wallet.h and introduce optional ua field.
|
||||||
|
SendTransaction: Introduce recipients argument.
|
||||||
|
Implement read and write for (txid, recipient) -> ua mapping.
|
||||||
|
z_sendmany: Only get ua if decoded is ua variant.
|
||||||
|
ShieldToAddress: Factor out static shieldToAddress() helper.
|
||||||
|
Docfixes.
|
||||||
|
CSerializeRecipientAddress: add Read method and make constructor private.
|
||||||
|
WriteRecipientMapping: Check that receiver exists in UA.
|
||||||
|
wallet_sendmany_any_taddr.py: Test sending from a change taddr.
|
||||||
|
wallet_sendmany_any_taddr.py: Test sending output from expired tx.
|
||||||
|
FindSpendableInputs: Add nDepth < 0 check.
|
||||||
|
wallet_sendmany_any_taddr.py: Expect expired tx to be ignored.
|
||||||
|
Orchard: invalidate mempool transactions that use orphaned anchors.
|
||||||
|
coins_tests.cpp: Add Orchard nullifier to TxWithNullifiers().
|
||||||
|
coins_tests: Update tests to include Orchard case.
|
||||||
|
CWallet::GetConflictS: Handle conflicting Orchard spends.
|
||||||
|
z_getbalance: Handle Unified Address case.
|
||||||
|
Adapt RPC tests to use z_getbalance for UAs.
|
||||||
|
|
||||||
|
ying tong (2):
|
||||||
|
Apply docfixes from code review
|
||||||
|
Style improvements in RPC tests.
|
||||||
|
|
||||||
|
Zancas Wilcox (4):
|
||||||
|
blake2b/s is integrated into hashlib, drop external python package dependency
|
||||||
|
update doctest in gtest suite to prefer hashlib
|
||||||
|
enforce usage of the get_tests comptool interface as ComparisonTestFramework method
|
||||||
|
All implementations of ComparisonTestFramework were overriding num_nodes
|
||||||
|
|
|
@ -60,6 +60,7 @@ BASE_SCRIPTS= [
|
||||||
'wallet_persistence.py',
|
'wallet_persistence.py',
|
||||||
'wallet_listnotes.py',
|
'wallet_listnotes.py',
|
||||||
# vv Tests less than 60s vv
|
# vv Tests less than 60s vv
|
||||||
|
'orchard_reorg.py',
|
||||||
'fundrawtransaction.py',
|
'fundrawtransaction.py',
|
||||||
'reorg_limit.py',
|
'reorg_limit.py',
|
||||||
'mempool_limit.py',
|
'mempool_limit.py',
|
||||||
|
@ -72,6 +73,7 @@ BASE_SCRIPTS= [
|
||||||
'wallet_changeindicator.py',
|
'wallet_changeindicator.py',
|
||||||
'wallet_import_export.py',
|
'wallet_import_export.py',
|
||||||
'wallet_isfromme.py',
|
'wallet_isfromme.py',
|
||||||
|
'wallet_orchard.py',
|
||||||
'wallet_nullifiers.py',
|
'wallet_nullifiers.py',
|
||||||
'wallet_sapling.py',
|
'wallet_sapling.py',
|
||||||
'wallet_sendmany_any_taddr.py',
|
'wallet_sendmany_any_taddr.py',
|
||||||
|
|
|
@ -6,9 +6,11 @@
|
||||||
|
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import (
|
from test_framework.util import (
|
||||||
|
NU5_BRANCH_ID,
|
||||||
assert_equal,
|
assert_equal,
|
||||||
connect_nodes_bi,
|
connect_nodes_bi,
|
||||||
get_coinbase_address,
|
get_coinbase_address,
|
||||||
|
nuparams,
|
||||||
start_nodes,
|
start_nodes,
|
||||||
wait_and_assert_operationid_status,
|
wait_and_assert_operationid_status,
|
||||||
)
|
)
|
||||||
|
@ -17,6 +19,7 @@ from decimal import Decimal
|
||||||
|
|
||||||
SPROUT_TREE_EMPTY_ROOT = "59d2cde5e65c1414c32ba54f0fe4bdb3d67618125286e6a191317917c812c6d7"
|
SPROUT_TREE_EMPTY_ROOT = "59d2cde5e65c1414c32ba54f0fe4bdb3d67618125286e6a191317917c812c6d7"
|
||||||
SAPLING_TREE_EMPTY_ROOT = "3e49b5f954aa9d3545bc6c37744661eea48d7c34e3000d82b7f0010c30f4c2fb"
|
SAPLING_TREE_EMPTY_ROOT = "3e49b5f954aa9d3545bc6c37744661eea48d7c34e3000d82b7f0010c30f4c2fb"
|
||||||
|
ORCHARD_TREE_EMPTY_ROOT = "2fd8e51a03d9bbe2dd809831b1497aeb68a6e37ddf707ced4aa2d8dff13529ae"
|
||||||
NULL_FIELD = "0000000000000000000000000000000000000000000000000000000000000000"
|
NULL_FIELD = "0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
|
||||||
# Verify block header field 'hashFinalSaplingRoot' (returned in rpc as 'finalsaplingroot')
|
# Verify block header field 'hashFinalSaplingRoot' (returned in rpc as 'finalsaplingroot')
|
||||||
|
@ -25,17 +28,16 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.num_nodes = 4
|
self.num_nodes = 2
|
||||||
self.setup_clean_chain = True
|
self.setup_clean_chain = True
|
||||||
|
|
||||||
def setup_network(self, split=False):
|
def setup_network(self, split=False):
|
||||||
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args=[[
|
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args=[[
|
||||||
'-txindex' # Avoid JSONRPC error: No information available about transaction
|
'-txindex', # Avoid JSONRPC error: No information available about transaction
|
||||||
|
nuparams(NU5_BRANCH_ID, 210),
|
||||||
|
'-debug',
|
||||||
]] * self.num_nodes)
|
]] * self.num_nodes)
|
||||||
connect_nodes_bi(self.nodes,0,1)
|
connect_nodes_bi(self.nodes,0,1)
|
||||||
connect_nodes_bi(self.nodes,1,2)
|
|
||||||
connect_nodes_bi(self.nodes,0,2)
|
|
||||||
connect_nodes_bi(self.nodes,0,3)
|
|
||||||
self.is_network_split=False
|
self.is_network_split=False
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
|
|
||||||
|
@ -52,13 +54,19 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
||||||
|
|
||||||
assert_equal(treestate["sprout"]["commitments"]["finalRoot"], SPROUT_TREE_EMPTY_ROOT)
|
assert_equal(treestate["sprout"]["commitments"]["finalRoot"], SPROUT_TREE_EMPTY_ROOT)
|
||||||
assert_equal(treestate["sprout"]["commitments"]["finalState"], "000000")
|
assert_equal(treestate["sprout"]["commitments"]["finalState"], "000000")
|
||||||
assert("skipHash" not in treestate["sprout"])
|
assert "skipHash" not in treestate["sprout"]
|
||||||
|
|
||||||
assert_equal(treestate["sapling"]["commitments"]["finalRoot"], NULL_FIELD)
|
assert_equal(treestate["sapling"]["commitments"]["finalRoot"], NULL_FIELD)
|
||||||
# There is no sapling state tree yet, and trying to find it in an earlier
|
# There is no sapling state tree yet, and trying to find it in an earlier
|
||||||
# block won't succeed (we're at genesis block), so skipHash is absent.
|
# block won't succeed (we're at genesis block), so skipHash is absent.
|
||||||
assert("finalState" not in treestate["sapling"])
|
assert "finalState" not in treestate["sapling"]
|
||||||
assert("skipHash" not in treestate["sapling"])
|
assert "skipHash" not in treestate["sapling"]
|
||||||
|
|
||||||
|
assert_equal(treestate["orchard"]["commitments"]["finalRoot"], NULL_FIELD)
|
||||||
|
# There is no orchard state tree yet, and trying to find it in an earlier
|
||||||
|
# block won't succeed (we're at genesis block), so skipHash is absent.
|
||||||
|
assert "finalState" not in treestate["orchard"]
|
||||||
|
assert "skipHash" not in treestate["orchard"]
|
||||||
|
|
||||||
# Verify all generated blocks contain the empty root of the Sapling tree.
|
# Verify all generated blocks contain the empty root of the Sapling tree.
|
||||||
blockcount = self.nodes[0].getblockcount()
|
blockcount = self.nodes[0].getblockcount()
|
||||||
|
@ -70,14 +78,18 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
||||||
assert_equal(treestate["height"], height)
|
assert_equal(treestate["height"], height)
|
||||||
assert_equal(treestate["hash"], self.nodes[0].getblockhash(height))
|
assert_equal(treestate["hash"], self.nodes[0].getblockhash(height))
|
||||||
|
|
||||||
assert("skipHash" not in treestate["sprout"])
|
assert "skipHash" not in treestate["sprout"]
|
||||||
assert_equal(treestate["sprout"]["commitments"]["finalRoot"], SPROUT_TREE_EMPTY_ROOT)
|
assert_equal(treestate["sprout"]["commitments"]["finalRoot"], SPROUT_TREE_EMPTY_ROOT)
|
||||||
assert_equal(treestate["sprout"]["commitments"]["finalState"], "000000")
|
assert_equal(treestate["sprout"]["commitments"]["finalState"], "000000")
|
||||||
|
|
||||||
assert("skipHash" not in treestate["sapling"])
|
|
||||||
|
assert "skipHash" not in treestate["sapling"]
|
||||||
assert_equal(treestate["sapling"]["commitments"]["finalRoot"], SAPLING_TREE_EMPTY_ROOT)
|
assert_equal(treestate["sapling"]["commitments"]["finalRoot"], SAPLING_TREE_EMPTY_ROOT)
|
||||||
assert_equal(treestate["sapling"]["commitments"]["finalState"], "000000")
|
assert_equal(treestate["sapling"]["commitments"]["finalState"], "000000")
|
||||||
|
|
||||||
|
assert "skipHash" not in treestate["orchard"]
|
||||||
|
assert_equal(treestate["orchard"]["commitments"]["finalRoot"], NULL_FIELD)
|
||||||
|
|
||||||
# Node 0 shields some funds
|
# Node 0 shields some funds
|
||||||
taddr0 = get_coinbase_address(self.nodes[0])
|
taddr0 = get_coinbase_address(self.nodes[0])
|
||||||
saplingAddr0 = self.nodes[0].z_getnewaddress('sapling')
|
saplingAddr0 = self.nodes[0].z_getnewaddress('sapling')
|
||||||
|
@ -93,8 +105,8 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
||||||
# Verify the final Sapling root has changed
|
# Verify the final Sapling root has changed
|
||||||
blk = self.nodes[0].getblock("201")
|
blk = self.nodes[0].getblock("201")
|
||||||
root = blk["finalsaplingroot"]
|
root = blk["finalsaplingroot"]
|
||||||
assert(root is not SAPLING_TREE_EMPTY_ROOT)
|
assert root is not SAPLING_TREE_EMPTY_ROOT
|
||||||
assert(root is not NULL_FIELD)
|
assert root is not NULL_FIELD
|
||||||
|
|
||||||
# Verify there is a Sapling output description (its commitment was added to tree)
|
# Verify there is a Sapling output description (its commitment was added to tree)
|
||||||
result = self.nodes[0].getrawtransaction(mytxid, 1)
|
result = self.nodes[0].getrawtransaction(mytxid, 1)
|
||||||
|
@ -105,8 +117,8 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
||||||
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
||||||
assert_equal(new_treestate["sapling"]["commitments"]["finalRoot"], root)
|
assert_equal(new_treestate["sapling"]["commitments"]["finalRoot"], root)
|
||||||
assert_equal(new_treestate["sprout"], treestate["sprout"])
|
assert_equal(new_treestate["sprout"], treestate["sprout"])
|
||||||
assert(new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"])
|
assert new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"]
|
||||||
assert(new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"])
|
assert new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"]
|
||||||
assert_equal(len(new_treestate["sapling"]["commitments"]["finalRoot"]), 64)
|
assert_equal(len(new_treestate["sapling"]["commitments"]["finalRoot"]), 64)
|
||||||
assert_equal(len(new_treestate["sapling"]["commitments"]["finalState"]), 70)
|
assert_equal(len(new_treestate["sapling"]["commitments"]["finalState"]), 70)
|
||||||
treestate = new_treestate
|
treestate = new_treestate
|
||||||
|
@ -145,8 +157,8 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
||||||
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
||||||
assert_equal(new_treestate["sapling"]["commitments"]["finalRoot"], root)
|
assert_equal(new_treestate["sapling"]["commitments"]["finalRoot"], root)
|
||||||
assert_equal(new_treestate["sapling"], treestate["sapling"])
|
assert_equal(new_treestate["sapling"], treestate["sapling"])
|
||||||
assert(new_treestate["sprout"]["commitments"]["finalRoot"] != treestate["sprout"]["commitments"]["finalRoot"])
|
assert new_treestate["sprout"]["commitments"]["finalRoot"] != treestate["sprout"]["commitments"]["finalRoot"]
|
||||||
assert(new_treestate["sprout"]["commitments"]["finalState"] != treestate["sprout"]["commitments"]["finalState"])
|
assert new_treestate["sprout"]["commitments"]["finalState"] != treestate["sprout"]["commitments"]["finalState"]
|
||||||
assert_equal(len(new_treestate["sprout"]["commitments"]["finalRoot"]), 64)
|
assert_equal(len(new_treestate["sprout"]["commitments"]["finalRoot"]), 64)
|
||||||
assert_equal(len(new_treestate["sprout"]["commitments"]["finalState"]), 134)
|
assert_equal(len(new_treestate["sprout"]["commitments"]["finalState"]), 134)
|
||||||
treestate = new_treestate
|
treestate = new_treestate
|
||||||
|
@ -164,7 +176,7 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
||||||
|
|
||||||
assert_equal(len(self.nodes[0].getblock("205")["tx"]), 2)
|
assert_equal(len(self.nodes[0].getblock("205")["tx"]), 2)
|
||||||
assert_equal(self.nodes[1].z_getbalance(saplingAddr1), Decimal("12.34"))
|
assert_equal(self.nodes[1].z_getbalance(saplingAddr1), Decimal("12.34"))
|
||||||
assert(root is not self.nodes[0].getblock("205")["finalsaplingroot"])
|
assert root is not self.nodes[0].getblock("205")["finalsaplingroot"]
|
||||||
|
|
||||||
# Verify there is a Sapling output description (its commitment was added to tree)
|
# Verify there is a Sapling output description (its commitment was added to tree)
|
||||||
result = self.nodes[0].getrawtransaction(mytxid, 1)
|
result = self.nodes[0].getrawtransaction(mytxid, 1)
|
||||||
|
@ -172,8 +184,8 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
||||||
|
|
||||||
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
||||||
assert_equal(new_treestate["sprout"], treestate["sprout"])
|
assert_equal(new_treestate["sprout"], treestate["sprout"])
|
||||||
assert(new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"])
|
assert new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"]
|
||||||
assert(new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"])
|
assert new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"]
|
||||||
assert_equal(len(new_treestate["sapling"]["commitments"]["finalRoot"]), 64)
|
assert_equal(len(new_treestate["sapling"]["commitments"]["finalRoot"]), 64)
|
||||||
assert_equal(len(new_treestate["sapling"]["commitments"]["finalState"]), 136)
|
assert_equal(len(new_treestate["sapling"]["commitments"]["finalState"]), 136)
|
||||||
treestate = new_treestate
|
treestate = new_treestate
|
||||||
|
@ -200,6 +212,21 @@ class FinalSaplingRootTest(BitcoinTestFramework):
|
||||||
assert_equal(new_treestate["sprout"], treestate["sprout"])
|
assert_equal(new_treestate["sprout"], treestate["sprout"])
|
||||||
assert_equal(new_treestate["sapling"], treestate["sapling"])
|
assert_equal(new_treestate["sapling"], treestate["sapling"])
|
||||||
|
|
||||||
|
# Activate NU5; more testing should be added once we can mine orchard transactions.
|
||||||
|
self.sync_all()
|
||||||
|
self.nodes[0].generate(4)
|
||||||
|
self.sync_all()
|
||||||
|
new_treestate = self.nodes[0].z_gettreestate(str(-1))
|
||||||
|
|
||||||
|
# sprout and sapling results should not change
|
||||||
|
assert_equal(new_treestate["sprout"], treestate["sprout"])
|
||||||
|
assert_equal(new_treestate["sapling"], treestate["sapling"])
|
||||||
|
|
||||||
|
assert "skipHash" not in treestate["orchard"]
|
||||||
|
assert_equal(new_treestate["orchard"]["commitments"]["finalRoot"], ORCHARD_TREE_EMPTY_ROOT)
|
||||||
|
assert_equal(new_treestate["orchard"]["commitments"]["finalState"], "00")
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
FinalSaplingRootTest().main()
|
FinalSaplingRootTest().main()
|
||||||
|
|
|
@ -6,10 +6,12 @@
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from test_framework.authproxy import JSONRPCException
|
from test_framework.authproxy import JSONRPCException
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.mininode import nuparams
|
from test_framework.mininode import COIN, nuparams
|
||||||
from test_framework.util import (
|
from test_framework.util import (
|
||||||
BLOSSOM_BRANCH_ID,
|
BLOSSOM_BRANCH_ID,
|
||||||
HEARTWOOD_BRANCH_ID,
|
HEARTWOOD_BRANCH_ID,
|
||||||
|
CANOPY_BRANCH_ID,
|
||||||
|
NU5_BRANCH_ID,
|
||||||
assert_equal,
|
assert_equal,
|
||||||
assert_raises,
|
assert_raises,
|
||||||
bitcoind_processes,
|
bitcoind_processes,
|
||||||
|
@ -28,8 +30,12 @@ class ShieldCoinbaseTest (BitcoinTestFramework):
|
||||||
|
|
||||||
def start_node_with(self, index, extra_args=[]):
|
def start_node_with(self, index, extra_args=[]):
|
||||||
args = [
|
args = [
|
||||||
|
'-experimentalfeatures',
|
||||||
|
'-orchardwallet',
|
||||||
nuparams(BLOSSOM_BRANCH_ID, 1),
|
nuparams(BLOSSOM_BRANCH_ID, 1),
|
||||||
nuparams(HEARTWOOD_BRANCH_ID, 10),
|
nuparams(HEARTWOOD_BRANCH_ID, 10),
|
||||||
|
nuparams(CANOPY_BRANCH_ID, 20),
|
||||||
|
nuparams(NU5_BRANCH_ID, 20),
|
||||||
"-nurejectoldversions=false",
|
"-nurejectoldversions=false",
|
||||||
]
|
]
|
||||||
return start_node(index, self.options.tmpdir, args + extra_args)
|
return start_node(index, self.options.tmpdir, args + extra_args)
|
||||||
|
@ -117,5 +123,50 @@ class ShieldCoinbaseTest (BitcoinTestFramework):
|
||||||
assert_equal(self.nodes[0].z_getbalance(node0_taddr), 2)
|
assert_equal(self.nodes[0].z_getbalance(node0_taddr), 2)
|
||||||
assert_equal(self.nodes[1].z_getbalance(node1_zaddr), 1)
|
assert_equal(self.nodes[1].z_getbalance(node1_zaddr), 1)
|
||||||
|
|
||||||
|
# Generate a Unified Address for node 1
|
||||||
|
self.nodes[1].z_getnewaccount()
|
||||||
|
node1_addr0 = self.nodes[1].z_getaddressforaccount(0)
|
||||||
|
assert_equal(node1_addr0['account'], 0)
|
||||||
|
assert_equal(set(node1_addr0['pools']), set(['transparent', 'sapling', 'orchard']))
|
||||||
|
node1_ua = node1_addr0['unifiedaddress']
|
||||||
|
|
||||||
|
# Set node 1's miner address to the UA
|
||||||
|
self.nodes[1].stop()
|
||||||
|
bitcoind_processes[1].wait()
|
||||||
|
self.nodes[1] = self.start_node_with(1, [
|
||||||
|
"-mineraddress=%s" % node1_ua,
|
||||||
|
])
|
||||||
|
connect_nodes(self.nodes[1], 0)
|
||||||
|
|
||||||
|
# The UA starts with zero balance.
|
||||||
|
assert_equal(self.nodes[1].z_getbalanceforaccount(0)['pools'], {})
|
||||||
|
|
||||||
|
# Node 1 can mine blocks because the miner selects the Sapling receiver
|
||||||
|
# of its UA.
|
||||||
|
print("Mining block with node 1")
|
||||||
|
self.nodes[1].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# The UA balance should show that Sapling funds were received.
|
||||||
|
assert_equal(self.nodes[1].z_getbalanceforaccount(0)['pools'], {
|
||||||
|
'sapling': {'valueZat': 5 * COIN },
|
||||||
|
})
|
||||||
|
|
||||||
|
# Activate NU5
|
||||||
|
print("Activating NU5")
|
||||||
|
self.nodes[0].generate(7)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# Now any block mined by node 1 should use the Orchard receiver of its UA.
|
||||||
|
print("Mining block with node 1")
|
||||||
|
self.nodes[1].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
assert_equal(self.nodes[1].z_getbalanceforaccount(0)['pools'], {
|
||||||
|
'sapling': {'valueZat': 5 * COIN },
|
||||||
|
# 6.25 ZEC because the FR always ends when Canopy activates, and
|
||||||
|
# regtest has no defined funding streams.
|
||||||
|
'orchard': {'valueZat': 6.25 * COIN },
|
||||||
|
})
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
ShieldCoinbaseTest().main()
|
ShieldCoinbaseTest().main()
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2022 The Zcash developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||||
|
|
||||||
|
#
|
||||||
|
# Test the effect of reorgs on the Orchard commitment tree.
|
||||||
|
#
|
||||||
|
|
||||||
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
|
from test_framework.util import (
|
||||||
|
BLOSSOM_BRANCH_ID,
|
||||||
|
HEARTWOOD_BRANCH_ID,
|
||||||
|
CANOPY_BRANCH_ID,
|
||||||
|
NU5_BRANCH_ID,
|
||||||
|
assert_equal,
|
||||||
|
get_coinbase_address,
|
||||||
|
nuparams,
|
||||||
|
start_nodes,
|
||||||
|
wait_and_assert_operationid_status,
|
||||||
|
)
|
||||||
|
|
||||||
|
from finalsaplingroot import ORCHARD_TREE_EMPTY_ROOT
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
class OrchardReorgTest(BitcoinTestFramework):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.num_nodes = 4
|
||||||
|
self.setup_clean_chain = True
|
||||||
|
|
||||||
|
def setup_nodes(self):
|
||||||
|
return start_nodes(self.num_nodes, self.options.tmpdir, extra_args=[[
|
||||||
|
nuparams(BLOSSOM_BRANCH_ID, 1),
|
||||||
|
nuparams(HEARTWOOD_BRANCH_ID, 5),
|
||||||
|
nuparams(CANOPY_BRANCH_ID, 5),
|
||||||
|
nuparams(NU5_BRANCH_ID, 10),
|
||||||
|
'-nurejectoldversions=false',
|
||||||
|
'-experimentalfeatures',
|
||||||
|
'-orchardwallet',
|
||||||
|
# '-debug',
|
||||||
|
]] * self.num_nodes)
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
# Activate NU5 so we can test Orchard.
|
||||||
|
self.nodes[0].generate(10)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# Generate a UA with only an Orchard receiver.
|
||||||
|
account = self.nodes[0].z_getnewaccount()['account']
|
||||||
|
addr = self.nodes[0].z_getaddressforaccount(account, ['orchard'])
|
||||||
|
assert_equal(addr['account'], account)
|
||||||
|
assert_equal(set(addr['pools']), set(['orchard']))
|
||||||
|
ua = addr['unifiedaddress']
|
||||||
|
|
||||||
|
# Before mining any Orchard notes, finalorchardroot should be the empty Orchard root.
|
||||||
|
assert_equal(
|
||||||
|
ORCHARD_TREE_EMPTY_ROOT,
|
||||||
|
self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot'],
|
||||||
|
)
|
||||||
|
|
||||||
|
# finalorchardroot should not change if we mine additional blocks without Orchard notes.
|
||||||
|
self.nodes[0].generate(100)
|
||||||
|
self.sync_all()
|
||||||
|
assert_equal(
|
||||||
|
ORCHARD_TREE_EMPTY_ROOT,
|
||||||
|
self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot'],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create an Orchard note.
|
||||||
|
recipients = [{'address': ua, 'amount': Decimal('12.5')}]
|
||||||
|
opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders')
|
||||||
|
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||||
|
|
||||||
|
# After mining a block, finalorchardroot should have changed.
|
||||||
|
self.sync_all()
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
orchardroot_oneleaf = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot']
|
||||||
|
print("Root of Orchard commitment tree with one leaf:", orchardroot_oneleaf)
|
||||||
|
assert(orchardroot_oneleaf != ORCHARD_TREE_EMPTY_ROOT)
|
||||||
|
|
||||||
|
# finalorchardroot should not change if we mine additional blocks without Orchard notes.
|
||||||
|
self.nodes[0].generate(4)
|
||||||
|
self.sync_all()
|
||||||
|
assert_equal(
|
||||||
|
orchardroot_oneleaf,
|
||||||
|
self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot'],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Split the network so we can test the effect of a reorg.
|
||||||
|
print("Splitting the network")
|
||||||
|
self.split_network()
|
||||||
|
|
||||||
|
# Create another Orchard note on node 0.
|
||||||
|
recipients = [{'address': ua, 'amount': Decimal('12.5')}]
|
||||||
|
opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders')
|
||||||
|
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||||
|
|
||||||
|
# Mine two blocks on node 0.
|
||||||
|
print("Mining 2 blocks on node 0")
|
||||||
|
self.nodes[0].generate(2)
|
||||||
|
self.sync_all()
|
||||||
|
orchardroot_twoleaf = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['finalorchardroot']
|
||||||
|
print("Root of Orchard commitment tree with two leaves:", orchardroot_twoleaf)
|
||||||
|
assert(orchardroot_twoleaf != ORCHARD_TREE_EMPTY_ROOT)
|
||||||
|
assert(orchardroot_twoleaf != orchardroot_oneleaf)
|
||||||
|
|
||||||
|
# Generate 10 blocks on node 2.
|
||||||
|
print("Mining alternate chain on node 2")
|
||||||
|
self.nodes[2].generate(10)
|
||||||
|
self.sync_all()
|
||||||
|
assert_equal(
|
||||||
|
orchardroot_oneleaf,
|
||||||
|
self.nodes[2].getblock(self.nodes[2].getbestblockhash())['finalorchardroot'],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Reconnect the nodes; node 0 will re-org to node 2's chain.
|
||||||
|
print("Re-joining the network so that node 0 reorgs")
|
||||||
|
self.join_network()
|
||||||
|
|
||||||
|
# Verify that node 0's latest Orchard root matches what we expect.
|
||||||
|
orchardroot_postreorg = self.nodes[0].getblock(self.nodes[2].getbestblockhash())['finalorchardroot']
|
||||||
|
print("Root of Orchard commitment tree after reorg:", orchardroot_postreorg)
|
||||||
|
assert_equal(orchardroot_postreorg, orchardroot_oneleaf)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
OrchardReorgTest().main()
|
|
@ -52,7 +52,7 @@ SPROUT_PROTO_VERSION = 170002 # past bip-31 for ping/pong
|
||||||
OVERWINTER_PROTO_VERSION = 170003
|
OVERWINTER_PROTO_VERSION = 170003
|
||||||
SAPLING_PROTO_VERSION = 170006
|
SAPLING_PROTO_VERSION = 170006
|
||||||
BLOSSOM_PROTO_VERSION = 170008
|
BLOSSOM_PROTO_VERSION = 170008
|
||||||
NU5_PROTO_VERSION = 170015
|
NU5_PROTO_VERSION = 170040
|
||||||
|
|
||||||
MY_SUBVERSION = b"/python-mininode-tester:0.0.3/"
|
MY_SUBVERSION = b"/python-mininode-tester:0.0.3/"
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ class BitcoinTestFramework(object):
|
||||||
def setup_nodes(self):
|
def setup_nodes(self):
|
||||||
return start_nodes(self.num_nodes, self.options.tmpdir)
|
return start_nodes(self.num_nodes, self.options.tmpdir)
|
||||||
|
|
||||||
def setup_network(self, split = False):
|
def setup_network(self, split = False, do_mempool_sync = True):
|
||||||
self.nodes = self.setup_nodes()
|
self.nodes = self.setup_nodes()
|
||||||
|
|
||||||
# Connect the nodes as a "chain". This allows us
|
# Connect the nodes as a "chain". This allows us
|
||||||
|
@ -64,12 +64,13 @@ class BitcoinTestFramework(object):
|
||||||
if not split:
|
if not split:
|
||||||
connect_nodes_bi(self.nodes, 1, 2)
|
connect_nodes_bi(self.nodes, 1, 2)
|
||||||
sync_blocks(self.nodes[1:3])
|
sync_blocks(self.nodes[1:3])
|
||||||
sync_mempools(self.nodes[1:3])
|
if do_mempool_sync:
|
||||||
|
sync_mempools(self.nodes[1:3])
|
||||||
|
|
||||||
connect_nodes_bi(self.nodes, 0, 1)
|
connect_nodes_bi(self.nodes, 0, 1)
|
||||||
connect_nodes_bi(self.nodes, 2, 3)
|
connect_nodes_bi(self.nodes, 2, 3)
|
||||||
self.is_network_split = split
|
self.is_network_split = split
|
||||||
self.sync_all()
|
self.sync_all(do_mempool_sync)
|
||||||
|
|
||||||
def split_network(self):
|
def split_network(self):
|
||||||
"""
|
"""
|
||||||
|
@ -80,15 +81,17 @@ class BitcoinTestFramework(object):
|
||||||
wait_bitcoinds()
|
wait_bitcoinds()
|
||||||
self.setup_network(True)
|
self.setup_network(True)
|
||||||
|
|
||||||
def sync_all(self):
|
def sync_all(self, do_mempool_sync = True):
|
||||||
if self.is_network_split:
|
if self.is_network_split:
|
||||||
sync_blocks(self.nodes[:2])
|
sync_blocks(self.nodes[:2])
|
||||||
sync_blocks(self.nodes[2:])
|
sync_blocks(self.nodes[2:])
|
||||||
sync_mempools(self.nodes[:2])
|
if do_mempool_sync:
|
||||||
sync_mempools(self.nodes[2:])
|
sync_mempools(self.nodes[:2])
|
||||||
|
sync_mempools(self.nodes[2:])
|
||||||
else:
|
else:
|
||||||
sync_blocks(self.nodes)
|
sync_blocks(self.nodes)
|
||||||
sync_mempools(self.nodes)
|
if do_mempool_sync:
|
||||||
|
sync_mempools(self.nodes)
|
||||||
|
|
||||||
def join_network(self):
|
def join_network(self):
|
||||||
"""
|
"""
|
||||||
|
@ -97,7 +100,7 @@ class BitcoinTestFramework(object):
|
||||||
assert self.is_network_split
|
assert self.is_network_split
|
||||||
stop_nodes(self.nodes)
|
stop_nodes(self.nodes)
|
||||||
wait_bitcoinds()
|
wait_bitcoinds()
|
||||||
self.setup_network(False)
|
self.setup_network(False, False)
|
||||||
|
|
||||||
def main(self):
|
def main(self):
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ SAPLING_BRANCH_ID = 0x76B809BB
|
||||||
BLOSSOM_BRANCH_ID = 0x2BB40E60
|
BLOSSOM_BRANCH_ID = 0x2BB40E60
|
||||||
HEARTWOOD_BRANCH_ID = 0xF5B9230B
|
HEARTWOOD_BRANCH_ID = 0xF5B9230B
|
||||||
CANOPY_BRANCH_ID = 0xE9FF75A6
|
CANOPY_BRANCH_ID = 0xE9FF75A6
|
||||||
NU5_BRANCH_ID = 0x37519621
|
NU5_BRANCH_ID = 0xC2D6D0B4
|
||||||
|
|
||||||
# The maximum number of nodes a single test can spawn
|
# The maximum number of nodes a single test can spawn
|
||||||
MAX_NODES = 8
|
MAX_NODES = 8
|
||||||
|
|
|
@ -7,9 +7,12 @@ from test_framework.authproxy import JSONRPCException
|
||||||
from test_framework.mininode import COIN
|
from test_framework.mininode import COIN
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import (
|
from test_framework.util import (
|
||||||
|
NU5_BRANCH_ID,
|
||||||
assert_equal,
|
assert_equal,
|
||||||
assert_raises_message,
|
assert_raises_message,
|
||||||
|
assert_true,
|
||||||
get_coinbase_address,
|
get_coinbase_address,
|
||||||
|
nuparams,
|
||||||
start_nodes,
|
start_nodes,
|
||||||
wait_and_assert_operationid_status,
|
wait_and_assert_operationid_status,
|
||||||
)
|
)
|
||||||
|
@ -22,6 +25,7 @@ class WalletAccountsTest(BitcoinTestFramework):
|
||||||
return start_nodes(self.num_nodes, self.options.tmpdir, [[
|
return start_nodes(self.num_nodes, self.options.tmpdir, [[
|
||||||
'-experimentalfeatures',
|
'-experimentalfeatures',
|
||||||
'-orchardwallet',
|
'-orchardwallet',
|
||||||
|
nuparams(NU5_BRANCH_ID, 210),
|
||||||
]] * self.num_nodes)
|
]] * self.num_nodes)
|
||||||
|
|
||||||
def check_receiver_types(self, ua, expected):
|
def check_receiver_types(self, ua, expected):
|
||||||
|
@ -32,14 +36,19 @@ class WalletAccountsTest(BitcoinTestFramework):
|
||||||
# Remember that empty pools are omitted from the output.
|
# Remember that empty pools are omitted from the output.
|
||||||
def _check_balance_for_rpc(self, rpcmethod, node, account, expected, minconf):
|
def _check_balance_for_rpc(self, rpcmethod, node, account, expected, minconf):
|
||||||
rpc = getattr(self.nodes[node], rpcmethod)
|
rpc = getattr(self.nodes[node], rpcmethod)
|
||||||
actual = rpc(account) if minconf is None else rpc(account, minconf)
|
actual = rpc(account, minconf)
|
||||||
assert_equal(set(expected), set(actual['pools']))
|
assert_equal(set(expected), set(actual['pools']))
|
||||||
|
total_balance = 0
|
||||||
for pool in expected:
|
for pool in expected:
|
||||||
assert_equal(expected[pool] * COIN, actual['pools'][pool]['valueZat'])
|
assert_equal(expected[pool] * COIN, actual['pools'][pool]['valueZat'])
|
||||||
assert_equal(actual['minimum_confirmations'], 1 if minconf is None else minconf)
|
total_balance += expected[pool]
|
||||||
|
assert_equal(actual['minimum_confirmations'], minconf)
|
||||||
|
return total_balance
|
||||||
|
|
||||||
def check_balance(self, node, account, address, expected, minconf=None):
|
def check_balance(self, node, account, address, expected, minconf=1):
|
||||||
self._check_balance_for_rpc('z_getbalanceforaccount', node, account, expected, minconf)
|
acct_balance = self._check_balance_for_rpc('z_getbalanceforaccount', node, account, expected, minconf)
|
||||||
|
z_getbalance = self.nodes[node].z_getbalance(address, minconf)
|
||||||
|
assert_equal(acct_balance, z_getbalance)
|
||||||
fvk = self.nodes[node].z_exportviewingkey(address)
|
fvk = self.nodes[node].z_exportviewingkey(address)
|
||||||
self._check_balance_for_rpc('z_getbalanceforviewingkey', node, fvk, expected, minconf)
|
self._check_balance_for_rpc('z_getbalanceforviewingkey', node, fvk, expected, minconf)
|
||||||
|
|
||||||
|
@ -55,7 +64,7 @@ class WalletAccountsTest(BitcoinTestFramework):
|
||||||
# Generate the first address for account 0.
|
# Generate the first address for account 0.
|
||||||
addr0 = self.nodes[0].z_getaddressforaccount(0)
|
addr0 = self.nodes[0].z_getaddressforaccount(0)
|
||||||
assert_equal(addr0['account'], 0)
|
assert_equal(addr0['account'], 0)
|
||||||
assert_equal(set(addr0['pools']), set(['transparent', 'sapling']))
|
assert_equal(set(addr0['pools']), set(['transparent', 'sapling', 'orchard']))
|
||||||
ua0 = addr0['unifiedaddress']
|
ua0 = addr0['unifiedaddress']
|
||||||
|
|
||||||
# We pick mnemonic phrases to ensure that we can always generate the default
|
# We pick mnemonic phrases to ensure that we can always generate the default
|
||||||
|
@ -70,24 +79,47 @@ class WalletAccountsTest(BitcoinTestFramework):
|
||||||
'no address at diversifier index 0',
|
'no address at diversifier index 0',
|
||||||
self.nodes[0].z_getaddressforaccount, 0, [], 0)
|
self.nodes[0].z_getaddressforaccount, 0, [], 0)
|
||||||
|
|
||||||
|
# The second address for account 0 is different to the first address.
|
||||||
|
addr0_2 = self.nodes[0].z_getaddressforaccount(0)
|
||||||
|
assert_equal(addr0_2['account'], 0)
|
||||||
|
assert_equal(set(addr0_2['pools']), set(['transparent', 'sapling', 'orchard']))
|
||||||
|
ua0_2 = addr0_2['unifiedaddress']
|
||||||
|
assert(ua0 != ua0_2)
|
||||||
|
|
||||||
|
# We can generate a fully-shielded address.
|
||||||
|
addr0_3 = self.nodes[0].z_getaddressforaccount(0, ['sapling', 'orchard'])
|
||||||
|
assert_equal(addr0_3['account'], 0)
|
||||||
|
assert_equal(set(addr0_3['pools']), set(['sapling', 'orchard']))
|
||||||
|
ua0_3 = addr0_3['unifiedaddress']
|
||||||
|
|
||||||
|
# We can generate an address without a Sapling receiver.
|
||||||
|
addr0_4 = self.nodes[0].z_getaddressforaccount(0, ['transparent', 'orchard'])
|
||||||
|
assert_equal(addr0_4['account'], 0)
|
||||||
|
assert_equal(set(addr0_4['pools']), set(['transparent', 'orchard']))
|
||||||
|
ua0_4 = addr0_4['unifiedaddress']
|
||||||
|
|
||||||
# The first address for account 1 is different to account 0.
|
# The first address for account 1 is different to account 0.
|
||||||
addr1 = self.nodes[0].z_getaddressforaccount(1)
|
addr1 = self.nodes[0].z_getaddressforaccount(1)
|
||||||
assert_equal(addr1['account'], 1)
|
assert_equal(addr1['account'], 1)
|
||||||
assert_equal(set(addr1['pools']), set(['transparent', 'sapling']))
|
assert_equal(set(addr1['pools']), set(['transparent', 'sapling', 'orchard']))
|
||||||
ua1 = addr1['unifiedaddress']
|
ua1 = addr1['unifiedaddress']
|
||||||
assert(ua0 != ua1)
|
assert(ua0 != ua1)
|
||||||
|
|
||||||
# The UA contains the expected receiver kinds.
|
# The UA contains the expected receiver kinds.
|
||||||
self.check_receiver_types(ua0, ['transparent', 'sapling'])
|
self.check_receiver_types(ua0, ['transparent', 'sapling', 'orchard'])
|
||||||
self.check_receiver_types(ua1, ['transparent', 'sapling'])
|
self.check_receiver_types(ua0_2, ['transparent', 'sapling', 'orchard'])
|
||||||
|
self.check_receiver_types(ua0_3, [ 'sapling', 'orchard'])
|
||||||
|
self.check_receiver_types(ua0_4, ['transparent', 'orchard'])
|
||||||
|
self.check_receiver_types(ua1, ['transparent', 'sapling', 'orchard'])
|
||||||
|
|
||||||
# The balances of the accounts are all zero.
|
# The balances of the accounts are all zero.
|
||||||
self.check_balance(0, 0, ua0, {})
|
self.check_balance(0, 0, ua0, {})
|
||||||
self.check_balance(0, 1, ua1, {})
|
self.check_balance(0, 1, ua1, {})
|
||||||
|
|
||||||
# Manually send funds to one of the receivers in the UA.
|
# Send coinbase funds to the UA.
|
||||||
|
print('Sending coinbase funds to account')
|
||||||
recipients = [{'address': ua0, 'amount': Decimal('10')}]
|
recipients = [{'address': ua0, 'amount': Decimal('10')}]
|
||||||
opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0)
|
opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders')
|
||||||
txid = wait_and_assert_operationid_status(self.nodes[0], opid)
|
txid = wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||||
|
|
||||||
# The wallet should detect the new note as belonging to the UA.
|
# The wallet should detect the new note as belonging to the UA.
|
||||||
|
@ -108,7 +140,8 @@ class WalletAccountsTest(BitcoinTestFramework):
|
||||||
# The default minconf should now detect the balance.
|
# The default minconf should now detect the balance.
|
||||||
self.check_balance(0, 0, ua0, {'sapling': 10})
|
self.check_balance(0, 0, ua0, {'sapling': 10})
|
||||||
|
|
||||||
# Manually send funds from the UA receiver.
|
# Send Sapling funds from the UA.
|
||||||
|
print('Sending account funds to Sapling address')
|
||||||
node1sapling = self.nodes[1].z_getnewaddress('sapling')
|
node1sapling = self.nodes[1].z_getnewaddress('sapling')
|
||||||
recipients = [{'address': node1sapling, 'amount': Decimal('1')}]
|
recipients = [{'address': node1sapling, 'amount': Decimal('1')}]
|
||||||
opid = self.nodes[0].z_sendmany(ua0, recipients, 1, 0)
|
opid = self.nodes[0].z_sendmany(ua0, recipients, 1, 0)
|
||||||
|
@ -128,6 +161,69 @@ class WalletAccountsTest(BitcoinTestFramework):
|
||||||
self.check_balance(0, 0, ua0, {})
|
self.check_balance(0, 0, ua0, {})
|
||||||
self.check_balance(0, 0, ua0, {'sapling': 9}, 0)
|
self.check_balance(0, 0, ua0, {'sapling': 9}, 0)
|
||||||
|
|
||||||
|
# Activate NU5
|
||||||
|
print('Activating NU5')
|
||||||
|
self.nodes[2].generate(9)
|
||||||
|
self.sync_all()
|
||||||
|
assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 210)
|
||||||
|
|
||||||
|
# Send more coinbase funds to the UA.
|
||||||
|
print('Sending coinbase funds to account')
|
||||||
|
recipients = [{'address': ua0, 'amount': Decimal('10')}]
|
||||||
|
opid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders')
|
||||||
|
txid = wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||||
|
|
||||||
|
# The wallet should detect the new note as belonging to the UA.
|
||||||
|
tx_details = self.nodes[0].z_viewtransaction(txid)
|
||||||
|
assert_equal(len(tx_details['outputs']), 1)
|
||||||
|
assert_equal(tx_details['outputs'][0]['type'], 'orchard')
|
||||||
|
assert_equal(tx_details['outputs'][0]['address'], ua0)
|
||||||
|
|
||||||
|
# The new balance should not be visible with the default minconf, but should be
|
||||||
|
# visible with minconf=0.
|
||||||
|
self.sync_all()
|
||||||
|
self.check_balance(0, 0, ua0, {'sapling': 9})
|
||||||
|
self.check_balance(0, 0, ua0, {'sapling': 9, 'orchard': 10}, 0)
|
||||||
|
|
||||||
|
# The total balance with the default minconf should be just the Sapling balance
|
||||||
|
assert_equal('9.00', self.nodes[0].z_gettotalbalance()['private'])
|
||||||
|
assert_equal('19.00', self.nodes[0].z_gettotalbalance(0)['private'])
|
||||||
|
|
||||||
|
self.nodes[2].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# Send Orchard funds from the UA.
|
||||||
|
print('Sending account funds to Orchard-only UA')
|
||||||
|
node1account = self.nodes[1].z_getnewaccount()['account']
|
||||||
|
node1orchard = self.nodes[1].z_getaddressforaccount(node1account, ['orchard'])['unifiedaddress']
|
||||||
|
recipients = [{'address': node1orchard, 'amount': Decimal('1')}]
|
||||||
|
opid = self.nodes[0].z_sendmany(ua0, recipients, 1, 0)
|
||||||
|
txid = wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||||
|
|
||||||
|
# The wallet should detect the spent note as belonging to the UA.
|
||||||
|
tx_details = self.nodes[0].z_viewtransaction(txid)
|
||||||
|
assert_equal(len(tx_details['spends']), 1)
|
||||||
|
assert_equal(tx_details['spends'][0]['type'], 'orchard')
|
||||||
|
assert_equal(tx_details['spends'][0]['address'], ua0)
|
||||||
|
|
||||||
|
assert_equal(len(tx_details['outputs']), 2)
|
||||||
|
outputs = sorted(tx_details['outputs'], key=lambda x: x['valueZat'])
|
||||||
|
assert_equal(outputs[0]['type'], 'orchard')
|
||||||
|
assert_equal(outputs[0]['address'], node1orchard)
|
||||||
|
assert_equal(outputs[0]['valueZat'], 100000000)
|
||||||
|
# outputs[1] is change
|
||||||
|
assert_equal(outputs[1]['type'], 'orchard')
|
||||||
|
assert_true('address' not in outputs[1]) #
|
||||||
|
|
||||||
|
# The balances of the account should reflect whether zero-conf transactions are
|
||||||
|
# being considered. The Sapling balance should remain at 9, while the Orchard
|
||||||
|
# balance will show either 0 (because the spent 10-ZEC note is never shown, as
|
||||||
|
# that transaction has been created and broadcast, and _might_ get mined up until
|
||||||
|
# the transaction expires), or 9 (if we include the unmined transaction).
|
||||||
|
self.sync_all()
|
||||||
|
self.check_balance(0, 0, ua0, {'sapling': 9})
|
||||||
|
self.check_balance(0, 0, ua0, {'sapling': 9, 'orchard': 9}, 0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
WalletAccountsTest().main()
|
WalletAccountsTest().main()
|
||||||
|
|
|
@ -4,53 +4,162 @@
|
||||||
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||||
|
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import assert_equal
|
from test_framework.util import assert_equal, start_nodes, connect_nodes_bi, NU5_BRANCH_ID
|
||||||
|
from test_framework.mininode import nuparams
|
||||||
|
|
||||||
# Test wallet address behaviour across network upgrades
|
# Test wallet address behaviour across network upgrades
|
||||||
class WalletAddressesTest(BitcoinTestFramework):
|
class WalletAddressesTest(BitcoinTestFramework):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
# need 2 nodes to import addresses
|
||||||
|
self.num_nodes = 2
|
||||||
|
self.setup_clean_chain = True
|
||||||
|
|
||||||
def run_test(self):
|
def setup_network(self):
|
||||||
def addr_checks(default_type):
|
self.nodes = start_nodes(
|
||||||
# Check default type, as well as explicit types
|
self.num_nodes, self.options.tmpdir,
|
||||||
types_and_addresses = [
|
extra_args=[['-experimentalfeatures', '-orchardwallet', nuparams(NU5_BRANCH_ID, 2),]] * self.num_nodes)
|
||||||
(default_type, self.nodes[0].z_getnewaddress()),
|
connect_nodes_bi(self.nodes, 0, 1)
|
||||||
('sprout', self.nodes[0].z_getnewaddress('sprout')),
|
self.is_network_split = False
|
||||||
('sapling', self.nodes[0].z_getnewaddress('sapling')),
|
|
||||||
]
|
|
||||||
|
|
||||||
all_addresses = self.nodes[0].z_listaddresses()
|
|
||||||
|
|
||||||
for addr_type, addr in types_and_addresses:
|
|
||||||
res = self.nodes[0].z_validateaddress(addr)
|
|
||||||
assert(res['isvalid'])
|
|
||||||
assert(res['ismine'])
|
|
||||||
assert_equal(res['type'], addr_type)
|
|
||||||
assert(addr in all_addresses)
|
|
||||||
|
|
||||||
listed_addresses = self.nodes[0].listaddresses()
|
|
||||||
legacy_random_src = next(src for src in listed_addresses if src['source'] == 'legacy_random')
|
|
||||||
legacy_hdseed_src = next(src for src in listed_addresses if src['source'] == 'legacy_hdseed')
|
|
||||||
for addr_type, addr in types_and_addresses:
|
|
||||||
if addr_type == 'sprout':
|
|
||||||
assert(addr in legacy_random_src['sprout']['addresses'])
|
|
||||||
if addr_type == 'sapling':
|
|
||||||
assert(addr in [x for obj in legacy_hdseed_src['sapling'] for x in obj['addresses']])
|
|
||||||
|
|
||||||
# Sanity-check the test harness
|
|
||||||
assert_equal(self.nodes[0].getblockcount(), 200)
|
|
||||||
|
|
||||||
# Current height = 200 -> Sapling
|
|
||||||
# Default address type is Sapling
|
|
||||||
print("Testing height 200 (Sapling)")
|
|
||||||
addr_checks('sapling')
|
|
||||||
|
|
||||||
self.nodes[0].generate(1)
|
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
|
|
||||||
# Current height = 201 -> Sapling
|
def list_addresses(self, node, expected_sources):
|
||||||
# Default address type is Sapling
|
addrs = self.nodes[node].listaddresses()
|
||||||
print("Testing height 201 (Sapling)")
|
sources = [s['source'] for s in addrs]
|
||||||
addr_checks('sapling')
|
# Sources should be unique.
|
||||||
|
assert_equal(len(set(sources)), len(sources))
|
||||||
|
assert_equal(set(sources), set(expected_sources))
|
||||||
|
|
||||||
|
# Extract a list of all addresses from the output.
|
||||||
|
all_addrs = [
|
||||||
|
source.get('transparent', {}).get('addresses', []) +
|
||||||
|
source.get('transparent', {}).get('changeAddresses', []) +
|
||||||
|
source.get('sprout', {}).get('addresses', []) +
|
||||||
|
[s['addresses'] for s in source.get('sapling', [])] +
|
||||||
|
[[a['address'] for a in s['addresses']] for s in source.get('unified', [])]
|
||||||
|
for source in addrs]
|
||||||
|
all_addrs = [a for s in all_addrs for a in s]
|
||||||
|
all_addrs = [a if type(a) == list else [a] for a in all_addrs]
|
||||||
|
all_addrs = [a for s in all_addrs for a in s]
|
||||||
|
|
||||||
|
assert_equal(len(set(all_addrs)), len(all_addrs), "Duplicates in listaddresses output: %s" % addrs)
|
||||||
|
return addrs
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
def get_source(listed_addresses, source):
|
||||||
|
return next(src for src in listed_addresses if src['source'] == source)
|
||||||
|
|
||||||
|
print("Testing height 1 (Sapling)")
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
assert_equal(self.nodes[0].getblockcount(), 1)
|
||||||
|
listed_addresses = self.list_addresses(0, ['mnemonic_seed'])
|
||||||
|
# There should be a single address from the coinbase, which was derived
|
||||||
|
# from the mnemonic seed.
|
||||||
|
assert 'transparent' in get_source(listed_addresses, 'mnemonic_seed')
|
||||||
|
|
||||||
|
# If we import a t-address, we should see imported_watchonly as a source.
|
||||||
|
taddr_import = self.nodes[1].getnewaddress()
|
||||||
|
self.nodes[0].importaddress(taddr_import)
|
||||||
|
listed_addresses = self.list_addresses(0, ['imported_watchonly', 'mnemonic_seed'])
|
||||||
|
imported_watchonly_src = get_source(listed_addresses, 'imported_watchonly')
|
||||||
|
assert_equal(imported_watchonly_src['transparent']['addresses'][0], taddr_import)
|
||||||
|
|
||||||
|
account = self.nodes[0].z_getnewaccount()['account']
|
||||||
|
sprout_1 = self.nodes[0].z_getnewaddress('sprout')
|
||||||
|
sapling_1 = self.nodes[0].z_getnewaddress('sapling')
|
||||||
|
unified_1 = self.nodes[0].z_getaddressforaccount(account)['unifiedaddress']
|
||||||
|
types_and_addresses = [
|
||||||
|
('sprout', sprout_1),
|
||||||
|
('sapling', sapling_1),
|
||||||
|
('unified', unified_1),
|
||||||
|
]
|
||||||
|
|
||||||
|
for addr_type, addr in types_and_addresses:
|
||||||
|
res = self.nodes[0].z_validateaddress(addr)
|
||||||
|
assert res['isvalid']
|
||||||
|
# assert res['ismine'] # this isn't present for unified addresses
|
||||||
|
assert_equal(res['type'], addr_type)
|
||||||
|
|
||||||
|
# We should see the following sources:
|
||||||
|
# - imported_watchonly (for the previously-imported t-addr)
|
||||||
|
# - legacy_random (for the new Sprout address)
|
||||||
|
# - mnemonic_seed (for the previous t-addrs and the new Sapling and Unified addrs)
|
||||||
|
listed_addresses = self.list_addresses(0, ['imported_watchonly', 'legacy_random', 'mnemonic_seed'])
|
||||||
|
legacy_random_src = get_source(listed_addresses, 'legacy_random')
|
||||||
|
mnemonic_seed_src = get_source(listed_addresses, 'mnemonic_seed')
|
||||||
|
|
||||||
|
# Check Sprout addrs
|
||||||
|
assert_equal(legacy_random_src['sprout']['addresses'], [sprout_1])
|
||||||
|
|
||||||
|
# Check Sapling addrs
|
||||||
|
assert_equal(
|
||||||
|
set([(obj['zip32KeyPath'], x) for obj in mnemonic_seed_src['sapling'] for x in obj['addresses']]),
|
||||||
|
set([("m/32'/1'/2147483647'/0'", sapling_1)]),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check Unified addrs
|
||||||
|
unified_obj = mnemonic_seed_src['unified']
|
||||||
|
assert_equal(unified_obj[0]['account'], 0)
|
||||||
|
assert_equal(unified_obj[0]['addresses'][0]['address'], unified_1)
|
||||||
|
assert 'diversifier_index' in unified_obj[0]['addresses'][0]
|
||||||
|
assert_equal(unified_obj[0]['addresses'][0]['receiver_types'], ['p2pkh', 'sapling', 'orchard'])
|
||||||
|
|
||||||
|
print("Testing height 2 (NU5)")
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
assert_equal(self.nodes[0].getblockcount(), 2)
|
||||||
|
# Sprout address generation is no longer allowed
|
||||||
|
sapling_2 = self.nodes[0].z_getnewaddress('sapling')
|
||||||
|
unified_2 = self.nodes[0].z_getaddressforaccount(account)['unifiedaddress']
|
||||||
|
types_and_addresses = [
|
||||||
|
('sapling', sapling_2),
|
||||||
|
('unified', unified_2),
|
||||||
|
]
|
||||||
|
|
||||||
|
for addr_type, addr in types_and_addresses:
|
||||||
|
res = self.nodes[0].z_validateaddress(addr)
|
||||||
|
assert res['isvalid']
|
||||||
|
# assert res['ismine'] # this isn't present for unified addresses
|
||||||
|
assert_equal(res['type'], addr_type)
|
||||||
|
|
||||||
|
# We should see the same sources (address generation does not change across the NU5 boundary).
|
||||||
|
listed_addresses = self.list_addresses(0, ['imported_watchonly', 'legacy_random', 'mnemonic_seed'])
|
||||||
|
legacy_random_src = get_source(listed_addresses, 'legacy_random')
|
||||||
|
mnemonic_seed_src = get_source(listed_addresses, 'mnemonic_seed')
|
||||||
|
|
||||||
|
# Check Sprout addrs
|
||||||
|
assert_equal(legacy_random_src['sprout']['addresses'], [sprout_1])
|
||||||
|
|
||||||
|
# Check Sapling addrs
|
||||||
|
assert_equal(
|
||||||
|
set([(obj['zip32KeyPath'], x) for obj in mnemonic_seed_src['sapling'] for x in obj['addresses']]),
|
||||||
|
set([
|
||||||
|
("m/32'/1'/2147483647'/0'", sapling_1),
|
||||||
|
("m/32'/1'/2147483647'/1'", sapling_2),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check Unified addrs
|
||||||
|
unified_obj = mnemonic_seed_src['unified']
|
||||||
|
assert_equal(unified_obj[0]['account'], 0)
|
||||||
|
assert_equal(
|
||||||
|
set([addr['address'] for addr in unified_obj[0]['addresses']]),
|
||||||
|
set([unified_1, unified_2]),
|
||||||
|
)
|
||||||
|
assert 'diversifier_index' in unified_obj[0]['addresses'][0]
|
||||||
|
assert 'diversifier_index' in unified_obj[0]['addresses'][1]
|
||||||
|
assert_equal(unified_obj[0]['addresses'][0]['receiver_types'], ['p2pkh', 'sapling', 'orchard'])
|
||||||
|
assert_equal(unified_obj[0]['addresses'][1]['receiver_types'], ['p2pkh', 'sapling', 'orchard'])
|
||||||
|
|
||||||
|
print("Generate mature coinbase, spend to create and detect change")
|
||||||
|
self.nodes[0].generate(100)
|
||||||
|
self.sync_all()
|
||||||
|
self.nodes[0].sendmany('', {taddr_import: 1})
|
||||||
|
listed_addresses = self.list_addresses(0, ['imported_watchonly', 'legacy_random', 'mnemonic_seed'])
|
||||||
|
mnemonic_seed_src = get_source(listed_addresses, 'mnemonic_seed')
|
||||||
|
assert len(mnemonic_seed_src['transparent']['changeAddresses']) > 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
WalletAddressesTest().main()
|
WalletAddressesTest().main()
|
||||||
|
|
|
@ -68,7 +68,7 @@ class WalletListNotes(BitcoinTestFramework):
|
||||||
change_amount_2 = receive_amount_1 - receive_amount_2 - DEFAULT_FEE
|
change_amount_2 = receive_amount_1 - receive_amount_2 - DEFAULT_FEE
|
||||||
assert_equal('sapling', self.nodes[0].z_validateaddress(saplingzaddr)['type'])
|
assert_equal('sapling', self.nodes[0].z_validateaddress(saplingzaddr)['type'])
|
||||||
recipients = [{"address": saplingzaddr, "amount":receive_amount_2}]
|
recipients = [{"address": saplingzaddr, "amount":receive_amount_2}]
|
||||||
myopid = self.nodes[0].z_sendmany(sproutzaddr, recipients, 1, DEFAULT_FEE, True)
|
myopid = self.nodes[0].z_sendmany(sproutzaddr, recipients, 1, DEFAULT_FEE)
|
||||||
txid_2 = wait_and_assert_operationid_status(self.nodes[0], myopid)
|
txid_2 = wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ class WalletListNotes(BitcoinTestFramework):
|
||||||
receive_amount_3 = Decimal('2.0')
|
receive_amount_3 = Decimal('2.0')
|
||||||
change_amount_3 = change_amount_2 - receive_amount_3 - DEFAULT_FEE
|
change_amount_3 = change_amount_2 - receive_amount_3 - DEFAULT_FEE
|
||||||
recipients = [{"address": saplingzaddr2, "amount":receive_amount_3}]
|
recipients = [{"address": saplingzaddr2, "amount":receive_amount_3}]
|
||||||
myopid = self.nodes[0].z_sendmany(sproutzaddr, recipients, 1, DEFAULT_FEE, True)
|
myopid = self.nodes[0].z_sendmany(sproutzaddr, recipients, 1, DEFAULT_FEE)
|
||||||
txid_3 = wait_and_assert_operationid_status(self.nodes[0], myopid)
|
txid_3 = wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
unspent_tx = self.nodes[0].z_listunspent(0)
|
unspent_tx = self.nodes[0].z_listunspent(0)
|
||||||
|
|
|
@ -12,8 +12,10 @@ from test_framework.util import (
|
||||||
assert_raises_message,
|
assert_raises_message,
|
||||||
connect_nodes_bi,
|
connect_nodes_bi,
|
||||||
get_coinbase_address,
|
get_coinbase_address,
|
||||||
|
nuparams,
|
||||||
DEFAULT_FEE,
|
DEFAULT_FEE,
|
||||||
DEFAULT_FEE_ZATS
|
DEFAULT_FEE_ZATS,
|
||||||
|
NU5_BRANCH_ID,
|
||||||
)
|
)
|
||||||
from test_framework.util import wait_and_assert_operationid_status, start_nodes
|
from test_framework.util import wait_and_assert_operationid_status, start_nodes
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
@ -33,7 +35,12 @@ class ListReceivedTest (BitcoinTestFramework):
|
||||||
def setup_network(self):
|
def setup_network(self):
|
||||||
self.nodes = start_nodes(
|
self.nodes = start_nodes(
|
||||||
self.num_nodes, self.options.tmpdir,
|
self.num_nodes, self.options.tmpdir,
|
||||||
extra_args=[['-experimentalfeatures', '-orchardwallet']] * self.num_nodes)
|
extra_args=[[
|
||||||
|
'-experimentalfeatures',
|
||||||
|
'-orchardwallet',
|
||||||
|
nuparams(NU5_BRANCH_ID, 225),
|
||||||
|
]] * self.num_nodes
|
||||||
|
)
|
||||||
connect_nodes_bi(self.nodes, 0, 1)
|
connect_nodes_bi(self.nodes, 0, 1)
|
||||||
connect_nodes_bi(self.nodes, 1, 2)
|
connect_nodes_bi(self.nodes, 1, 2)
|
||||||
connect_nodes_bi(self.nodes, 0, 2)
|
connect_nodes_bi(self.nodes, 0, 2)
|
||||||
|
@ -381,9 +388,19 @@ class ListReceivedTest (BitcoinTestFramework):
|
||||||
r = node.z_getaddressforaccount(account)
|
r = node.z_getaddressforaccount(account)
|
||||||
unified_addr = r['unifiedaddress']
|
unified_addr = r['unifiedaddress']
|
||||||
receivers = node.z_listunifiedreceivers(unified_addr)
|
receivers = node.z_listunifiedreceivers(unified_addr)
|
||||||
assert_equal(len(receivers), 2)
|
assert_equal(len(receivers), 3)
|
||||||
assert 'transparent' in receivers
|
assert 'transparent' in receivers
|
||||||
assert 'sapling' in receivers
|
assert 'sapling' in receivers
|
||||||
|
assert 'orchard' in receivers
|
||||||
|
assert_raises_message(
|
||||||
|
JSONRPCException,
|
||||||
|
"The provided address is a bare receiver from a Unified Address in this wallet.",
|
||||||
|
node.z_listreceivedbyaddress, receivers['transparent'], 0)
|
||||||
|
assert_raises_message(
|
||||||
|
JSONRPCException,
|
||||||
|
"The provided address is a bare receiver from a Unified Address in this wallet.",
|
||||||
|
node.z_listreceivedbyaddress, receivers['sapling'], 0)
|
||||||
|
|
||||||
# Wallet contains no notes
|
# Wallet contains no notes
|
||||||
r = node.z_listreceivedbyaddress(unified_addr, 0)
|
r = node.z_listreceivedbyaddress(unified_addr, 0)
|
||||||
assert_equal(len(r), 0, "unified_addr should have received zero notes")
|
assert_equal(len(r), 0, "unified_addr should have received zero notes")
|
||||||
|
@ -425,9 +442,116 @@ class ListReceivedTest (BitcoinTestFramework):
|
||||||
assert_equal(r[1]['blockindex'], -1) # not yet mined
|
assert_equal(r[1]['blockindex'], -1) # not yet mined
|
||||||
assert 'blocktime' in r[1]
|
assert 'blocktime' in r[1]
|
||||||
|
|
||||||
|
def test_received_orchard(self, height):
|
||||||
|
self.generate_and_sync(height+1)
|
||||||
|
taddr = self.nodes[1].getnewaddress()
|
||||||
|
acct1 = self.nodes[1].z_getnewaccount()['account']
|
||||||
|
acct2 = self.nodes[1].z_getnewaccount()['account']
|
||||||
|
|
||||||
|
addrResO = self.nodes[1].z_getaddressforaccount(acct1, ['orchard'])
|
||||||
|
assert_equal(addrResO['pools'], ['orchard'])
|
||||||
|
uao = addrResO['unifiedaddress']
|
||||||
|
|
||||||
|
addrResSO = self.nodes[1].z_getaddressforaccount(acct2, ['sapling', 'orchard'])
|
||||||
|
assert_equal(addrResSO['pools'], ['sapling', 'orchard'])
|
||||||
|
uaso = addrResSO['unifiedaddress']
|
||||||
|
|
||||||
|
self.nodes[0].sendtoaddress(taddr, 4.0)
|
||||||
|
self.generate_and_sync(height+2)
|
||||||
|
|
||||||
|
acct_node0 = self.nodes[0].z_getnewaccount()['account']
|
||||||
|
ua_node0 = self.nodes[0].z_getaddressforaccount(acct_node0, ['sapling', 'orchard'])['unifiedaddress']
|
||||||
|
|
||||||
|
opid = self.nodes[1].z_sendmany(taddr, [
|
||||||
|
{'address': uao, 'amount': 1, 'memo': my_memo},
|
||||||
|
{'address': uaso, 'amount': 2},
|
||||||
|
], 1, 0, 'AllowRevealedSenders')
|
||||||
|
txid0 = wait_and_assert_operationid_status(self.nodes[1], opid)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# Decrypted transaction details should be correct, even though
|
||||||
|
# the transaction is still just in the mempool
|
||||||
|
pt = self.nodes[1].z_viewtransaction(txid0)
|
||||||
|
|
||||||
|
assert_equal(pt['txid'], txid0)
|
||||||
|
assert_equal(len(pt['spends']), 0)
|
||||||
|
assert_equal(len(pt['outputs']), 2)
|
||||||
|
|
||||||
|
# Outputs are not returned in a defined order but the amounts are deterministic
|
||||||
|
outputs = sorted(pt['outputs'], key=lambda x: x['valueZat'])
|
||||||
|
assert_equal(outputs[0]['type'], 'orchard')
|
||||||
|
assert_equal(outputs[0]['address'], uao)
|
||||||
|
assert_equal(outputs[0]['value'], Decimal('1'))
|
||||||
|
assert_equal(outputs[0]['valueZat'], 100000000)
|
||||||
|
assert_equal(outputs[0]['action'], 0)
|
||||||
|
assert_equal(outputs[0]['outgoing'], False)
|
||||||
|
assert_equal(outputs[0]['memo'], my_memo)
|
||||||
|
assert_equal(outputs[0]['memoStr'], my_memo_str)
|
||||||
|
|
||||||
|
assert_equal(outputs[1]['type'], 'orchard')
|
||||||
|
assert_equal(outputs[1]['address'], uaso)
|
||||||
|
assert_equal(outputs[1]['value'], Decimal('2'))
|
||||||
|
assert_equal(outputs[1]['valueZat'], 200000000)
|
||||||
|
assert_equal(outputs[1]['action'], 1)
|
||||||
|
assert_equal(outputs[1]['outgoing'], False)
|
||||||
|
assert_equal(outputs[1]['memo'], no_memo)
|
||||||
|
assert 'memoStr' not in outputs[1]
|
||||||
|
|
||||||
|
self.generate_and_sync(height+3)
|
||||||
|
|
||||||
|
opid = self.nodes[1].z_sendmany(uao, [
|
||||||
|
{'address': uaso, 'amount': Decimal('0.3')},
|
||||||
|
{'address': ua_node0, 'amount': Decimal('0.2')}
|
||||||
|
])
|
||||||
|
txid1 = wait_and_assert_operationid_status(self.nodes[1], opid)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
pt = self.nodes[1].z_viewtransaction(txid1)
|
||||||
|
|
||||||
|
assert_equal(pt['txid'], txid1)
|
||||||
|
assert_equal(len(pt['spends']), 1) # one spend we can see
|
||||||
|
assert_equal(len(pt['outputs']), 3) # one output + one change output we can see
|
||||||
|
|
||||||
|
spends = pt['spends']
|
||||||
|
assert_equal(spends[0]['type'], 'orchard')
|
||||||
|
assert_equal(spends[0]['action'], 0)
|
||||||
|
assert_equal(spends[0]['txidPrev'], txid0)
|
||||||
|
assert_equal(spends[0]['actionPrev'], 0)
|
||||||
|
assert_equal(spends[0]['address'], uao)
|
||||||
|
assert_equal(spends[0]['value'], Decimal('1.0'))
|
||||||
|
assert_equal(spends[0]['valueZat'], 100000000)
|
||||||
|
|
||||||
|
outputs = sorted(pt['outputs'], key=lambda x: x['valueZat'])
|
||||||
|
assert_equal(outputs[0]['type'], 'orchard')
|
||||||
|
assert_equal(outputs[0]['address'], ua_node0)
|
||||||
|
assert_equal(outputs[0]['value'], Decimal('0.2'))
|
||||||
|
assert_equal(outputs[0]['valueZat'], 20000000)
|
||||||
|
assert_equal(outputs[0]['outgoing'], True)
|
||||||
|
assert_equal(outputs[0]['walletInternal'], False)
|
||||||
|
assert_equal(outputs[0]['memo'], no_memo)
|
||||||
|
|
||||||
|
assert_equal(outputs[1]['type'], 'orchard')
|
||||||
|
assert_equal(outputs[1]['address'], uaso)
|
||||||
|
assert_equal(outputs[1]['value'], Decimal('0.3'))
|
||||||
|
assert_equal(outputs[1]['valueZat'], 30000000)
|
||||||
|
assert_equal(outputs[1]['outgoing'], False)
|
||||||
|
assert_equal(outputs[1]['walletInternal'], False)
|
||||||
|
assert_equal(outputs[1]['memo'], no_memo)
|
||||||
|
|
||||||
|
# Verify that we observe the change output
|
||||||
|
assert_equal(outputs[2]['type'], 'orchard')
|
||||||
|
assert_equal(outputs[2]['value'], Decimal('0.49999'))
|
||||||
|
assert_equal(outputs[2]['valueZat'], 49999000)
|
||||||
|
assert_equal(outputs[2]['outgoing'], False)
|
||||||
|
assert_equal(outputs[2]['walletInternal'], True)
|
||||||
|
assert_equal(outputs[2]['memo'], no_memo)
|
||||||
|
# The change address should have been erased
|
||||||
|
assert_true('address' not in outputs[2])
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
self.test_received_sprout(200)
|
self.test_received_sprout(200)
|
||||||
self.test_received_sapling(214)
|
self.test_received_sapling(214)
|
||||||
|
self.test_received_orchard(230)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2022 The Zcash developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||||
|
|
||||||
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
|
from test_framework.util import (
|
||||||
|
NU5_BRANCH_ID,
|
||||||
|
assert_equal,
|
||||||
|
get_coinbase_address,
|
||||||
|
nuparams,
|
||||||
|
start_nodes,
|
||||||
|
wait_and_assert_operationid_status,
|
||||||
|
)
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
# Test wallet behaviour with the Orchard protocol
|
||||||
|
class WalletOrchardTest(BitcoinTestFramework):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.num_nodes = 4
|
||||||
|
|
||||||
|
def setup_nodes(self):
|
||||||
|
return start_nodes(self.num_nodes, self.options.tmpdir, [[
|
||||||
|
'-experimentalfeatures',
|
||||||
|
'-orchardwallet',
|
||||||
|
nuparams(NU5_BRANCH_ID, 210),
|
||||||
|
]] * self.num_nodes)
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
# Sanity-check the test harness
|
||||||
|
assert_equal(self.nodes[0].getblockcount(), 200)
|
||||||
|
|
||||||
|
# Get a new orchard-only unified address
|
||||||
|
acct1 = self.nodes[1].z_getnewaccount()['account']
|
||||||
|
addrRes1 = self.nodes[1].z_getaddressforaccount(acct1, ['orchard'])
|
||||||
|
assert_equal(acct1, addrRes1['account'])
|
||||||
|
assert_equal(addrRes1['pools'], ['orchard'])
|
||||||
|
ua1 = addrRes1['unifiedaddress']
|
||||||
|
|
||||||
|
# Verify that we have only an Orchard component
|
||||||
|
receiver_types = self.nodes[0].z_listunifiedreceivers(ua1)
|
||||||
|
assert_equal(set(['orchard']), set(receiver_types))
|
||||||
|
|
||||||
|
# Verify balance
|
||||||
|
assert_equal({'pools': {}, 'minimum_confirmations': 1}, self.nodes[1].z_getbalanceforaccount(acct1))
|
||||||
|
|
||||||
|
# Send some sapling funds to node 2 for later spending after we split the network
|
||||||
|
acct2 = self.nodes[2].z_getnewaccount()['account']
|
||||||
|
addrRes2 = self.nodes[2].z_getaddressforaccount(acct2, ['sapling', 'orchard'])
|
||||||
|
assert_equal(acct2, addrRes2['account'])
|
||||||
|
ua2 = addrRes2['unifiedaddress']
|
||||||
|
saplingAddr2 = self.nodes[2].z_listunifiedreceivers(ua2)['sapling']
|
||||||
|
|
||||||
|
recipients = [{"address": saplingAddr2, "amount": Decimal('10')}]
|
||||||
|
myopid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders')
|
||||||
|
wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||||
|
|
||||||
|
# Mine the tx & activate NU5
|
||||||
|
self.sync_all()
|
||||||
|
self.nodes[0].generate(10)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# Check the value sent to saplingAddr2 was received in node 2's account
|
||||||
|
assert_equal(
|
||||||
|
{'pools': {'sapling': {'valueZat': Decimal('1000000000')}}, 'minimum_confirmations': 1},
|
||||||
|
self.nodes[2].z_getbalanceforaccount(acct2))
|
||||||
|
|
||||||
|
# Node 0 shields some funds
|
||||||
|
# t-coinbase -> Orchard
|
||||||
|
recipients = [{"address": ua1, "amount": Decimal('10')}]
|
||||||
|
myopid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders')
|
||||||
|
wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||||
|
|
||||||
|
self.sync_all()
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
{'pools': {'orchard': {'valueZat': Decimal('1000000000')}}, 'minimum_confirmations': 1},
|
||||||
|
self.nodes[1].z_getbalanceforaccount(acct1))
|
||||||
|
|
||||||
|
# Split the network
|
||||||
|
self.split_network()
|
||||||
|
|
||||||
|
# Send another tx to ua1
|
||||||
|
recipients = [{"address": ua1, "amount": Decimal('10')}]
|
||||||
|
myopid = self.nodes[0].z_sendmany(get_coinbase_address(self.nodes[0]), recipients, 1, 0, 'AllowRevealedSenders')
|
||||||
|
wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||||
|
|
||||||
|
# Mine the tx & generate a majority chain on the 0/1 side of the split
|
||||||
|
self.sync_all()
|
||||||
|
self.nodes[0].generate(10)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
{'pools': {'orchard': {'valueZat': Decimal('2000000000')}}, 'minimum_confirmations': 1},
|
||||||
|
self.nodes[1].z_getbalanceforaccount(acct1))
|
||||||
|
|
||||||
|
# On the other side of the split, send some funds to node 3
|
||||||
|
acct3 = self.nodes[3].z_getnewaccount()['account']
|
||||||
|
addrRes3 = self.nodes[3].z_getaddressforaccount(acct3, ['sapling', 'orchard'])
|
||||||
|
assert_equal(acct3, addrRes3['account'])
|
||||||
|
ua3 = addrRes3['unifiedaddress']
|
||||||
|
|
||||||
|
recipients = [{"address": ua3, "amount": Decimal('1')}]
|
||||||
|
myopid = self.nodes[2].z_sendmany(ua2, recipients, 1, 0)
|
||||||
|
rollback_tx = wait_and_assert_operationid_status(self.nodes[2], myopid)
|
||||||
|
|
||||||
|
self.sync_all()
|
||||||
|
self.nodes[2].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# The remaining change from ua2's Sapling note has been sent to the
|
||||||
|
# account's internal Orchard change address.
|
||||||
|
assert_equal(
|
||||||
|
{'pools': {'orchard': {'valueZat': Decimal('900000000')}}, 'minimum_confirmations': 1},
|
||||||
|
self.nodes[2].z_getbalanceforaccount(acct2))
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
{'pools': {'orchard': {'valueZat': Decimal('100000000')}}, 'minimum_confirmations': 1},
|
||||||
|
self.nodes[3].z_getbalanceforaccount(acct3))
|
||||||
|
|
||||||
|
# Check that the mempools are empty
|
||||||
|
for i in range(self.num_nodes):
|
||||||
|
assert_equal(set([]), set(self.nodes[i].getrawmempool()))
|
||||||
|
|
||||||
|
# Reconnect the nodes; nodes 2 and 3 will re-org to node 0's chain.
|
||||||
|
print("Re-joining the network so that nodes 2 and 3 reorg")
|
||||||
|
self.join_network()
|
||||||
|
|
||||||
|
# split 0/1's chain should have won, so their wallet balance should be consistent
|
||||||
|
assert_equal(
|
||||||
|
{'pools': {'orchard': {'valueZat': Decimal('2000000000')}}, 'minimum_confirmations': 1},
|
||||||
|
self.nodes[1].z_getbalanceforaccount(acct1))
|
||||||
|
|
||||||
|
# split 2/3's chain should have been rolled back, so their txn should have been
|
||||||
|
# un-mined and returned to the mempool
|
||||||
|
assert_equal(set([rollback_tx]), set(self.nodes[2].getrawmempool()))
|
||||||
|
|
||||||
|
# acct2's sole Orchard note is spent by a transaction in the mempool, so our
|
||||||
|
# confirmed balance is currently 0
|
||||||
|
assert_equal(
|
||||||
|
{'pools': {}, 'minimum_confirmations': 1},
|
||||||
|
self.nodes[2].z_getbalanceforaccount(acct2))
|
||||||
|
|
||||||
|
# acct2's incoming change (unconfirmed, still in the mempool) is 9 zec
|
||||||
|
assert_equal(
|
||||||
|
{'pools': {'orchard': {'valueZat': Decimal('900000000')}}, 'minimum_confirmations': 0},
|
||||||
|
self.nodes[2].z_getbalanceforaccount(acct2, 0))
|
||||||
|
|
||||||
|
# The transaction was un-mined, so acct3 should have no confirmed balance
|
||||||
|
assert_equal(
|
||||||
|
{'pools': {}, 'minimum_confirmations': 1},
|
||||||
|
self.nodes[3].z_getbalanceforaccount(acct3))
|
||||||
|
|
||||||
|
# acct3's unconfirmed balance is 1 zec
|
||||||
|
assert_equal(
|
||||||
|
{'pools': {'orchard': {'valueZat': Decimal('100000000')}}, 'minimum_confirmations': 0},
|
||||||
|
self.nodes[3].z_getbalanceforaccount(acct3, 0))
|
||||||
|
|
||||||
|
# Manually resend the transaction in node 2's mempool
|
||||||
|
self.nodes[2].resendwallettransactions()
|
||||||
|
|
||||||
|
# Sync the network
|
||||||
|
self.sync_all()
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# The un-mined transaction should now have been re-mined
|
||||||
|
assert_equal(
|
||||||
|
{'pools': {'orchard': {'valueZat': Decimal('900000000')}}, 'minimum_confirmations': 1},
|
||||||
|
self.nodes[2].z_getbalanceforaccount(acct2))
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
{'pools': {'orchard': {'valueZat': Decimal('100000000')}}, 'minimum_confirmations': 1},
|
||||||
|
self.nodes[3].z_getbalanceforaccount(acct3))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
WalletOrchardTest().main()
|
|
@ -6,17 +6,31 @@
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import (
|
from test_framework.util import (
|
||||||
assert_equal,
|
assert_equal,
|
||||||
|
connect_nodes_bi,
|
||||||
|
start_nodes,
|
||||||
|
sync_blocks,
|
||||||
wait_and_assert_operationid_status,
|
wait_and_assert_operationid_status,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TX_EXPIRY_DELTA = 10
|
||||||
|
TX_EXPIRING_SOON_THRESHOLD = 3
|
||||||
|
|
||||||
# Test ANY_TADDR special string in z_sendmany
|
# Test ANY_TADDR special string in z_sendmany
|
||||||
class WalletSendManyAnyTaddr(BitcoinTestFramework):
|
class WalletSendManyAnyTaddr(BitcoinTestFramework):
|
||||||
|
def setup_nodes(self):
|
||||||
|
return start_nodes(self.num_nodes, self.options.tmpdir,
|
||||||
|
[[
|
||||||
|
"-txexpirydelta=%d" % TX_EXPIRY_DELTA,
|
||||||
|
]] * self.num_nodes)
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
# Sanity-check the test harness
|
# Sanity-check the test harness
|
||||||
assert_equal(self.nodes[0].getblockcount(), 200)
|
assert_equal(self.nodes[0].getblockcount(), 200)
|
||||||
|
|
||||||
# Create the addresses we will be using.
|
# Create the addresses we will be using.
|
||||||
recipient = self.nodes[1].z_getnewaddress()
|
recipient = self.nodes[1].z_getnewaddress()
|
||||||
|
node2zaddr = self.nodes[2].z_getnewaddress()
|
||||||
|
node2taddr1 = self.nodes[2].getnewaddress()
|
||||||
node3zaddr = self.nodes[3].z_getnewaddress()
|
node3zaddr = self.nodes[3].z_getnewaddress()
|
||||||
node3taddr1 = self.nodes[3].getnewaddress()
|
node3taddr1 = self.nodes[3].getnewaddress()
|
||||||
node3taddr2 = self.nodes[3].getnewaddress()
|
node3taddr2 = self.nodes[3].getnewaddress()
|
||||||
|
@ -67,5 +81,48 @@ class WalletSendManyAnyTaddr(BitcoinTestFramework):
|
||||||
assert_equal(self.nodes[3].z_getbalance(node3taddr1), 0)
|
assert_equal(self.nodes[3].z_getbalance(node3taddr1), 0)
|
||||||
assert_equal(self.nodes[3].z_getbalance(node3taddr2), 0)
|
assert_equal(self.nodes[3].z_getbalance(node3taddr2), 0)
|
||||||
|
|
||||||
|
# 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.sync_all()
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# The recipient has their funds!
|
||||||
|
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}])
|
||||||
|
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.
|
||||||
|
self.split_network()
|
||||||
|
expire_transparent = self.nodes[3].sendtoaddress(node2taddr1, 14)
|
||||||
|
assert(expire_transparent in self.nodes[3].getrawmempool())
|
||||||
|
self.sync_all()
|
||||||
|
assert_equal('waiting', self.nodes[2].gettransaction(expire_transparent)['status'])
|
||||||
|
|
||||||
|
self.nodes[0].generate(TX_EXPIRY_DELTA + TX_EXPIRING_SOON_THRESHOLD)
|
||||||
|
self.sync_all()
|
||||||
|
connect_nodes_bi(self.nodes, 1, 2)
|
||||||
|
sync_blocks(self.nodes[1:3])
|
||||||
|
assert_equal('expired', self.nodes[2].gettransaction(expire_transparent)['status'])
|
||||||
|
|
||||||
|
# Ensure that node 2 has no transparent funds.
|
||||||
|
self.nodes[2].generate(100) # To ensure node 2's pending coinbase is spendable
|
||||||
|
self.sync_all()
|
||||||
|
wait_and_assert_operationid_status(
|
||||||
|
self.nodes[2],
|
||||||
|
self.nodes[2].z_shieldcoinbase("*", node2zaddr, 0)['opid'],
|
||||||
|
)
|
||||||
|
self.sync_all()
|
||||||
|
assert_equal(0, self.nodes[2].getbalance())
|
||||||
|
|
||||||
|
# Check that ANY_TADDR doesn't select an expired output.
|
||||||
|
wait_and_assert_operationid_status(self.nodes[2], self.nodes[2].z_sendmany('ANY_TADDR', [{'address': recipient, 'amount': 13}]), "failed", "Insufficient funds: have 0.00, need 13.00001")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
WalletSendManyAnyTaddr().main()
|
WalletSendManyAnyTaddr().main()
|
||||||
|
|
|
@ -23,7 +23,8 @@ class WalletShieldCoinbaseUANU5(WalletShieldCoinbaseTest):
|
||||||
assert('transparent' not in balances['pools'])
|
assert('transparent' not in balances['pools'])
|
||||||
assert('sprout' not in balances['pools'])
|
assert('sprout' not in balances['pools'])
|
||||||
# assert('sapling' not in balances['pools'])
|
# assert('sapling' not in balances['pools'])
|
||||||
assert_equal(balances['pools']['sapling']['valueZat'], expected * COIN)
|
sapling_balance = balances['pools']['sapling']['valueZat']
|
||||||
|
assert_equal(sapling_balance, expected * COIN)
|
||||||
# assert_equal(balances['pools']['orchard']['valueZat'], expected * COIN)
|
# assert_equal(balances['pools']['orchard']['valueZat'], expected * COIN)
|
||||||
|
|
||||||
# While we're at it, check that z_listunspent only shows outputs with
|
# While we're at it, check that z_listunspent only shows outputs with
|
||||||
|
@ -35,6 +36,8 @@ class WalletShieldCoinbaseUANU5(WalletShieldCoinbaseTest):
|
||||||
[{'type': x['type'], 'address': x['address']} for x in unspent],
|
[{'type': x['type'], 'address': x['address']} for x in unspent],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
total_balance = node.z_getbalance(self.addr) * COIN
|
||||||
|
assert_equal(total_balance, sapling_balance)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("Test shielding to a unified address with NU5 activated")
|
print("Test shielding to a unified address with NU5 activated")
|
||||||
|
|
|
@ -21,7 +21,8 @@ class WalletShieldCoinbaseUASapling(WalletShieldCoinbaseTest):
|
||||||
balances = node.z_getbalanceforaccount(self.account)
|
balances = node.z_getbalanceforaccount(self.account)
|
||||||
assert('transparent' not in balances['pools'])
|
assert('transparent' not in balances['pools'])
|
||||||
assert('sprout' not in balances['pools'])
|
assert('sprout' not in balances['pools'])
|
||||||
assert_equal(balances['pools']['sapling']['valueZat'], expected * COIN)
|
sapling_balance = balances['pools']['sapling']['valueZat']
|
||||||
|
assert_equal(sapling_balance, expected * COIN)
|
||||||
assert('orchard' not in balances['pools'])
|
assert('orchard' not in balances['pools'])
|
||||||
|
|
||||||
# While we're at it, check that z_listunspent only shows outputs with
|
# While we're at it, check that z_listunspent only shows outputs with
|
||||||
|
@ -33,6 +34,8 @@ class WalletShieldCoinbaseUASapling(WalletShieldCoinbaseTest):
|
||||||
[{'type': x['type'], 'address': x['address']} for x in unspent],
|
[{'type': x['type'], 'address': x['address']} for x in unspent],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
total_balance = node.z_getbalance(self.addr) * COIN
|
||||||
|
assert_equal(total_balance, sapling_balance)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("Test shielding to a unified address with sapling activated (but not NU5)")
|
print("Test shielding to a unified address with sapling activated (but not NU5)")
|
||||||
|
|
|
@ -4,8 +4,15 @@
|
||||||
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||||
|
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import assert_equal, assert_greater_than, connect_nodes_bi, \
|
from test_framework.util import (
|
||||||
DEFAULT_FEE, start_nodes, wait_and_assert_operationid_status
|
assert_equal,
|
||||||
|
assert_greater_than,
|
||||||
|
assert_raises_message,
|
||||||
|
connect_nodes_bi,
|
||||||
|
DEFAULT_FEE,
|
||||||
|
start_nodes,
|
||||||
|
wait_and_assert_operationid_status,
|
||||||
|
)
|
||||||
from test_framework.authproxy import JSONRPCException
|
from test_framework.authproxy import JSONRPCException
|
||||||
from test_framework.mininode import COIN
|
from test_framework.mininode import COIN
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
@ -27,14 +34,19 @@ class WalletZSendmanyTest(BitcoinTestFramework):
|
||||||
# Remember that empty pools are omitted from the output.
|
# Remember that empty pools are omitted from the output.
|
||||||
def _check_balance_for_rpc(self, rpcmethod, node, account, expected, minconf):
|
def _check_balance_for_rpc(self, rpcmethod, node, account, expected, minconf):
|
||||||
rpc = getattr(self.nodes[node], rpcmethod)
|
rpc = getattr(self.nodes[node], rpcmethod)
|
||||||
actual = rpc(account) if minconf is None else rpc(account, minconf)
|
actual = rpc(account, minconf)
|
||||||
assert_equal(set(expected), set(actual['pools']))
|
assert_equal(set(expected), set(actual['pools']))
|
||||||
|
total_balance = 0
|
||||||
for pool in expected:
|
for pool in expected:
|
||||||
assert_equal(expected[pool] * COIN, actual['pools'][pool]['valueZat'])
|
assert_equal(expected[pool] * COIN, actual['pools'][pool]['valueZat'])
|
||||||
assert_equal(actual['minimum_confirmations'], 1 if minconf is None else minconf)
|
total_balance += expected[pool]
|
||||||
|
assert_equal(actual['minimum_confirmations'], minconf)
|
||||||
|
return total_balance
|
||||||
|
|
||||||
def check_balance(self, node, account, address, expected, minconf=None):
|
def check_balance(self, node, account, address, expected, minconf=1):
|
||||||
self._check_balance_for_rpc('z_getbalanceforaccount', node, account, expected, minconf)
|
acct_balance = self._check_balance_for_rpc('z_getbalanceforaccount', node, account, expected, minconf)
|
||||||
|
z_getbalance = self.nodes[node].z_getbalance(address, minconf)
|
||||||
|
assert_equal(acct_balance, z_getbalance)
|
||||||
fvk = self.nodes[node].z_exportviewingkey(address)
|
fvk = self.nodes[node].z_exportviewingkey(address)
|
||||||
self._check_balance_for_rpc('z_getbalanceforviewingkey', node, fvk, expected, minconf)
|
self._check_balance_for_rpc('z_getbalanceforviewingkey', node, fvk, expected, minconf)
|
||||||
|
|
||||||
|
@ -159,9 +171,36 @@ class WalletZSendmanyTest(BitcoinTestFramework):
|
||||||
|
|
||||||
# Change went to a fresh address, so use `ANY_TADDR` which
|
# Change went to a fresh address, so use `ANY_TADDR` which
|
||||||
# should hold the rest of our transparent funds.
|
# should hold the rest of our transparent funds.
|
||||||
|
source = 'ANY_TADDR'
|
||||||
recipients = []
|
recipients = []
|
||||||
recipients.append({"address":n0ua0, "amount":10})
|
recipients.append({"address":n0ua0, "amount":10})
|
||||||
opid = self.nodes[2].z_sendmany('ANY_TADDR', recipients, 1, 0)
|
|
||||||
|
# If we attempt to spend with the default privacy policy, z_sendmany
|
||||||
|
# fails because it needs to spend transparent coins in a transaction
|
||||||
|
# involving a Unified Address.
|
||||||
|
revealed_senders_msg = 'This transaction requires selecting transparent coins, which is not enabled by default because it will publicly reveal transaction senders and amounts. THIS MAY AFFECT YOUR PRIVACY. Resubmit with the `privacyPolicy` parameter set to `AllowRevealedSenders` or weaker if you wish to allow this transaction to proceed anyway.'
|
||||||
|
opid = self.nodes[2].z_sendmany(source, recipients, 1, 0)
|
||||||
|
wait_and_assert_operationid_status(self.nodes[2], opid, 'failed', revealed_senders_msg)
|
||||||
|
|
||||||
|
# We can't create a transaction with an unknown privacy policy.
|
||||||
|
assert_raises_message(
|
||||||
|
JSONRPCException,
|
||||||
|
'Unknown privacy policy name \'ZcashIsAwesome\'',
|
||||||
|
self.nodes[2].z_sendmany,
|
||||||
|
source, recipients, 1, 0, 'ZcashIsAwesome')
|
||||||
|
|
||||||
|
# If we set any policy that does not include AllowRevealedSenders,
|
||||||
|
# z_sendmany also fails.
|
||||||
|
for policy in [
|
||||||
|
'FullPrivacy',
|
||||||
|
'AllowRevealedAmounts',
|
||||||
|
'AllowRevealedRecipients',
|
||||||
|
]:
|
||||||
|
opid = self.nodes[2].z_sendmany(source, recipients, 1, 0, policy)
|
||||||
|
wait_and_assert_operationid_status(self.nodes[2], opid, 'failed', revealed_senders_msg)
|
||||||
|
|
||||||
|
# By setting the correct policy, we can create the transaction.
|
||||||
|
opid = self.nodes[2].z_sendmany(source, recipients, 1, 0, 'AllowRevealedSenders')
|
||||||
wait_and_assert_operationid_status(self.nodes[2], opid)
|
wait_and_assert_operationid_status(self.nodes[2], opid)
|
||||||
|
|
||||||
self.nodes[2].generate(1)
|
self.nodes[2].generate(1)
|
||||||
|
@ -176,7 +215,32 @@ class WalletZSendmanyTest(BitcoinTestFramework):
|
||||||
# Send some funds to a specific legacy taddr that we can spend from
|
# Send some funds to a specific legacy taddr that we can spend from
|
||||||
recipients = []
|
recipients = []
|
||||||
recipients.append({"address":mytaddr, "amount":5})
|
recipients.append({"address":mytaddr, "amount":5})
|
||||||
opid = self.nodes[0].z_sendmany(n0ua0, recipients, 1, 0)
|
|
||||||
|
# If we attempt to spend with the default privacy policy, z_sendmany
|
||||||
|
# returns an error because it needs to create a transparent recipient in
|
||||||
|
# a transaction involving a Unified Address.
|
||||||
|
assert_raises_message(
|
||||||
|
JSONRPCException,
|
||||||
|
'AllowRevealedRecipients',
|
||||||
|
self.nodes[0].z_sendmany,
|
||||||
|
n0ua0, recipients, 1, 0)
|
||||||
|
|
||||||
|
# If we set any policy that does not include AllowRevealedRecipients,
|
||||||
|
# z_sendmany also returns an error.
|
||||||
|
for policy in [
|
||||||
|
'FullPrivacy',
|
||||||
|
'AllowRevealedAmounts',
|
||||||
|
'AllowRevealedSenders',
|
||||||
|
'AllowLinkingAccountAddresses',
|
||||||
|
]:
|
||||||
|
assert_raises_message(
|
||||||
|
JSONRPCException,
|
||||||
|
'AllowRevealedRecipients',
|
||||||
|
self.nodes[0].z_sendmany,
|
||||||
|
n0ua0, recipients, 1, 0, policy)
|
||||||
|
|
||||||
|
# By setting the correct policy, we can create the transaction.
|
||||||
|
opid = self.nodes[0].z_sendmany(n0ua0, recipients, 1, 0, 'AllowRevealedRecipients')
|
||||||
wait_and_assert_operationid_status(self.nodes[0], opid)
|
wait_and_assert_operationid_status(self.nodes[0], opid)
|
||||||
|
|
||||||
self.nodes[0].generate(1)
|
self.nodes[0].generate(1)
|
||||||
|
@ -199,10 +263,12 @@ class WalletZSendmanyTest(BitcoinTestFramework):
|
||||||
self.check_balance(0, 0, n0ua0, {'sapling': 2})
|
self.check_balance(0, 0, n0ua0, {'sapling': 2})
|
||||||
assert_equal(Decimal(self.nodes[2].z_getbalance(myzaddr)), zbalance)
|
assert_equal(Decimal(self.nodes[2].z_getbalance(myzaddr)), zbalance)
|
||||||
|
|
||||||
# Send funds back from the legacy taddr to the UA
|
# Send funds back from the legacy taddr to the UA. This requires
|
||||||
|
# AllowRevealedSenders, but we can also use any weaker policy that
|
||||||
|
# includes it.
|
||||||
recipients = []
|
recipients = []
|
||||||
recipients.append({"address":n0ua0, "amount":4})
|
recipients.append({"address":n0ua0, "amount":4})
|
||||||
opid = self.nodes[2].z_sendmany(mytaddr, recipients, 1, 0)
|
opid = self.nodes[2].z_sendmany(mytaddr, recipients, 1, 0, 'AllowFullyTransparent')
|
||||||
wait_and_assert_operationid_status(self.nodes[2], opid)
|
wait_and_assert_operationid_status(self.nodes[2], opid)
|
||||||
|
|
||||||
self.nodes[2].generate(1)
|
self.nodes[2].generate(1)
|
||||||
|
@ -225,5 +291,99 @@ class WalletZSendmanyTest(BitcoinTestFramework):
|
||||||
self.check_balance(0, 0, n0ua0, {'sapling': 8})
|
self.check_balance(0, 0, n0ua0, {'sapling': 8})
|
||||||
assert_equal(Decimal(self.nodes[2].z_getbalance(myzaddr)), zbalance)
|
assert_equal(Decimal(self.nodes[2].z_getbalance(myzaddr)), zbalance)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Test that z_sendmany avoids UA linkability unless we allow it.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Generate a new account with two new addresses.
|
||||||
|
n1account = self.nodes[1].z_getnewaccount()['account']
|
||||||
|
n1ua0 = self.nodes[1].z_getaddressforaccount(n1account)['unifiedaddress']
|
||||||
|
n1ua1 = self.nodes[1].z_getaddressforaccount(n1account)['unifiedaddress']
|
||||||
|
|
||||||
|
# Send funds to the transparent receivers of both addresses.
|
||||||
|
for ua in [n1ua0, n1ua1]:
|
||||||
|
taddr = self.nodes[1].z_listunifiedreceivers(ua)['transparent']
|
||||||
|
self.nodes[0].sendtoaddress(taddr, 2)
|
||||||
|
|
||||||
|
self.sync_all()
|
||||||
|
self.nodes[2].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# The account should see all funds.
|
||||||
|
assert_equal(
|
||||||
|
self.nodes[1].z_getbalanceforaccount(n1account)['pools'],
|
||||||
|
{'transparent': {'valueZat': 4 * COIN}},
|
||||||
|
)
|
||||||
|
|
||||||
|
# The addresses should see only the transparent funds sent to them.
|
||||||
|
assert_equal(self.nodes[1].z_getbalance(n1ua0), 2)
|
||||||
|
assert_equal(self.nodes[1].z_getbalance(n1ua1), 2)
|
||||||
|
|
||||||
|
# If we try to send 3 ZEC from n1ua0, it will fail with too-few funds.
|
||||||
|
recipients = [{"address":n0ua0, "amount":3}]
|
||||||
|
linked_addrs_msg = 'Insufficient funds: have 2.00, need 3.00 (This transaction may require selecting transparent coins that were sent to multiple Unified Addresses, which is not enabled by default because it would create a public link between the transparent receivers of these addresses. THIS MAY AFFECT YOUR PRIVACY. Resubmit with the `privacyPolicy` parameter set to `AllowLinkingAccountAddresses` or weaker if you wish to allow this transaction to proceed anyway.)'
|
||||||
|
opid = self.nodes[1].z_sendmany(n1ua0, recipients, 1, 0)
|
||||||
|
wait_and_assert_operationid_status(self.nodes[1], opid, 'failed', linked_addrs_msg)
|
||||||
|
|
||||||
|
# If we try it again with any policy that is too strong, it also fails.
|
||||||
|
for policy in [
|
||||||
|
'FullPrivacy',
|
||||||
|
'AllowRevealedAmounts',
|
||||||
|
'AllowRevealedRecipients',
|
||||||
|
'AllowRevealedSenders',
|
||||||
|
'AllowFullyTransparent',
|
||||||
|
]:
|
||||||
|
opid = self.nodes[1].z_sendmany(n1ua0, recipients, 1, 0, policy)
|
||||||
|
wait_and_assert_operationid_status(self.nodes[1], opid, 'failed', linked_addrs_msg)
|
||||||
|
|
||||||
|
# Once we provide a sufficiently-weak policy, the transaction succeeds.
|
||||||
|
opid = self.nodes[1].z_sendmany(n1ua0, recipients, 1, 0, 'AllowLinkingAccountAddresses')
|
||||||
|
wait_and_assert_operationid_status(self.nodes[1], opid)
|
||||||
|
|
||||||
|
self.sync_all()
|
||||||
|
self.nodes[2].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# The account should see the remaining funds, and they should have been
|
||||||
|
# sent to the Sapling change address (because NU5 is not active).
|
||||||
|
assert_equal(
|
||||||
|
self.nodes[1].z_getbalanceforaccount(n1account)['pools'],
|
||||||
|
{'sapling': {'valueZat': 1 * COIN}},
|
||||||
|
)
|
||||||
|
|
||||||
|
# The addresses should both show the same balance, as they both show the
|
||||||
|
# Sapling balance.
|
||||||
|
assert_equal(self.nodes[1].z_getbalance(n1ua0), 1)
|
||||||
|
assert_equal(self.nodes[1].z_getbalance(n1ua1), 1)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Test NoPrivacy policy
|
||||||
|
#
|
||||||
|
|
||||||
|
# Send some legacy transparent funds to n1ua0, creating Sapling outputs.
|
||||||
|
recipients = [{"address":n1ua0, "amount":10}]
|
||||||
|
# This requires the AllowRevealedSenders policy...
|
||||||
|
opid = self.nodes[2].z_sendmany('ANY_TADDR', recipients, 1, 0)
|
||||||
|
wait_and_assert_operationid_status(self.nodes[2], opid, 'failed', revealed_senders_msg)
|
||||||
|
# ... which we can always override with the NoPrivacy policy.
|
||||||
|
opid = self.nodes[2].z_sendmany('ANY_TADDR', recipients, 1, 0, 'NoPrivacy')
|
||||||
|
wait_and_assert_operationid_status(self.nodes[2], opid)
|
||||||
|
|
||||||
|
self.sync_all()
|
||||||
|
self.nodes[2].generate(1)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
|
# Send some funds from node 1's account to a transparent address.
|
||||||
|
recipients = [{"address":mytaddr, "amount":5}]
|
||||||
|
# This requires the AllowRevealedRecipients policy...
|
||||||
|
assert_raises_message(
|
||||||
|
JSONRPCException,
|
||||||
|
'AllowRevealedRecipients',
|
||||||
|
self.nodes[1].z_sendmany,
|
||||||
|
n1ua0, recipients, 1, 0)
|
||||||
|
# ... which we can always override with the NoPrivacy policy.
|
||||||
|
opid = self.nodes[1].z_sendmany(n1ua0, recipients, 1, 0, 'NoPrivacy')
|
||||||
|
wait_and_assert_operationid_status(self.nodes[1], opid)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
WalletZSendmanyTest().main()
|
WalletZSendmanyTest().main()
|
||||||
|
|
|
@ -46,6 +46,17 @@ RE_RPATH_RUNPATH = re.compile('No RPATH.*No RUNPATH')
|
||||||
RE_FORTIFY_AVAILABLE = re.compile('FORTIFY_SOURCE support available.*Yes')
|
RE_FORTIFY_AVAILABLE = re.compile('FORTIFY_SOURCE support available.*Yes')
|
||||||
RE_FORTIFY_USED = re.compile('Binary compiled with FORTIFY_SOURCE support.*Yes')
|
RE_FORTIFY_USED = re.compile('Binary compiled with FORTIFY_SOURCE support.*Yes')
|
||||||
|
|
||||||
|
CXX_BINARIES = [
|
||||||
|
'src/zcashd',
|
||||||
|
'src/zcash-cli',
|
||||||
|
'src/zcash-gtest',
|
||||||
|
'src/zcash-tx',
|
||||||
|
'src/test/test_bitcoin',
|
||||||
|
]
|
||||||
|
RUST_BINARIES = [
|
||||||
|
'src/zcashd-wallet-tool',
|
||||||
|
]
|
||||||
|
|
||||||
def test_rpath_runpath(filename):
|
def test_rpath_runpath(filename):
|
||||||
output = subprocess.check_output(
|
output = subprocess.check_output(
|
||||||
[repofile('qa/zcash/checksec.sh'), '--file=' + repofile(filename)]
|
[repofile('qa/zcash/checksec.sh'), '--file=' + repofile(filename)]
|
||||||
|
@ -86,21 +97,14 @@ def check_security_hardening():
|
||||||
if not magic.startswith(b'\x7fELF'):
|
if not magic.startswith(b'\x7fELF'):
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
ret &= test_rpath_runpath('src/zcashd')
|
for bin in CXX_BINARIES + RUST_BINARIES:
|
||||||
ret &= test_rpath_runpath('src/zcash-cli')
|
ret &= test_rpath_runpath(bin)
|
||||||
ret &= test_rpath_runpath('src/zcash-gtest')
|
|
||||||
ret &= test_rpath_runpath('src/zcash-tx')
|
|
||||||
ret &= test_rpath_runpath('src/test/test_bitcoin')
|
|
||||||
|
|
||||||
# NOTE: checksec.sh does not reliably determine whether FORTIFY_SOURCE
|
# NOTE: checksec.sh does not reliably determine whether FORTIFY_SOURCE
|
||||||
# is enabled for the entire binary. See issue #915.
|
# is enabled for the entire binary. See issue #915.
|
||||||
# FORTIFY_SOURCE does mostly nothing for Clang before 10, which we don't
|
# FORTIFY_SOURCE is not applicable to Rust binaries.
|
||||||
# pin yet, so we disable these tests.
|
for bin in CXX_BINARIES:
|
||||||
# ret &= test_fortify_source('src/zcashd')
|
ret &= test_fortify_source(bin)
|
||||||
# ret &= test_fortify_source('src/zcash-cli')
|
|
||||||
# ret &= test_fortify_source('src/zcash-gtest')
|
|
||||||
# ret &= test_fortify_source('src/zcash-tx')
|
|
||||||
# ret &= test_fortify_source('src/test/test_bitcoin')
|
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
|
@ -5,23 +5,25 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
# Ccache 4.0 requires adding CMake to the depends system.
|
# Ccache 4.0 requires adding CMake to the depends system.
|
||||||
native_ccache 4.0 2022-02-01
|
native_ccache 4.0 2022-05-01
|
||||||
native_ccache 4.1 2022-02-01
|
native_ccache 4.1 2022-05-01
|
||||||
native_ccache 4.2 2022-02-01
|
native_ccache 4.2 2022-05-01
|
||||||
native_ccache 4.2.1 2022-02-01
|
native_ccache 4.2.1 2022-05-01
|
||||||
native_ccache 4.3 2022-02-01
|
native_ccache 4.3 2022-05-01
|
||||||
native_ccache 4.4 2022-02-01
|
native_ccache 4.4 2022-05-01
|
||||||
native_ccache 4.4.1 2022-02-01
|
native_ccache 4.4.1 2022-05-01
|
||||||
native_ccache 4.4.2 2022-02-01
|
native_ccache 4.4.2 2022-05-01
|
||||||
native_ccache 4.5 2022-02-01
|
native_ccache 4.5 2022-05-01
|
||||||
native_ccache 4.5.1 2022-02-01
|
native_ccache 4.5.1 2022-05-01
|
||||||
|
native_ccache 4.6 2022-05-01
|
||||||
|
|
||||||
# Clang is currently pinned to LLVM 13
|
# Clang is currently pinned to LLVM 13
|
||||||
|
|
||||||
# Rust is currently pinned to 1.56.1
|
# Rust is currently pinned to 1.59.0
|
||||||
|
|
||||||
bdb 18.1.40 2022-02-01
|
# We're never updating to this version
|
||||||
|
bdb 18.1.40 2024-02-01
|
||||||
|
|
||||||
# Google Test 1.10.0 requires adding CMake to the depends system.
|
# Google Test 1.10.0 requires adding CMake to the depends system.
|
||||||
googletest 1.10.0 2022-02-01
|
googletest 1.10.0 2022-05-01
|
||||||
googletest 1.11.0 2022-02-01
|
googletest 1.11.0 2022-05-01
|
||||||
|
|
|
@ -328,7 +328,9 @@ def main():
|
||||||
# packages.mk is not a dependency, it just specifies the list of them all.
|
# packages.mk is not a dependency, it just specifies the list of them all.
|
||||||
"packages",
|
"packages",
|
||||||
# This package doesn't have conventional version numbers
|
# This package doesn't have conventional version numbers
|
||||||
"native_cctools"
|
"native_cctools",
|
||||||
|
# This package is pinned specifically for Arch.
|
||||||
|
"native_libtinfo",
|
||||||
]
|
]
|
||||||
|
|
||||||
print_row("NAME", "STATUS", "CURRENT VERSION", "NEWER VERSIONS")
|
print_row("NAME", "STATUS", "CURRENT VERSION", "NEWER VERSIONS")
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
1.56.1
|
1.59.0
|
||||||
|
|
|
@ -29,6 +29,9 @@ LIBSECP256K1=secp256k1/libsecp256k1.la
|
||||||
LIBUNIVALUE=univalue/libunivalue.la
|
LIBUNIVALUE=univalue/libunivalue.la
|
||||||
LIBZCASH=libzcash.a
|
LIBZCASH=libzcash.a
|
||||||
|
|
||||||
|
WALLET_TOOL_BIN=zcashd-wallet-tool$(EXEEXT)
|
||||||
|
WALLET_TOOL_BUILD=$(top_builddir)/target/$(RUST_TARGET)/release/zcashd-wallet-tool$(EXEEXT)
|
||||||
|
|
||||||
if ENABLE_ZMQ
|
if ENABLE_ZMQ
|
||||||
LIBBITCOIN_ZMQ=libbitcoin_zmq.a
|
LIBBITCOIN_ZMQ=libbitcoin_zmq.a
|
||||||
endif
|
endif
|
||||||
|
@ -47,7 +50,7 @@ endif
|
||||||
# - Note that this does not prevent the secp256k1-sys vendored code from being built; this
|
# - Note that this does not prevent the secp256k1-sys vendored code from being built; this
|
||||||
# requires https://github.com/rust-bitcoin/rust-secp256k1/issues/380 to be addressed.
|
# requires https://github.com/rust-bitcoin/rust-secp256k1/issues/380 to be addressed.
|
||||||
RUST_ENV_VARS = RUSTC="$(RUSTC)" TERM=dumb RUSTFLAGS="--cfg=rust_secp_no_symbol_renaming"
|
RUST_ENV_VARS = RUSTC="$(RUSTC)" TERM=dumb RUSTFLAGS="--cfg=rust_secp_no_symbol_renaming"
|
||||||
RUST_BUILD_OPTS = --lib --release --target $(RUST_TARGET)
|
RUST_BUILD_OPTS = --release --target $(RUST_TARGET) --manifest-path $(top_srcdir)/Cargo.toml
|
||||||
|
|
||||||
rust_verbose = $(rust_verbose_@AM_V@)
|
rust_verbose = $(rust_verbose_@AM_V@)
|
||||||
rust_verbose_ = $(rust_verbose_@AM_DEFAULT_V@)
|
rust_verbose_ = $(rust_verbose_@AM_DEFAULT_V@)
|
||||||
|
@ -72,10 +75,16 @@ $(CARGO_CONFIGURED): $(top_srcdir)/.cargo/config.offline
|
||||||
$(AM_V_at)touch $@
|
$(AM_V_at)touch $@
|
||||||
endif
|
endif
|
||||||
|
|
||||||
cargo-build: $(CARGO_CONFIGURED)
|
cargo-build-lib: $(CARGO_CONFIGURED)
|
||||||
$(RUST_ENV_VARS) $(CARGO) build $(RUST_BUILD_OPTS) $(rust_verbose) --manifest-path $(top_srcdir)/Cargo.toml
|
$(RUST_ENV_VARS) $(CARGO) build --lib $(RUST_BUILD_OPTS) $(rust_verbose)
|
||||||
|
|
||||||
$(LIBRUSTZCASH): cargo-build
|
cargo-build-bins: $(CARGO_CONFIGURED)
|
||||||
|
$(RUST_ENV_VARS) $(CARGO) build --bins $(RUST_BUILD_OPTS) $(rust_verbose)
|
||||||
|
|
||||||
|
$(WALLET_TOOL_BIN): cargo-build-bins
|
||||||
|
$(AM_V_at)cp $(WALLET_TOOL_BUILD) $@
|
||||||
|
|
||||||
|
$(LIBRUSTZCASH): cargo-build-lib
|
||||||
|
|
||||||
$(LIBSECP256K1): $(wildcard secp256k1/src/*) $(wildcard secp256k1/include/*)
|
$(LIBSECP256K1): $(wildcard secp256k1/src/*) $(wildcard secp256k1/include/*)
|
||||||
$(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F)
|
$(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F)
|
||||||
|
@ -99,6 +108,7 @@ lib_LTLIBRARIES = $(LIBZCASH_SCRIPT)
|
||||||
|
|
||||||
bin_PROGRAMS =
|
bin_PROGRAMS =
|
||||||
noinst_PROGRAMS =
|
noinst_PROGRAMS =
|
||||||
|
bin_SCRIPTS =
|
||||||
TESTS =
|
TESTS =
|
||||||
BENCHMARKS =
|
BENCHMARKS =
|
||||||
|
|
||||||
|
@ -108,6 +118,7 @@ endif
|
||||||
|
|
||||||
if BUILD_BITCOIN_UTILS
|
if BUILD_BITCOIN_UTILS
|
||||||
bin_PROGRAMS += zcash-cli zcash-tx
|
bin_PROGRAMS += zcash-cli zcash-tx
|
||||||
|
bin_SCRIPTS += $(WALLET_TOOL_BIN)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
LIBZCASH_H = \
|
LIBZCASH_H = \
|
||||||
|
@ -116,7 +127,7 @@ LIBZCASH_H = \
|
||||||
zcash/Address.hpp \
|
zcash/Address.hpp \
|
||||||
zcash/address/transparent.h \
|
zcash/address/transparent.h \
|
||||||
zcash/address/mnemonic.h \
|
zcash/address/mnemonic.h \
|
||||||
zcash/address/orchard.h \
|
zcash/address/orchard.hpp \
|
||||||
zcash/address/sapling.hpp \
|
zcash/address/sapling.hpp \
|
||||||
zcash/address/sprout.hpp \
|
zcash/address/sprout.hpp \
|
||||||
zcash/address/unified.h \
|
zcash/address/unified.h \
|
||||||
|
@ -129,7 +140,7 @@ LIBZCASH_H = \
|
||||||
zcash/util.h \
|
zcash/util.h \
|
||||||
zcash/Zcash.h
|
zcash/Zcash.h
|
||||||
|
|
||||||
.PHONY: FORCE cargo-build check-symbols check-security
|
.PHONY: FORCE cargo-build-lib cargo-build-bins check-symbols check-security
|
||||||
# bitcoin core #
|
# bitcoin core #
|
||||||
BITCOIN_CORE_H = \
|
BITCOIN_CORE_H = \
|
||||||
addrdb.h \
|
addrdb.h \
|
||||||
|
@ -234,6 +245,7 @@ BITCOIN_CORE_H = \
|
||||||
uint252.h \
|
uint252.h \
|
||||||
undo.h \
|
undo.h \
|
||||||
util.h \
|
util.h \
|
||||||
|
util/match.h \
|
||||||
utilmoneystr.h \
|
utilmoneystr.h \
|
||||||
utilstrencodings.h \
|
utilstrencodings.h \
|
||||||
utiltest.h \
|
utiltest.h \
|
||||||
|
@ -247,6 +259,7 @@ BITCOIN_CORE_H = \
|
||||||
wallet/asyncrpcoperation_shieldcoinbase.h \
|
wallet/asyncrpcoperation_shieldcoinbase.h \
|
||||||
wallet/crypter.h \
|
wallet/crypter.h \
|
||||||
wallet/db.h \
|
wallet/db.h \
|
||||||
|
wallet/orchard.h \
|
||||||
wallet/paymentdisclosure.h \
|
wallet/paymentdisclosure.h \
|
||||||
wallet/paymentdisclosuredb.h \
|
wallet/paymentdisclosuredb.h \
|
||||||
wallet/rpcwallet.h \
|
wallet/rpcwallet.h \
|
||||||
|
@ -334,6 +347,7 @@ libbitcoin_wallet_a_SOURCES = \
|
||||||
wallet/asyncrpcoperation_shieldcoinbase.cpp \
|
wallet/asyncrpcoperation_shieldcoinbase.cpp \
|
||||||
wallet/crypter.cpp \
|
wallet/crypter.cpp \
|
||||||
wallet/db.cpp \
|
wallet/db.cpp \
|
||||||
|
wallet/orchard.cpp \
|
||||||
wallet/paymentdisclosure.cpp \
|
wallet/paymentdisclosure.cpp \
|
||||||
wallet/paymentdisclosuredb.cpp \
|
wallet/paymentdisclosuredb.cpp \
|
||||||
wallet/rpcdisclosure.cpp \
|
wallet/rpcdisclosure.cpp \
|
||||||
|
@ -601,7 +615,7 @@ CTAES_DIST += crypto/ctaes/ctaes.h
|
||||||
CTAES_DIST += crypto/ctaes/README.md
|
CTAES_DIST += crypto/ctaes/README.md
|
||||||
CTAES_DIST += crypto/ctaes/test.c
|
CTAES_DIST += crypto/ctaes/test.c
|
||||||
|
|
||||||
CLEANFILES = *.gcda *.gcno */*.gcno wallet/*/*.gcno
|
CLEANFILES = *.gcda *.gcno */*.gcno wallet/*/*.gcno $(bin_SCRIPTS)
|
||||||
|
|
||||||
DISTCLEANFILES = obj/build.h
|
DISTCLEANFILES = obj/build.h
|
||||||
|
|
||||||
|
@ -625,16 +639,17 @@ clean-local:
|
||||||
$(AM_V_CXX) $(OBJCXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
|
$(AM_V_CXX) $(OBJCXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
|
||||||
$(CPPFLAGS) $(AM_CXXFLAGS) $(AM_CXXFLAGS) $(PIE_FLAGS) $(CXXFLAGS) -c -o $@ $<
|
$(CPPFLAGS) $(AM_CXXFLAGS) $(AM_CXXFLAGS) $(PIE_FLAGS) $(CXXFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
check-symbols: $(bin_PROGRAMS)
|
check-symbols: $(bin_PROGRAMS) $(bin_SCRIPTS)
|
||||||
if GLIBC_BACK_COMPAT
|
if GLIBC_BACK_COMPAT
|
||||||
@echo "Checking glibc back compat of [$(bin_PROGRAMS)]..."
|
@echo "Checking glibc back compat of [$(bin_PROGRAMS) $(bin_SCRIPTS)]..."
|
||||||
$(AM_V_at) READELF=$(READELF) CPPFILT=$(CPPFILT) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS)
|
$(AM_V_at) READELF=$(READELF) CPPFILT=$(CPPFILT) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS) $(bin_SCRIPTS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
check-security: $(bin_PROGRAMS)
|
check-security: $(bin_PROGRAMS) $(bin_SCRIPTS)
|
||||||
if HARDEN
|
if HARDEN
|
||||||
@echo "Checking binary security of [$(bin_PROGRAMS)]..."
|
@echo "Checking binary security of [$(bin_PROGRAMS) $(bin_SCRIPTS)]..."
|
||||||
$(AM_V_at) READELF=$(READELF) OBJDUMP=$(OBJDUMP) $(top_srcdir)/contrib/devtools/security-check.py $(bin_PROGRAMS)
|
$(AM_V_at) READELF=$(READELF) OBJDUMP=$(OBJDUMP) $(top_srcdir)/contrib/devtools/security-check.py $(bin_PROGRAMS)
|
||||||
|
$(AM_V_at) READELF=$(READELF) OBJDUMP=$(OBJDUMP) $(top_srcdir)/contrib/devtools/security-check.py --allow-no-canary $(bin_SCRIPTS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
%.pb.cc %.pb.h: %.proto
|
%.pb.cc %.pb.h: %.proto
|
||||||
|
|
|
@ -54,12 +54,15 @@ zcash_gtest_SOURCES += \
|
||||||
gtest/test_timedata.cpp \
|
gtest/test_timedata.cpp \
|
||||||
gtest/test_transaction.cpp \
|
gtest/test_transaction.cpp \
|
||||||
gtest/test_transaction_builder.cpp \
|
gtest/test_transaction_builder.cpp \
|
||||||
|
gtest/test_transaction_builder.h \
|
||||||
gtest/test_txid.cpp \
|
gtest/test_txid.cpp \
|
||||||
gtest/test_upgrades.cpp \
|
gtest/test_upgrades.cpp \
|
||||||
gtest/test_validation.cpp \
|
gtest/test_validation.cpp \
|
||||||
gtest/test_zip32.cpp
|
gtest/test_zip32.cpp
|
||||||
if ENABLE_WALLET
|
if ENABLE_WALLET
|
||||||
zcash_gtest_SOURCES += \
|
zcash_gtest_SOURCES += \
|
||||||
|
wallet/gtest/test_note_selection.cpp \
|
||||||
|
wallet/gtest/test_orchard_wallet.cpp \
|
||||||
wallet/gtest/test_paymentdisclosure.cpp \
|
wallet/gtest/test_paymentdisclosure.cpp \
|
||||||
wallet/gtest/test_wallet.cpp
|
wallet/gtest/test_wallet.cpp
|
||||||
endif
|
endif
|
||||||
|
|
|
@ -39,7 +39,7 @@ std::string HelpMessageCli()
|
||||||
strUsage += HelpMessageGroup(_("Options:"));
|
strUsage += HelpMessageGroup(_("Options:"));
|
||||||
strUsage += HelpMessageOpt("-?", _("This help message"));
|
strUsage += HelpMessageOpt("-?", _("This help message"));
|
||||||
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), BITCOIN_CONF_FILENAME));
|
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), BITCOIN_CONF_FILENAME));
|
||||||
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory"));
|
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory (this path cannot use '~')"));
|
||||||
strUsage += HelpMessageOpt("-stdin", _("Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases). If first extra argument is `walletpassphrase` then the first line(password) will not be echoed."));
|
strUsage += HelpMessageOpt("-stdin", _("Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases). If first extra argument is `walletpassphrase` then the first line(password) will not be echoed."));
|
||||||
AppendParamsHelpMessages(strUsage);
|
AppendParamsHelpMessages(strUsage);
|
||||||
strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf(_("Send commands to node running on <ip> (default: %s)"), DEFAULT_RPCCONNECT));
|
strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf(_("Send commands to node running on <ip> (default: %s)"), DEFAULT_RPCCONNECT));
|
||||||
|
|
|
@ -133,7 +133,7 @@ public:
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight = 1046400;
|
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight = 1046400;
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].hashActivationBlock =
|
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].hashActivationBlock =
|
||||||
uint256S("00000000002038016f976744c369dce7419fca30e7171dfac703af5e5f7ad1d4");
|
uint256S("00000000002038016f976744c369dce7419fca30e7171dfac703af5e5f7ad1d4");
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nProtocolVersion = 170017;
|
consensus.vUpgrades[Consensus::UPGRADE_NU5].nProtocolVersion = 170100;
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nActivationHeight =
|
consensus.vUpgrades[Consensus::UPGRADE_NU5].nActivationHeight =
|
||||||
Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT;
|
Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT;
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_ZFUTURE].nProtocolVersion = 0x7FFFFFFF;
|
consensus.vUpgrades[Consensus::UPGRADE_ZFUTURE].nProtocolVersion = 0x7FFFFFFF;
|
||||||
|
@ -417,8 +417,9 @@ public:
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight = 1028500;
|
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight = 1028500;
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].hashActivationBlock =
|
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].hashActivationBlock =
|
||||||
uint256S("01a4d7c6aada30c87762c1bf33fff5df7266b1fd7616bfdb5227fa59bd79e7a2");
|
uint256S("01a4d7c6aada30c87762c1bf33fff5df7266b1fd7616bfdb5227fa59bd79e7a2");
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nProtocolVersion = 170015;
|
consensus.vUpgrades[Consensus::UPGRADE_NU5].nProtocolVersion = 170050;
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nActivationHeight = 1599200;
|
consensus.vUpgrades[Consensus::UPGRADE_NU5].nActivationHeight =
|
||||||
|
Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT;
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_ZFUTURE].nProtocolVersion = 0x7FFFFFFF;
|
consensus.vUpgrades[Consensus::UPGRADE_ZFUTURE].nProtocolVersion = 0x7FFFFFFF;
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_ZFUTURE].nActivationHeight =
|
consensus.vUpgrades[Consensus::UPGRADE_ZFUTURE].nActivationHeight =
|
||||||
Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT;
|
Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT;
|
||||||
|
@ -633,7 +634,7 @@ public:
|
||||||
static_assert(equihash_parameters_acceptable(N, K));
|
static_assert(equihash_parameters_acceptable(N, K));
|
||||||
consensus.nEquihashN = N;
|
consensus.nEquihashN = N;
|
||||||
consensus.nEquihashK = K;
|
consensus.nEquihashK = K;
|
||||||
consensus.powLimit = uint256S("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f");
|
consensus.powLimit = uint256S("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"); // if this is any larger, the for loop in GetNextWorkRequired can overflow bnTot
|
||||||
consensus.nPowAveragingWindow = 17;
|
consensus.nPowAveragingWindow = 17;
|
||||||
assert(maxUint/UintToArith256(consensus.powLimit) >= consensus.nPowAveragingWindow);
|
assert(maxUint/UintToArith256(consensus.powLimit) >= consensus.nPowAveragingWindow);
|
||||||
consensus.nPowMaxAdjustDown = 0; // Turn off adjustment down
|
consensus.nPowMaxAdjustDown = 0; // Turn off adjustment down
|
||||||
|
@ -663,7 +664,7 @@ public:
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nProtocolVersion = 170012;
|
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nProtocolVersion = 170012;
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight =
|
consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight =
|
||||||
Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT;
|
Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT;
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nProtocolVersion = 170015;
|
consensus.vUpgrades[Consensus::UPGRADE_NU5].nProtocolVersion = 170040;
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_NU5].nActivationHeight =
|
consensus.vUpgrades[Consensus::UPGRADE_NU5].nActivationHeight =
|
||||||
Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT;
|
Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT;
|
||||||
consensus.vUpgrades[Consensus::UPGRADE_ZFUTURE].nProtocolVersion = 0x7FFFFFFF;
|
consensus.vUpgrades[Consensus::UPGRADE_ZFUTURE].nProtocolVersion = 0x7FFFFFFF;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
||||||
// Copyright (c) 2016-2021 The Zcash developers
|
// Copyright (c) 2016-2022 The Zcash developers
|
||||||
// Distributed under the MIT software license, see the accompanying
|
// Distributed under the MIT software license, see the accompanying
|
||||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@
|
||||||
|
|
||||||
//! These need to be macros, as clientversion.cpp's and bitcoin*-res.rc's voodoo requires it
|
//! These need to be macros, as clientversion.cpp's and bitcoin*-res.rc's voodoo requires it
|
||||||
#define CLIENT_VERSION_MAJOR 4
|
#define CLIENT_VERSION_MAJOR 4
|
||||||
#define CLIENT_VERSION_MINOR 6
|
#define CLIENT_VERSION_MINOR 7
|
||||||
#define CLIENT_VERSION_REVISION 0
|
#define CLIENT_VERSION_REVISION 0
|
||||||
#define CLIENT_VERSION_BUILD 51
|
#define CLIENT_VERSION_BUILD 25
|
||||||
|
|
||||||
//! Set to true for release, false for prerelease or test build
|
//! Set to true for release, false for prerelease or test build
|
||||||
#define CLIENT_VERSION_IS_RELEASE true
|
#define CLIENT_VERSION_IS_RELEASE true
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
* Copyright year (2009-this)
|
* Copyright year (2009-this)
|
||||||
* Todo: update this when changing our copyright comments in the source
|
* Todo: update this when changing our copyright comments in the source
|
||||||
*/
|
*/
|
||||||
#define COPYRIGHT_YEAR 2021
|
#define COPYRIGHT_YEAR 2022
|
||||||
|
|
||||||
#endif //HAVE_CONFIG_H
|
#endif //HAVE_CONFIG_H
|
||||||
|
|
||||||
|
|
|
@ -683,6 +683,14 @@ void CCoinsViewCache::PopAnchor(const uint256 &newrt, ShieldedType type) {
|
||||||
hashSaplingAnchor
|
hashSaplingAnchor
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case ORCHARD:
|
||||||
|
AbstractPopAnchor<OrchardMerkleFrontier, CAnchorsOrchardMap, CAnchorsOrchardCacheEntry>(
|
||||||
|
newrt,
|
||||||
|
ORCHARD,
|
||||||
|
cacheOrchardAnchors,
|
||||||
|
hashOrchardAnchor
|
||||||
|
);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("Unknown shielded type");
|
throw std::runtime_error("Unknown shielded type");
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,15 @@
|
||||||
#include "util/match.h"
|
#include "util/match.h"
|
||||||
|
|
||||||
namespace Consensus {
|
namespace Consensus {
|
||||||
|
std::optional<int> Params::GetActivationHeight(Consensus::UpgradeIndex idx) const {
|
||||||
|
auto nActivationHeight = vUpgrades[idx].nActivationHeight;
|
||||||
|
if (nActivationHeight == Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT) {
|
||||||
|
return std::nullopt;
|
||||||
|
} else {
|
||||||
|
return nActivationHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Params::NetworkUpgradeActive(int nHeight, Consensus::UpgradeIndex idx) const {
|
bool Params::NetworkUpgradeActive(int nHeight, Consensus::UpgradeIndex idx) const {
|
||||||
return NetworkUpgradeState(nHeight, *this, idx) == UPGRADE_ACTIVE;
|
return NetworkUpgradeState(nHeight, *this, idx) == UPGRADE_ACTIVE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,6 +225,11 @@ static const unsigned int PRE_BLOSSOM_REGTEST_HALVING_INTERVAL = 144;
|
||||||
* Parameters that influence chain consensus.
|
* Parameters that influence chain consensus.
|
||||||
*/
|
*/
|
||||||
struct Params {
|
struct Params {
|
||||||
|
/**
|
||||||
|
* Returns the activation height for the specified network upgrade, if any.
|
||||||
|
*/
|
||||||
|
std::optional<int> GetActivationHeight(Consensus::UpgradeIndex idx) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the given network upgrade is active as of the given block
|
* Returns true if the given network upgrade is active as of the given block
|
||||||
* height. Caller must check that the height is >= 0 (and handle unknown
|
* height. Caller must check that the height is >= 0 (and handle unknown
|
||||||
|
|
|
@ -45,7 +45,7 @@ const struct NUInfo NetworkUpgradeInfo[Consensus::MAX_NETWORK_UPGRADES] = {
|
||||||
.strInfo = "See https://z.cash/upgrade/canopy/ for details.",
|
.strInfo = "See https://z.cash/upgrade/canopy/ for details.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.nBranchId = 0x37519621,
|
.nBranchId = 0xc2d6d0b4,
|
||||||
.strName = "NU5",
|
.strName = "NU5",
|
||||||
.strInfo = "See https://z.cash/upgrade/nu5/ for details.",
|
.strInfo = "See https://z.cash/upgrade/nu5/ for details.",
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#include "key.h"
|
#include "key.h"
|
||||||
#include "pubkey.h"
|
#include "pubkey.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "utiltest.h"
|
||||||
|
|
||||||
#include "librustzcash.h"
|
#include "librustzcash.h"
|
||||||
#include <sodium.h>
|
#include <sodium.h>
|
||||||
|
@ -19,26 +20,6 @@ int main(int argc, char **argv) {
|
||||||
assert(sodium_init() != -1);
|
assert(sodium_init() != -1);
|
||||||
ECC_Start();
|
ECC_Start();
|
||||||
|
|
||||||
fs::path sapling_spend = ZC_GetParamsDir() / "sapling-spend.params";
|
|
||||||
fs::path sapling_output = ZC_GetParamsDir() / "sapling-output.params";
|
|
||||||
fs::path sprout_groth16 = ZC_GetParamsDir() / "sprout-groth16.params";
|
|
||||||
|
|
||||||
static_assert(
|
|
||||||
sizeof(fs::path::value_type) == sizeof(codeunit),
|
|
||||||
"librustzcash not configured correctly");
|
|
||||||
auto sapling_spend_str = sapling_spend.native();
|
|
||||||
auto sapling_output_str = sapling_output.native();
|
|
||||||
auto sprout_groth16_str = sprout_groth16.native();
|
|
||||||
|
|
||||||
librustzcash_init_zksnark_params(
|
|
||||||
reinterpret_cast<const codeunit*>(sapling_spend_str.c_str()),
|
|
||||||
sapling_spend_str.length(),
|
|
||||||
reinterpret_cast<const codeunit*>(sapling_output_str.c_str()),
|
|
||||||
sapling_output_str.length(),
|
|
||||||
reinterpret_cast<const codeunit*>(sprout_groth16_str.c_str()),
|
|
||||||
sprout_groth16_str.length()
|
|
||||||
);
|
|
||||||
|
|
||||||
testing::InitGoogleMock(&argc, argv);
|
testing::InitGoogleMock(&argc, argv);
|
||||||
|
|
||||||
// The "threadsafe" style is necessary for correct operation of death/exit
|
// The "threadsafe" style is necessary for correct operation of death/exit
|
||||||
|
|
|
@ -34,6 +34,9 @@ TEST(CheckBlock, VersionTooLow) {
|
||||||
block.nVersion = 1;
|
block.nVersion = 1;
|
||||||
|
|
||||||
MockCValidationState state;
|
MockCValidationState state;
|
||||||
|
|
||||||
|
SelectParams(CBaseChainParams::MAIN);
|
||||||
|
|
||||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "version-too-low", false, "")).Times(1);
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "version-too-low", false, "")).Times(1);
|
||||||
EXPECT_FALSE(CheckBlock(block, state, Params(), verifier, false, false, true));
|
EXPECT_FALSE(CheckBlock(block, state, Params(), verifier, false, false, true));
|
||||||
}
|
}
|
||||||
|
@ -89,7 +92,7 @@ protected:
|
||||||
|
|
||||||
void TearDown() override {
|
void TearDown() override {
|
||||||
// Revert to test default. No-op on mainnet params.
|
// Revert to test default. No-op on mainnet params.
|
||||||
RegtestDeactivateSapling();
|
RegtestDeactivateBlossom();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a valid but empty mutable transaction at block height 1.
|
// Returns a valid but empty mutable transaction at block height 1.
|
||||||
|
|
|
@ -1159,6 +1159,8 @@ TEST(ChecktransactionTests, InvalidShieldedCoinbase) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChecktransactionTests, HeartwoodAcceptsShieldedCoinbase) {
|
TEST(ChecktransactionTests, HeartwoodAcceptsShieldedCoinbase) {
|
||||||
|
LoadProofParameters();
|
||||||
|
|
||||||
RegtestActivateHeartwood(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
RegtestActivateHeartwood(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
||||||
auto chainparams = Params();
|
auto chainparams = Params();
|
||||||
|
|
||||||
|
@ -1239,6 +1241,8 @@ TEST(ChecktransactionTests, HeartwoodAcceptsShieldedCoinbase) {
|
||||||
// bindingSig from https://zips.z.cash/protocol/protocol.pdf#txnencoding are
|
// bindingSig from https://zips.z.cash/protocol/protocol.pdf#txnencoding are
|
||||||
// applied to coinbase transactions.
|
// applied to coinbase transactions.
|
||||||
TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
|
TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
|
||||||
|
LoadProofParameters();
|
||||||
|
|
||||||
RegtestActivateHeartwood(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
RegtestActivateHeartwood(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
||||||
auto chainparams = Params();
|
auto chainparams = Params();
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
#include "transaction_builder.h"
|
#include "transaction_builder.h"
|
||||||
#include "utiltest.h"
|
#include "utiltest.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
TEST(RecursiveDynamicUsageTests, TestTransactionTransparent)
|
TEST(RecursiveDynamicUsageTests, TestTransactionTransparent)
|
||||||
{
|
{
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
@ -20,7 +22,7 @@ TEST(RecursiveDynamicUsageTests, TestTransactionTransparent)
|
||||||
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
||||||
CTxDestination taddr = tsk.GetPubKey().GetID();
|
CTxDestination taddr = tsk.GetPubKey().GetID();
|
||||||
|
|
||||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
|
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
|
||||||
builder.AddTransparentOutput(taddr, 40000);
|
builder.AddTransparentOutput(taddr, 40000);
|
||||||
|
|
||||||
|
@ -48,12 +50,14 @@ TEST(RecursiveDynamicUsageTests, TestTransactionJoinSplit)
|
||||||
|
|
||||||
TEST(RecursiveDynamicUsageTests, TestTransactionSaplingToSapling)
|
TEST(RecursiveDynamicUsageTests, TestTransactionSaplingToSapling)
|
||||||
{
|
{
|
||||||
|
LoadProofParameters();
|
||||||
|
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
auto sk = libzcash::SaplingSpendingKey::random();
|
auto sk = libzcash::SaplingSpendingKey::random();
|
||||||
auto testNote = GetTestSaplingNote(sk.default_address(), 50000);
|
auto testNote = GetTestSaplingNote(sk.default_address(), 50000);
|
||||||
|
|
||||||
auto builder = TransactionBuilder(consensusParams, 1);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});
|
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});
|
||||||
|
|
||||||
|
@ -67,6 +71,8 @@ TEST(RecursiveDynamicUsageTests, TestTransactionSaplingToSapling)
|
||||||
|
|
||||||
TEST(RecursiveDynamicUsageTests, TestTransactionTransparentToSapling)
|
TEST(RecursiveDynamicUsageTests, TestTransactionTransparentToSapling)
|
||||||
{
|
{
|
||||||
|
LoadProofParameters();
|
||||||
|
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
CBasicKeyStore keystore;
|
CBasicKeyStore keystore;
|
||||||
|
@ -74,7 +80,7 @@ TEST(RecursiveDynamicUsageTests, TestTransactionTransparentToSapling)
|
||||||
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
||||||
auto sk = libzcash::SaplingSpendingKey::random();
|
auto sk = libzcash::SaplingSpendingKey::random();
|
||||||
|
|
||||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
|
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
|
||||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 40000, {});
|
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 40000, {});
|
||||||
|
|
||||||
|
@ -88,6 +94,8 @@ TEST(RecursiveDynamicUsageTests, TestTransactionTransparentToSapling)
|
||||||
|
|
||||||
TEST(RecursiveDynamicUsageTests, TestTransactionSaplingToTransparent)
|
TEST(RecursiveDynamicUsageTests, TestTransactionSaplingToTransparent)
|
||||||
{
|
{
|
||||||
|
LoadProofParameters();
|
||||||
|
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
CBasicKeyStore keystore;
|
CBasicKeyStore keystore;
|
||||||
|
@ -96,7 +104,7 @@ TEST(RecursiveDynamicUsageTests, TestTransactionSaplingToTransparent)
|
||||||
auto sk = libzcash::SaplingSpendingKey::random();
|
auto sk = libzcash::SaplingSpendingKey::random();
|
||||||
auto testNote = GetTestSaplingNote(sk.default_address(), 50000);
|
auto testNote = GetTestSaplingNote(sk.default_address(), 50000);
|
||||||
|
|
||||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||||
builder.AddTransparentOutput(taddr, 40000);
|
builder.AddTransparentOutput(taddr, 40000);
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
#include "proof_verifier.h"
|
#include "proof_verifier.h"
|
||||||
#include "transaction_builder.h"
|
#include "transaction_builder.h"
|
||||||
|
#include "utiltest.h"
|
||||||
#include "zcash/JoinSplit.hpp"
|
#include "zcash/JoinSplit.hpp"
|
||||||
#include "zcash/Note.hpp"
|
#include "zcash/Note.hpp"
|
||||||
#include "zcash/NoteEncryption.hpp"
|
#include "zcash/NoteEncryption.hpp"
|
||||||
|
@ -309,6 +310,8 @@ void increment_note_witnesses(
|
||||||
|
|
||||||
TEST(Joinsplit, FullApiTest)
|
TEST(Joinsplit, FullApiTest)
|
||||||
{
|
{
|
||||||
|
LoadProofParameters();
|
||||||
|
|
||||||
{
|
{
|
||||||
std::vector<SproutWitness> witnesses;
|
std::vector<SproutWitness> witnesses;
|
||||||
SproutMerkleTree tree;
|
SproutMerkleTree tree;
|
||||||
|
|
|
@ -69,34 +69,8 @@ TEST(Keys, EncodeAndDecodeSapling)
|
||||||
#define MAKE_STRING(x) std::string((x), (x) + sizeof(x))
|
#define MAKE_STRING(x) std::string((x), (x) + sizeof(x))
|
||||||
|
|
||||||
namespace libzcash {
|
namespace libzcash {
|
||||||
class ReceiverToString {
|
|
||||||
public:
|
|
||||||
ReceiverToString() {}
|
|
||||||
|
|
||||||
std::string operator()(const SaplingPaymentAddress &zaddr) const {
|
|
||||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
|
||||||
ss << zaddr;
|
|
||||||
return tfm::format("Sapling(%s)", HexStr(ss.begin(), ss.end()));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string operator()(const CScriptID &p2sh) const {
|
|
||||||
return tfm::format("P2SH(%s)", p2sh.GetHex());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string operator()(const CKeyID &p2pkh) const {
|
|
||||||
return tfm::format("P2PKH(%s)", p2pkh.GetHex());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string operator()(const UnknownReceiver &unknown) const {
|
|
||||||
return tfm::format(
|
|
||||||
"Unknown(%x, %s)",
|
|
||||||
unknown.typecode,
|
|
||||||
HexStr(unknown.data.begin(), unknown.data.end()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void PrintTo(const Receiver& receiver, std::ostream* os) {
|
void PrintTo(const Receiver& receiver, std::ostream* os) {
|
||||||
*os << std::visit(ReceiverToString(), receiver);
|
*os << DebugPrintReceiver(receiver);
|
||||||
}
|
}
|
||||||
void PrintTo(const UnifiedAddress& ua, std::ostream* os) {
|
void PrintTo(const UnifiedAddress& ua, std::ostream* os) {
|
||||||
*os << "UnifiedAddress(" << testing::PrintToString(ua.GetReceiversAsParsed()) << ")";
|
*os << "UnifiedAddress(" << testing::PrintToString(ua.GetReceiversAsParsed()) << ")";
|
||||||
|
@ -126,8 +100,11 @@ TEST(Keys, EncodeAndDecodeUnifiedAddresses)
|
||||||
// These were added to the UA in preference order by the Python test vectors.
|
// These were added to the UA in preference order by the Python test vectors.
|
||||||
if (!test[3].isNull()) {
|
if (!test[3].isNull()) {
|
||||||
auto data = ParseHex(test[3].get_str());
|
auto data = ParseHex(test[3].get_str());
|
||||||
libzcash::UnknownReceiver r(0x03, data);
|
CDataStream ss(
|
||||||
ua.AddReceiver(r);
|
data,
|
||||||
|
SER_NETWORK,
|
||||||
|
PROTOCOL_VERSION);
|
||||||
|
ua.AddReceiver(libzcash::OrchardRawAddress::Read(ss));
|
||||||
}
|
}
|
||||||
if (!test[2].isNull()) {
|
if (!test[2].isNull()) {
|
||||||
auto data = ParseHex(test[2].get_str());
|
auto data = ParseHex(test[2].get_str());
|
||||||
|
@ -186,6 +163,16 @@ TEST(Keys, DeriveUnifiedFullViewingKeys)
|
||||||
if (test.size() == 1) continue; // comment
|
if (test.size() == 1) continue; // comment
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// [
|
||||||
|
// t_key_bytes,
|
||||||
|
// sapling_fvk_bytes,
|
||||||
|
// orchard_fvk_bytes,
|
||||||
|
// unknown_fvk_typecode,
|
||||||
|
// unknown_fvk_bytes,
|
||||||
|
// unified_fvk,
|
||||||
|
// root_seed,
|
||||||
|
// account,
|
||||||
|
// ],
|
||||||
auto seed_hex = test[6].get_str();
|
auto seed_hex = test[6].get_str();
|
||||||
auto seed_data = ParseHex(seed_hex);
|
auto seed_data = ParseHex(seed_hex);
|
||||||
RawHDSeed raw_seed(seed_data.begin(), seed_data.end());
|
RawHDSeed raw_seed(seed_data.begin(), seed_data.end());
|
||||||
|
@ -235,6 +222,24 @@ TEST(Keys, DeriveUnifiedFullViewingKeys)
|
||||||
auto key = libzcash::SaplingDiversifiableFullViewingKey::Read(ss);
|
auto key = libzcash::SaplingDiversifiableFullViewingKey::Read(ss);
|
||||||
EXPECT_EQ(key, saplingKey);
|
EXPECT_EQ(key, saplingKey);
|
||||||
}
|
}
|
||||||
|
if (!test[2].isNull()) {
|
||||||
|
auto expectedHex = test[2].get_str();
|
||||||
|
|
||||||
|
// Ensure that the serialized Orchard fvk matches the test data.
|
||||||
|
auto orchardKey = ufvk.GetOrchardKey().value();
|
||||||
|
CDataStream ssEncode(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
ssEncode << orchardKey;
|
||||||
|
EXPECT_EQ(ssEncode.size(), 96);
|
||||||
|
auto skeyHex = HexStr(ssEncode.begin(), ssEncode.end());
|
||||||
|
EXPECT_EQ(expectedHex, skeyHex);
|
||||||
|
|
||||||
|
// Ensure that parsing the test data derives the correct dfvk
|
||||||
|
auto data = ParseHex(expectedHex);
|
||||||
|
ASSERT_EQ(data.size(), 96);
|
||||||
|
CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
auto key = libzcash::OrchardFullViewingKey::Read(ss);
|
||||||
|
EXPECT_EQ(key, orchardKey);
|
||||||
|
}
|
||||||
// Enable the following after Orchard keys are supported.
|
// Enable the following after Orchard keys are supported.
|
||||||
//{
|
//{
|
||||||
// auto fvk_data = ParseHex(test[5].get_str());
|
// auto fvk_data = ParseHex(test[5].get_str());
|
||||||
|
@ -284,14 +289,13 @@ TEST(Keys, EncodeAndDecodeUnifiedFullViewingKeys)
|
||||||
auto key = libzcash::SaplingDiversifiableFullViewingKey::Read(ss);
|
auto key = libzcash::SaplingDiversifiableFullViewingKey::Read(ss);
|
||||||
ASSERT_TRUE(builder.AddSaplingKey(key));
|
ASSERT_TRUE(builder.AddSaplingKey(key));
|
||||||
}
|
}
|
||||||
|
if (!test[2].isNull()) {
|
||||||
// Orchard keys and unknown items are not yet supported; instead,
|
auto data = ParseHex(test[2].get_str());
|
||||||
// we just test that we're able to parse the unified key string
|
ASSERT_EQ(data.size(), 96);
|
||||||
// and that the constituent items match the elements; if no Sapling
|
CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION);
|
||||||
// key is present then UFVK construction would fail because it might
|
auto key = libzcash::OrchardFullViewingKey::Read(ss);
|
||||||
// presume the UFVK to be transparent-only.
|
ASSERT_TRUE(builder.AddOrchardKey(key));
|
||||||
if (test[1].isNull())
|
}
|
||||||
continue;
|
|
||||||
|
|
||||||
auto built = builder.build();
|
auto built = builder.build();
|
||||||
ASSERT_TRUE(built.has_value());
|
ASSERT_TRUE(built.has_value());
|
||||||
|
@ -304,5 +308,6 @@ TEST(Keys, EncodeAndDecodeUnifiedFullViewingKeys)
|
||||||
|
|
||||||
EXPECT_EQ(decoded.value().GetTransparentKey(), built.value().GetTransparentKey());
|
EXPECT_EQ(decoded.value().GetTransparentKey(), built.value().GetTransparentKey());
|
||||||
EXPECT_EQ(decoded.value().GetSaplingKey(), built.value().GetSaplingKey());
|
EXPECT_EQ(decoded.value().GetSaplingKey(), built.value().GetSaplingKey());
|
||||||
|
EXPECT_EQ(decoded.value().GetOrchardKey(), built.value().GetOrchardKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -557,20 +557,55 @@ TEST(KeystoreTests, StoreAndRetrieveUFVK) {
|
||||||
EXPECT_EQ(keyStore.GetUnifiedFullViewingKey(ufvkid).value(), zufvk);
|
EXPECT_EQ(keyStore.GetUnifiedFullViewingKey(ufvkid).value(), zufvk);
|
||||||
|
|
||||||
auto addrPair = std::get<std::pair<UnifiedAddress, diversifier_index_t>>(zufvk.FindAddress(diversifier_index_t(0), {ReceiverType::Sapling}));
|
auto addrPair = std::get<std::pair<UnifiedAddress, diversifier_index_t>>(zufvk.FindAddress(diversifier_index_t(0), {ReceiverType::Sapling}));
|
||||||
|
|
||||||
|
|
||||||
EXPECT_TRUE(addrPair.first.GetSaplingReceiver().has_value());
|
EXPECT_TRUE(addrPair.first.GetSaplingReceiver().has_value());
|
||||||
auto saplingReceiver = addrPair.first.GetSaplingReceiver().value();
|
auto saplingReceiver = addrPair.first.GetSaplingReceiver().value();
|
||||||
auto ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
|
|
||||||
EXPECT_FALSE(ufvkmeta.has_value());
|
|
||||||
|
|
||||||
|
// We detect this even though we haven't added the Sapling address, because
|
||||||
|
// we trial-decrypt diversifiers (which also means we learn the index).
|
||||||
|
auto ufvkmetaUnadded = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
|
||||||
|
EXPECT_TRUE(ufvkmetaUnadded.has_value());
|
||||||
|
EXPECT_EQ(ufvkmetaUnadded.value().GetUFVKId(), ufvkid);
|
||||||
|
EXPECT_EQ(ufvkmetaUnadded.value().GetDiversifierIndex().value(), addrPair.second);
|
||||||
|
|
||||||
|
// Adding the Sapling addr -> ivk map entry causes us to find the same UFVK,
|
||||||
|
// and since we trial-decrypt with both external and internal IVKs to
|
||||||
|
// verify whether it's an internal address, we learn the index.
|
||||||
auto saplingIvk = zufvk.GetSaplingKey().value().ToIncomingViewingKey();
|
auto saplingIvk = zufvk.GetSaplingKey().value().ToIncomingViewingKey();
|
||||||
keyStore.AddSaplingPaymentAddress(saplingIvk, saplingReceiver);
|
keyStore.AddSaplingPaymentAddress(saplingIvk, saplingReceiver);
|
||||||
|
|
||||||
ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
|
auto ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
|
||||||
EXPECT_TRUE(ufvkmeta.has_value());
|
EXPECT_TRUE(ufvkmeta.has_value());
|
||||||
EXPECT_EQ(ufvkmeta.value().first, ufvkid);
|
EXPECT_EQ(ufvkmeta.value().GetUFVKId(), ufvkid);
|
||||||
EXPECT_FALSE(ufvkmeta.value().second.has_value());
|
EXPECT_TRUE(ufvkmeta.value().GetDiversifierIndex().has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(KeystoreTests, StoreAndRetrieveUFVKByOrchard) {
|
||||||
|
SelectParams(CBaseChainParams::TESTNET);
|
||||||
|
CBasicKeyStore keyStore;
|
||||||
|
|
||||||
|
auto seed = MnemonicSeed::Random(SLIP44_TESTNET_TYPE);
|
||||||
|
auto usk = ZcashdUnifiedSpendingKey::ForAccount(seed, SLIP44_TESTNET_TYPE, 0);
|
||||||
|
EXPECT_TRUE(usk.has_value());
|
||||||
|
|
||||||
|
auto ufvk = usk.value().ToFullViewingKey();
|
||||||
|
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), ufvk);
|
||||||
|
auto ufvkid = zufvk.GetKeyID();
|
||||||
|
EXPECT_FALSE(keyStore.GetUnifiedFullViewingKey(ufvkid).has_value());
|
||||||
|
|
||||||
|
EXPECT_TRUE(keyStore.AddUnifiedFullViewingKey(zufvk));
|
||||||
|
EXPECT_EQ(keyStore.GetUnifiedFullViewingKey(ufvkid).value(), zufvk);
|
||||||
|
|
||||||
|
auto addrPair = std::get<std::pair<UnifiedAddress, diversifier_index_t>>(zufvk.FindAddress(diversifier_index_t(0), {ReceiverType::Orchard}));
|
||||||
|
EXPECT_TRUE(addrPair.first.GetOrchardReceiver().has_value());
|
||||||
|
auto orchardReceiver = addrPair.first.GetOrchardReceiver().value();
|
||||||
|
|
||||||
|
// We don't store Orchard addresses in CBasicKeyStore (the addr -> ivk
|
||||||
|
// mapping is stored in the Rust wallet), but we still detect this because
|
||||||
|
// we trial-decrypt diversifiers (which also means we learn the index).
|
||||||
|
auto ufvkmetaUnadded = keyStore.GetUFVKMetadataForReceiver(orchardReceiver);
|
||||||
|
EXPECT_TRUE(ufvkmetaUnadded.has_value());
|
||||||
|
EXPECT_EQ(ufvkmetaUnadded.value().GetUFVKId(), ufvkid);
|
||||||
|
EXPECT_EQ(ufvkmetaUnadded.value().GetDiversifierIndex().value(), addrPair.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(KeystoreTests, AddTransparentReceiverForUnifiedAddress) {
|
TEST(KeystoreTests, AddTransparentReceiverForUnifiedAddress) {
|
||||||
|
@ -593,7 +628,7 @@ TEST(KeystoreTests, AddTransparentReceiverForUnifiedAddress) {
|
||||||
|
|
||||||
ufvkmeta = keyStore.GetUFVKMetadataForReceiver(addrPair.first.GetP2PKHReceiver().value());
|
ufvkmeta = keyStore.GetUFVKMetadataForReceiver(addrPair.first.GetP2PKHReceiver().value());
|
||||||
EXPECT_TRUE(ufvkmeta.has_value());
|
EXPECT_TRUE(ufvkmeta.has_value());
|
||||||
EXPECT_EQ(ufvkmeta.value().first, ufvkid);
|
EXPECT_EQ(ufvkmeta.value().GetUFVKId(), ufvkid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,8 @@ TEST(MempoolLimitTests, WeightedTxTreeCheckSizeAfterDropping)
|
||||||
|
|
||||||
TEST(MempoolLimitTests, WeightedTxInfoFromTx)
|
TEST(MempoolLimitTests, WeightedTxInfoFromTx)
|
||||||
{
|
{
|
||||||
|
LoadProofParameters();
|
||||||
|
|
||||||
// The transaction creation is based on the test:
|
// The transaction creation is based on the test:
|
||||||
// test_transaction_builder.cpp/TEST(TransactionBuilder, SetFee)
|
// test_transaction_builder.cpp/TEST(TransactionBuilder, SetFee)
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
@ -119,7 +121,7 @@ TEST(MempoolLimitTests, WeightedTxInfoFromTx)
|
||||||
|
|
||||||
// Default fee
|
// Default fee
|
||||||
{
|
{
|
||||||
auto builder = TransactionBuilder(consensusParams, 1);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 25000, {});
|
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 25000, {});
|
||||||
|
|
||||||
|
@ -130,7 +132,7 @@ TEST(MempoolLimitTests, WeightedTxInfoFromTx)
|
||||||
|
|
||||||
// Lower than standard fee
|
// Lower than standard fee
|
||||||
{
|
{
|
||||||
auto builder = TransactionBuilder(consensusParams, 1);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 25000, {});
|
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 25000, {});
|
||||||
static_assert(DEFAULT_FEE == 1000);
|
static_assert(DEFAULT_FEE == 1000);
|
||||||
|
@ -143,7 +145,7 @@ TEST(MempoolLimitTests, WeightedTxInfoFromTx)
|
||||||
|
|
||||||
// Larger Tx
|
// Larger Tx
|
||||||
{
|
{
|
||||||
auto builder = TransactionBuilder(consensusParams, 1);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});
|
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});
|
||||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});
|
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});
|
||||||
|
|
|
@ -310,5 +310,15 @@ TEST(orchardMerkleTree, appendBundle) {
|
||||||
uint256 anchor(merkle_roots_orchard[i].anchor);
|
uint256 anchor(merkle_roots_orchard[i].anchor);
|
||||||
|
|
||||||
ASSERT_EQ(newTree.root(), anchor);
|
ASSERT_EQ(newTree.root(), anchor);
|
||||||
|
|
||||||
|
// Sanity check roundtrip serialization of the updated tree
|
||||||
|
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
ss << newTree;
|
||||||
|
|
||||||
|
OrchardMerkleFrontier readBack;
|
||||||
|
ss >> readBack;
|
||||||
|
|
||||||
|
EXPECT_NE(newTree.root(), OrchardMerkleFrontier::empty_root());
|
||||||
|
EXPECT_EQ(newTree.root(), readBack.root());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,6 +92,12 @@ TEST(Metrics, GetLocalSolPS) {
|
||||||
// Increment time
|
// Increment time
|
||||||
SetMockTime(104);
|
SetMockTime(104);
|
||||||
EXPECT_EQ(1, GetLocalSolPS());
|
EXPECT_EQ(1, GetLocalSolPS());
|
||||||
|
|
||||||
|
miningTimer.stop();
|
||||||
|
miningTimer.zeroize();
|
||||||
|
solutionTargetChecks.decrement();
|
||||||
|
solutionTargetChecks.decrement();
|
||||||
|
solutionTargetChecks.decrement();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Metrics, EstimateNetHeight) {
|
TEST(Metrics, EstimateNetHeight) {
|
||||||
|
|
|
@ -7,73 +7,20 @@
|
||||||
#include "pubkey.h"
|
#include "pubkey.h"
|
||||||
#include "rpc/protocol.h"
|
#include "rpc/protocol.h"
|
||||||
#include "transaction_builder.h"
|
#include "transaction_builder.h"
|
||||||
|
#include "gtest/test_transaction_builder.h"
|
||||||
#include "utiltest.h"
|
#include "utiltest.h"
|
||||||
#include "zcash/Address.hpp"
|
#include "zcash/Address.hpp"
|
||||||
|
#include "zcash/address/mnemonic.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
// Fake an empty view
|
|
||||||
class TransactionBuilderCoinsViewDB : public CCoinsView {
|
|
||||||
public:
|
|
||||||
std::map<uint256, SproutMerkleTree> sproutTrees;
|
|
||||||
|
|
||||||
TransactionBuilderCoinsViewDB() {}
|
|
||||||
|
|
||||||
bool GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const {
|
|
||||||
auto it = sproutTrees.find(rt);
|
|
||||||
if (it != sproutTrees.end()) {
|
|
||||||
tree = it->second;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GetNullifier(const uint256 &nf, ShieldedType type) const {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GetCoins(const uint256 &txid, CCoins &coins) const {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HaveCoins(const uint256 &txid) const {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 GetBestBlock() const {
|
|
||||||
uint256 a;
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 GetBestAnchor(ShieldedType type) const {
|
|
||||||
uint256 a;
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BatchWrite(CCoinsMap &mapCoins,
|
|
||||||
const uint256 &hashBlock,
|
|
||||||
const uint256 &hashSproutAnchor,
|
|
||||||
const uint256 &hashSaplingAnchor,
|
|
||||||
CAnchorsSproutMap &mapSproutAnchors,
|
|
||||||
CAnchorsSaplingMap &mapSaplingAnchors,
|
|
||||||
CNullifiersMap &mapSproutNullifiers,
|
|
||||||
CNullifiersMap saplingNullifiersMap) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GetStats(CCoinsStats &stats) const {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(TransactionBuilder, TransparentToSapling)
|
TEST(TransactionBuilder, TransparentToSapling)
|
||||||
{
|
{
|
||||||
|
LoadProofParameters();
|
||||||
|
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
CBasicKeyStore keystore;
|
CBasicKeyStore keystore;
|
||||||
|
@ -92,7 +39,7 @@ TEST(TransactionBuilder, TransparentToSapling)
|
||||||
|
|
||||||
// Create a shielding transaction from transparent to Sapling
|
// Create a shielding transaction from transparent to Sapling
|
||||||
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, default fee
|
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, default fee
|
||||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||||
builder.AddTransparentInput(COutPoint(uint256S("1234"), 0), scriptPubKey, 50000);
|
builder.AddTransparentInput(COutPoint(uint256S("1234"), 0), scriptPubKey, 50000);
|
||||||
builder.AddSaplingOutput(fvk_from.ovk, pk, 40000, {});
|
builder.AddSaplingOutput(fvk_from.ovk, pk, 40000, {});
|
||||||
auto tx = builder.Build().GetTxOrThrow();
|
auto tx = builder.Build().GetTxOrThrow();
|
||||||
|
@ -113,6 +60,8 @@ TEST(TransactionBuilder, TransparentToSapling)
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TransactionBuilder, SaplingToSapling) {
|
TEST(TransactionBuilder, SaplingToSapling) {
|
||||||
|
LoadProofParameters();
|
||||||
|
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
auto sk = libzcash::SaplingSpendingKey::random();
|
auto sk = libzcash::SaplingSpendingKey::random();
|
||||||
|
@ -124,7 +73,7 @@ TEST(TransactionBuilder, SaplingToSapling) {
|
||||||
|
|
||||||
// Create a Sapling-only transaction
|
// Create a Sapling-only transaction
|
||||||
// 0.0004 z-ZEC in, 0.00025 z-ZEC out, default fee, 0.00005 z-ZEC change
|
// 0.0004 z-ZEC in, 0.00025 z-ZEC out, default fee, 0.00005 z-ZEC change
|
||||||
auto builder = TransactionBuilder(consensusParams, 2);
|
auto builder = TransactionBuilder(consensusParams, 2, std::nullopt);
|
||||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||||
|
|
||||||
// Check that trying to add a different anchor fails
|
// Check that trying to add a different anchor fails
|
||||||
|
@ -150,6 +99,8 @@ TEST(TransactionBuilder, SaplingToSapling) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TransactionBuilder, SaplingToSprout) {
|
TEST(TransactionBuilder, SaplingToSprout) {
|
||||||
|
LoadProofParameters();
|
||||||
|
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
auto sk = libzcash::SaplingSpendingKey::random();
|
auto sk = libzcash::SaplingSpendingKey::random();
|
||||||
|
@ -165,7 +116,7 @@ TEST(TransactionBuilder, SaplingToSprout) {
|
||||||
// - 0.0004 Sapling-ZEC in - 0.00025 Sprout-ZEC out
|
// - 0.0004 Sapling-ZEC in - 0.00025 Sprout-ZEC out
|
||||||
// - 0.00005 Sapling-ZEC change
|
// - 0.00005 Sapling-ZEC change
|
||||||
// - default t-ZEC fee
|
// - default t-ZEC fee
|
||||||
auto builder = TransactionBuilder(consensusParams, 2, nullptr);
|
auto builder = TransactionBuilder(consensusParams, 2, std::nullopt, nullptr);
|
||||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||||
builder.AddSproutOutput(sproutAddr, 25000);
|
builder.AddSproutOutput(sproutAddr, 25000);
|
||||||
auto tx = builder.Build().GetTxOrThrow();
|
auto tx = builder.Build().GetTxOrThrow();
|
||||||
|
@ -188,6 +139,8 @@ TEST(TransactionBuilder, SaplingToSprout) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TransactionBuilder, SproutToSproutAndSapling) {
|
TEST(TransactionBuilder, SproutToSproutAndSapling) {
|
||||||
|
LoadProofParameters();
|
||||||
|
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
auto sk = libzcash::SaplingSpendingKey::random();
|
auto sk = libzcash::SaplingSpendingKey::random();
|
||||||
|
@ -217,7 +170,7 @@ TEST(TransactionBuilder, SproutToSproutAndSapling) {
|
||||||
// - 0.00005 Sprout-ZEC change
|
// - 0.00005 Sprout-ZEC change
|
||||||
// - 0.00005 Sapling-ZEC out
|
// - 0.00005 Sapling-ZEC out
|
||||||
// - 0.00005 t-ZEC fee
|
// - 0.00005 t-ZEC fee
|
||||||
auto builder = TransactionBuilder(consensusParams, 2, nullptr, &view);
|
auto builder = TransactionBuilder(consensusParams, 2, std::nullopt, nullptr, &view);
|
||||||
builder.SetFee(5000);
|
builder.SetFee(5000);
|
||||||
builder.AddSproutInput(sproutSk, sproutNote, sproutWitness);
|
builder.AddSproutInput(sproutSk, sproutNote, sproutWitness);
|
||||||
builder.AddSproutOutput(sproutAddr, 6000);
|
builder.AddSproutOutput(sproutAddr, 6000);
|
||||||
|
@ -248,12 +201,60 @@ TEST(TransactionBuilder, SproutToSproutAndSapling) {
|
||||||
RegtestDeactivateSapling();
|
RegtestDeactivateSapling();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(TransactionBuilder, TransparentToOrchard)
|
||||||
|
{
|
||||||
|
auto consensusParams = RegtestActivateNU5();
|
||||||
|
|
||||||
|
CBasicKeyStore keystore;
|
||||||
|
CKey tsk = AddTestCKeyToKeyStore(keystore);
|
||||||
|
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
||||||
|
|
||||||
|
auto coinType = Params().BIP44CoinType();
|
||||||
|
auto seed = MnemonicSeed::Random(coinType);
|
||||||
|
auto sk = libzcash::OrchardSpendingKey::ForAccount(seed, coinType, 0);
|
||||||
|
auto fvk = sk.ToFullViewingKey();
|
||||||
|
auto ivk = fvk.ToIncomingViewingKey();
|
||||||
|
libzcash::diversifier_index_t j(0);
|
||||||
|
auto recipient = ivk.Address(j);
|
||||||
|
|
||||||
|
TransactionBuilderCoinsViewDB fakeDB;
|
||||||
|
auto orchardAnchor = fakeDB.GetBestAnchor(ShieldedType::ORCHARD);
|
||||||
|
|
||||||
|
// Create a shielding transaction from transparent to Orchard
|
||||||
|
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, default fee
|
||||||
|
auto builder = TransactionBuilder(consensusParams, 1, orchardAnchor, &keystore);
|
||||||
|
builder.AddTransparentInput(COutPoint(uint256S("1234"), 0), scriptPubKey, 50000);
|
||||||
|
builder.AddOrchardOutput(std::nullopt, recipient, 40000, std::nullopt);
|
||||||
|
auto maybeTx = builder.Build();
|
||||||
|
EXPECT_TRUE(maybeTx.IsTx());
|
||||||
|
if (maybeTx.IsError()) {
|
||||||
|
std::cerr << "Failed to build transaction: " << maybeTx.GetError() << std::endl;
|
||||||
|
GTEST_FAIL();
|
||||||
|
}
|
||||||
|
auto tx = maybeTx.GetTxOrThrow();
|
||||||
|
|
||||||
|
EXPECT_EQ(tx.vin.size(), 1);
|
||||||
|
EXPECT_EQ(tx.vout.size(), 0);
|
||||||
|
EXPECT_EQ(tx.vJoinSplit.size(), 0);
|
||||||
|
EXPECT_EQ(tx.vShieldedSpend.size(), 0);
|
||||||
|
EXPECT_EQ(tx.vShieldedOutput.size(), 0);
|
||||||
|
EXPECT_TRUE(tx.GetOrchardBundle().IsPresent());
|
||||||
|
EXPECT_EQ(tx.GetOrchardBundle().GetValueBalance(), -40000);
|
||||||
|
|
||||||
|
CValidationState state;
|
||||||
|
EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 2, true));
|
||||||
|
EXPECT_EQ(state.GetRejectReason(), "");
|
||||||
|
|
||||||
|
// Revert to default
|
||||||
|
RegtestDeactivateNU5();
|
||||||
|
}
|
||||||
|
|
||||||
TEST(TransactionBuilder, ThrowsOnTransparentInputWithoutKeyStore)
|
TEST(TransactionBuilder, ThrowsOnTransparentInputWithoutKeyStore)
|
||||||
{
|
{
|
||||||
SelectParams(CBaseChainParams::REGTEST);
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
const Consensus::Params& consensusParams = Params().GetConsensus();
|
const Consensus::Params& consensusParams = Params().GetConsensus();
|
||||||
|
|
||||||
auto builder = TransactionBuilder(consensusParams, 1);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||||
ASSERT_THROW(builder.AddTransparentInput(COutPoint(), CScript(), 1), std::runtime_error);
|
ASSERT_THROW(builder.AddTransparentInput(COutPoint(), CScript(), 1), std::runtime_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,12 +265,14 @@ TEST(TransactionBuilder, RejectsInvalidTransparentOutput)
|
||||||
|
|
||||||
// Default CTxDestination type is an invalid address
|
// Default CTxDestination type is an invalid address
|
||||||
CTxDestination taddr;
|
CTxDestination taddr;
|
||||||
auto builder = TransactionBuilder(consensusParams, 1);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||||
ASSERT_THROW(builder.AddTransparentOutput(taddr, 50), UniValue);
|
ASSERT_THROW(builder.AddTransparentOutput(taddr, 50), UniValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TransactionBuilder, FailsWithNegativeChange)
|
TEST(TransactionBuilder, FailsWithNegativeChange)
|
||||||
{
|
{
|
||||||
|
LoadProofParameters();
|
||||||
|
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
// Generate dummy Sapling address
|
// Generate dummy Sapling address
|
||||||
|
@ -289,13 +292,13 @@ TEST(TransactionBuilder, FailsWithNegativeChange)
|
||||||
|
|
||||||
// Fail if there is only a Sapling output
|
// Fail if there is only a Sapling output
|
||||||
// 0.0005 z-ZEC out, default fee
|
// 0.0005 z-ZEC out, default fee
|
||||||
auto builder = TransactionBuilder(consensusParams, 1);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||||
builder.AddSaplingOutput(fvk.ovk, pa, 50000, {});
|
builder.AddSaplingOutput(fvk.ovk, pa, 50000, {});
|
||||||
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
|
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
|
||||||
|
|
||||||
// Fail if there is only a transparent output
|
// Fail if there is only a transparent output
|
||||||
// 0.0005 t-ZEC out, default fee
|
// 0.0005 t-ZEC out, default fee
|
||||||
builder = TransactionBuilder(consensusParams, 1, &keystore);
|
builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||||
builder.AddTransparentOutput(taddr, 50000);
|
builder.AddTransparentOutput(taddr, 50000);
|
||||||
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
|
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
|
||||||
|
|
||||||
|
@ -314,6 +317,8 @@ TEST(TransactionBuilder, FailsWithNegativeChange)
|
||||||
|
|
||||||
TEST(TransactionBuilder, ChangeOutput)
|
TEST(TransactionBuilder, ChangeOutput)
|
||||||
{
|
{
|
||||||
|
LoadProofParameters();
|
||||||
|
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
// Generate dummy Sapling address
|
// Generate dummy Sapling address
|
||||||
|
@ -336,14 +341,14 @@ TEST(TransactionBuilder, ChangeOutput)
|
||||||
|
|
||||||
// No change address and no Sapling spends
|
// No change address and no Sapling spends
|
||||||
{
|
{
|
||||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
||||||
EXPECT_EQ("Could not determine change address", builder.Build().GetError());
|
EXPECT_EQ("Could not determine change address", builder.Build().GetError());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change to the same address as the first Sapling spend
|
// Change to the same address as the first Sapling spend
|
||||||
{
|
{
|
||||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
||||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||||
auto tx = builder.Build().GetTxOrThrow();
|
auto tx = builder.Build().GetTxOrThrow();
|
||||||
|
@ -358,7 +363,7 @@ TEST(TransactionBuilder, ChangeOutput)
|
||||||
|
|
||||||
// Change to a Sapling address
|
// Change to a Sapling address
|
||||||
{
|
{
|
||||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
||||||
builder.SendChangeTo(zChangeAddr, fvkOut.ovk);
|
builder.SendChangeTo(zChangeAddr, fvkOut.ovk);
|
||||||
auto tx = builder.Build().GetTxOrThrow();
|
auto tx = builder.Build().GetTxOrThrow();
|
||||||
|
@ -373,7 +378,7 @@ TEST(TransactionBuilder, ChangeOutput)
|
||||||
|
|
||||||
// Change to a transparent address
|
// Change to a transparent address
|
||||||
{
|
{
|
||||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
||||||
builder.SendChangeTo(tkeyid, {});
|
builder.SendChangeTo(tkeyid, {});
|
||||||
auto tx = builder.Build().GetTxOrThrow();
|
auto tx = builder.Build().GetTxOrThrow();
|
||||||
|
@ -393,6 +398,8 @@ TEST(TransactionBuilder, ChangeOutput)
|
||||||
|
|
||||||
TEST(TransactionBuilder, SetFee)
|
TEST(TransactionBuilder, SetFee)
|
||||||
{
|
{
|
||||||
|
LoadProofParameters();
|
||||||
|
|
||||||
auto consensusParams = RegtestActivateSapling();
|
auto consensusParams = RegtestActivateSapling();
|
||||||
|
|
||||||
// Generate dummy Sapling address
|
// Generate dummy Sapling address
|
||||||
|
@ -405,7 +412,7 @@ TEST(TransactionBuilder, SetFee)
|
||||||
|
|
||||||
// Default fee
|
// Default fee
|
||||||
{
|
{
|
||||||
auto builder = TransactionBuilder(consensusParams, 1);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||||
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
|
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
|
||||||
auto tx = builder.Build().GetTxOrThrow();
|
auto tx = builder.Build().GetTxOrThrow();
|
||||||
|
@ -420,7 +427,7 @@ TEST(TransactionBuilder, SetFee)
|
||||||
|
|
||||||
// Configured fee
|
// Configured fee
|
||||||
{
|
{
|
||||||
auto builder = TransactionBuilder(consensusParams, 1);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||||
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
|
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
|
||||||
builder.SetFee(20000);
|
builder.SetFee(20000);
|
||||||
|
@ -449,7 +456,7 @@ TEST(TransactionBuilder, CheckSaplingTxVersion)
|
||||||
auto pk = sk.default_address();
|
auto pk = sk.default_address();
|
||||||
|
|
||||||
// Cannot add Sapling outputs to a non-Sapling transaction
|
// Cannot add Sapling outputs to a non-Sapling transaction
|
||||||
auto builder = TransactionBuilder(consensusParams, 1);
|
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||||
try {
|
try {
|
||||||
builder.AddSaplingOutput(uint256(), pk, 12345, {});
|
builder.AddSaplingOutput(uint256(), pk, 12345, {});
|
||||||
} catch (std::runtime_error const & err) {
|
} catch (std::runtime_error const & err) {
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright (c) 2022 The Zcash developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||||
|
|
||||||
|
#ifndef ZCASH_GTEST_TEST_TRANSACTION_BUILDER_H
|
||||||
|
#define ZCASH_GTEST_TEST_TRANSACTION_BUILDER_H
|
||||||
|
|
||||||
|
#include "coins.h"
|
||||||
|
|
||||||
|
// Fake an empty view
|
||||||
|
class TransactionBuilderCoinsViewDB : public CCoinsView {
|
||||||
|
public:
|
||||||
|
std::map<uint256, SproutMerkleTree> sproutTrees;
|
||||||
|
|
||||||
|
TransactionBuilderCoinsViewDB() {}
|
||||||
|
|
||||||
|
bool GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const {
|
||||||
|
auto it = sproutTrees.find(rt);
|
||||||
|
if (it != sproutTrees.end()) {
|
||||||
|
tree = it->second;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetOrchardAnchorAt(const uint256 &rt, OrchardMerkleFrontier &tree) const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetNullifier(const uint256 &nf, ShieldedType type) const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetCoins(const uint256 &txid, CCoins &coins) const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HaveCoins(const uint256 &txid) const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 GetBestBlock() const {
|
||||||
|
uint256 a;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 GetBestAnchor(ShieldedType type) const {
|
||||||
|
uint256 a;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BatchWrite(CCoinsMap &mapCoins,
|
||||||
|
const uint256 &hashBlock,
|
||||||
|
const uint256 &hashSproutAnchor,
|
||||||
|
const uint256 &hashSaplingAnchor,
|
||||||
|
CAnchorsSproutMap &mapSproutAnchors,
|
||||||
|
CAnchorsSaplingMap &mapSaplingAnchors,
|
||||||
|
CNullifiersMap &mapSproutNullifiers,
|
||||||
|
CNullifiersMap saplingNullifiersMap) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetStats(CCoinsStats &stats) const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -173,7 +173,7 @@ TEST(Validation, ContextualCheckInputsDetectsOldBranchId) {
|
||||||
|
|
||||||
// Create a transparent transaction that spends the coin, targeting
|
// Create a transparent transaction that spends the coin, targeting
|
||||||
// a height during the Overwinter epoch.
|
// a height during the Overwinter epoch.
|
||||||
auto builder = TransactionBuilder(consensusParams, 15, &keystore);
|
auto builder = TransactionBuilder(consensusParams, 15, std::nullopt, &keystore);
|
||||||
builder.AddTransparentInput(utxo, scriptPubKey, coinValue);
|
builder.AddTransparentInput(utxo, scriptPubKey, coinValue);
|
||||||
builder.AddTransparentOutput(destination, 40000);
|
builder.AddTransparentOutput(destination, 40000);
|
||||||
auto tx = builder.Build().GetTxOrThrow();
|
auto tx = builder.Build().GetTxOrThrow();
|
||||||
|
|
|
@ -305,7 +305,7 @@ bool static Bind(const CService &addr, unsigned int flags) {
|
||||||
|
|
||||||
void OnRPCStopped()
|
void OnRPCStopped()
|
||||||
{
|
{
|
||||||
cvBlockChange.notify_all();
|
g_best_block_cv.notify_all();
|
||||||
LogPrint("rpc", "RPC stopped.\n");
|
LogPrint("rpc", "RPC stopped.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,7 +341,7 @@ std::string HelpMessage(HelpMessageMode mode)
|
||||||
strUsage += HelpMessageOpt("-daemon", _("Run in the background as a daemon and accept commands"));
|
strUsage += HelpMessageOpt("-daemon", _("Run in the background as a daemon and accept commands"));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory"));
|
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory (this path cannot use '~')"));
|
||||||
strUsage += HelpMessageOpt("-paramsdir=<dir>", _("Specify Zcash network parameters directory"));
|
strUsage += HelpMessageOpt("-paramsdir=<dir>", _("Specify Zcash network parameters directory"));
|
||||||
strUsage += HelpMessageOpt("-dbcache=<n>", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache));
|
strUsage += HelpMessageOpt("-dbcache=<n>", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache));
|
||||||
strUsage += HelpMessageOpt("-debuglogfile=<file>", strprintf(_("Specify location of debug log file: this can be an absolute path or a path relative to the data directory (default: %s)"), DEFAULT_DEBUGLOGFILE));
|
strUsage += HelpMessageOpt("-debuglogfile=<file>", strprintf(_("Specify location of debug log file: this can be an absolute path or a path relative to the data directory (default: %s)"), DEFAULT_DEBUGLOGFILE));
|
||||||
|
@ -1105,7 +1105,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||||
#ifdef ENABLE_MINING
|
#ifdef ENABLE_MINING
|
||||||
if (mapArgs.count("-mineraddress")) {
|
if (mapArgs.count("-mineraddress")) {
|
||||||
auto addr = keyIO.DecodePaymentAddress(mapArgs["-mineraddress"]);
|
auto addr = keyIO.DecodePaymentAddress(mapArgs["-mineraddress"]);
|
||||||
if (!(addr.has_value() && std::visit(ExtractMinerAddress(), addr.value()).has_value())) {
|
if (!(addr.has_value() && std::visit(ExtractMinerAddress(chainparams.GetConsensus(), 0), addr.value()).has_value())) {
|
||||||
return InitError(strprintf(
|
return InitError(strprintf(
|
||||||
_("Invalid address for -mineraddress=<addr>: '%s' (must be a Sapling or transparent P2PKH address)"),
|
_("Invalid address for -mineraddress=<addr>: '%s' (must be a Sapling or transparent P2PKH address)"),
|
||||||
mapArgs["-mineraddress"]));
|
mapArgs["-mineraddress"]));
|
||||||
|
@ -1683,7 +1683,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||||
if (!zaddr.has_value()) {
|
if (!zaddr.has_value()) {
|
||||||
return InitError(_("-mineraddress is not a valid " PACKAGE_NAME " address."));
|
return InitError(_("-mineraddress is not a valid " PACKAGE_NAME " address."));
|
||||||
}
|
}
|
||||||
auto ztxoSelector = pwalletMain->ZTXOSelectorForAddress(zaddr.value(), true);
|
auto ztxoSelector = pwalletMain->ZTXOSelectorForAddress(zaddr.value(), true, false);
|
||||||
minerAddressInLocalWallet = ztxoSelector.has_value();
|
minerAddressInLocalWallet = ztxoSelector.has_value();
|
||||||
}
|
}
|
||||||
if (GetBoolArg("-minetolocalwallet", true) && !minerAddressInLocalWallet) {
|
if (GetBoolArg("-minetolocalwallet", true) && !minerAddressInLocalWallet) {
|
||||||
|
|
|
@ -290,7 +290,7 @@ std::optional<CExtKey> CExtKey::Derive(unsigned int _nChild) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CExtKey CExtKey::Master(const unsigned char *seed, unsigned int nSeedLen) {
|
std::optional<CExtKey> CExtKey::Master(const unsigned char *seed, unsigned int nSeedLen) {
|
||||||
CExtKey xk;
|
CExtKey xk;
|
||||||
static const unsigned char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'};
|
static const unsigned char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'};
|
||||||
std::vector<unsigned char, secure_allocator<unsigned char>> vout(64);
|
std::vector<unsigned char, secure_allocator<unsigned char>> vout(64);
|
||||||
|
@ -301,7 +301,11 @@ CExtKey CExtKey::Master(const unsigned char *seed, unsigned int nSeedLen) {
|
||||||
xk.nChild = 0;
|
xk.nChild = 0;
|
||||||
memset(xk.vchFingerprint, 0, sizeof(xk.vchFingerprint));
|
memset(xk.vchFingerprint, 0, sizeof(xk.vchFingerprint));
|
||||||
|
|
||||||
return xk;
|
if (xk.key.IsValid()) {
|
||||||
|
return xk;
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CExtPubKey CExtKey::Neuter() const {
|
CExtPubKey CExtKey::Neuter() const {
|
||||||
|
|
|
@ -166,7 +166,7 @@ struct CExtKey {
|
||||||
a.key == b.key;
|
a.key == b.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
static CExtKey Master(const unsigned char* seed, unsigned int nSeedLen);
|
static std::optional<CExtKey> Master(const unsigned char* seed, unsigned int nSeedLen);
|
||||||
|
|
||||||
void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;
|
void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;
|
||||||
void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
|
void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
|
||||||
|
|
106
src/key_io.cpp
106
src/key_io.cpp
|
@ -56,6 +56,7 @@ class DataLenForReceiver {
|
||||||
public:
|
public:
|
||||||
DataLenForReceiver() {}
|
DataLenForReceiver() {}
|
||||||
|
|
||||||
|
size_t operator()(const libzcash::OrchardRawAddress &zaddr) const { return 43; }
|
||||||
size_t operator()(const libzcash::SaplingPaymentAddress &zaddr) const { return 43; }
|
size_t operator()(const libzcash::SaplingPaymentAddress &zaddr) const { return 43; }
|
||||||
size_t operator()(const CScriptID &p2sh) const { return 20; }
|
size_t operator()(const CScriptID &p2sh) const { return 20; }
|
||||||
size_t operator()(const CKeyID &p2pkh) const { return 20; }
|
size_t operator()(const CKeyID &p2pkh) const { return 20; }
|
||||||
|
@ -76,6 +77,13 @@ class CopyDataForReceiver {
|
||||||
public:
|
public:
|
||||||
CopyDataForReceiver(unsigned char* data, size_t length) : data(data), length(length) {}
|
CopyDataForReceiver(unsigned char* data, size_t length) : data(data), length(length) {}
|
||||||
|
|
||||||
|
void operator()(const libzcash::OrchardRawAddress &zaddr) const {
|
||||||
|
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
ss << zaddr;
|
||||||
|
assert(length == ss.size());
|
||||||
|
memcpy(data, ss.data(), ss.size());
|
||||||
|
}
|
||||||
|
|
||||||
void operator()(const libzcash::SaplingPaymentAddress &zaddr) const {
|
void operator()(const libzcash::SaplingPaymentAddress &zaddr) const {
|
||||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
ss << zaddr;
|
ss << zaddr;
|
||||||
|
@ -247,7 +255,7 @@ const size_t ConvertedSaplingExtendedFullViewingKeySize = (ZIP32_XFVK_SIZE * 8 +
|
||||||
const size_t ConvertedSaplingExtendedSpendingKeySize = (ZIP32_XSK_SIZE * 8 + 4) / 5;
|
const size_t ConvertedSaplingExtendedSpendingKeySize = (ZIP32_XSK_SIZE * 8 + 4) / 5;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
CTxDestination KeyIO::DecodeDestination(const std::string& str)
|
CTxDestination KeyIO::DecodeDestination(const std::string& str) const
|
||||||
{
|
{
|
||||||
std::vector<unsigned char> data;
|
std::vector<unsigned char> data;
|
||||||
uint160 hash;
|
uint160 hash;
|
||||||
|
@ -271,7 +279,7 @@ CTxDestination KeyIO::DecodeDestination(const std::string& str)
|
||||||
return CNoDestination();
|
return CNoDestination();
|
||||||
};
|
};
|
||||||
|
|
||||||
CKey KeyIO::DecodeSecret(const std::string& str)
|
CKey KeyIO::DecodeSecret(const std::string& str) const
|
||||||
{
|
{
|
||||||
CKey key;
|
CKey key;
|
||||||
std::vector<unsigned char> data;
|
std::vector<unsigned char> data;
|
||||||
|
@ -287,7 +295,7 @@ CKey KeyIO::DecodeSecret(const std::string& str)
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string KeyIO::EncodeSecret(const CKey& key)
|
std::string KeyIO::EncodeSecret(const CKey& key) const
|
||||||
{
|
{
|
||||||
assert(key.IsValid());
|
assert(key.IsValid());
|
||||||
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::SECRET_KEY);
|
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::SECRET_KEY);
|
||||||
|
@ -300,7 +308,7 @@ std::string KeyIO::EncodeSecret(const CKey& key)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
CExtPubKey KeyIO::DecodeExtPubKey(const std::string& str)
|
CExtPubKey KeyIO::DecodeExtPubKey(const std::string& str) const
|
||||||
{
|
{
|
||||||
CExtPubKey key;
|
CExtPubKey key;
|
||||||
std::vector<unsigned char> data;
|
std::vector<unsigned char> data;
|
||||||
|
@ -313,7 +321,7 @@ CExtPubKey KeyIO::DecodeExtPubKey(const std::string& str)
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string KeyIO::EncodeExtPubKey(const CExtPubKey& key)
|
std::string KeyIO::EncodeExtPubKey(const CExtPubKey& key) const
|
||||||
{
|
{
|
||||||
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::EXT_PUBLIC_KEY);
|
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::EXT_PUBLIC_KEY);
|
||||||
size_t size = data.size();
|
size_t size = data.size();
|
||||||
|
@ -323,7 +331,7 @@ std::string KeyIO::EncodeExtPubKey(const CExtPubKey& key)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
CExtKey KeyIO::DecodeExtKey(const std::string& str)
|
CExtKey KeyIO::DecodeExtKey(const std::string& str) const
|
||||||
{
|
{
|
||||||
CExtKey key;
|
CExtKey key;
|
||||||
std::vector<unsigned char> data;
|
std::vector<unsigned char> data;
|
||||||
|
@ -336,7 +344,7 @@ CExtKey KeyIO::DecodeExtKey(const std::string& str)
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string KeyIO::EncodeExtKey(const CExtKey& key)
|
std::string KeyIO::EncodeExtKey(const CExtKey& key) const
|
||||||
{
|
{
|
||||||
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::EXT_SECRET_KEY);
|
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::EXT_SECRET_KEY);
|
||||||
size_t size = data.size();
|
size_t size = data.size();
|
||||||
|
@ -347,17 +355,17 @@ std::string KeyIO::EncodeExtKey(const CExtKey& key)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string KeyIO::EncodeDestination(const CTxDestination& dest)
|
std::string KeyIO::EncodeDestination(const CTxDestination& dest) const
|
||||||
{
|
{
|
||||||
return std::visit(DestinationEncoder(keyConstants), dest);
|
return std::visit(DestinationEncoder(keyConstants), dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KeyIO::IsValidDestinationString(const std::string& str)
|
bool KeyIO::IsValidDestinationString(const std::string& str) const
|
||||||
{
|
{
|
||||||
return IsValidDestination(DecodeDestination(str));
|
return IsValidDestination(DecodeDestination(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string KeyIO::EncodePaymentAddress(const libzcash::PaymentAddress& zaddr)
|
std::string KeyIO::EncodePaymentAddress(const libzcash::PaymentAddress& zaddr) const
|
||||||
{
|
{
|
||||||
return std::visit(PaymentAddressEncoder(keyConstants), zaddr);
|
return std::visit(PaymentAddressEncoder(keyConstants), zaddr);
|
||||||
}
|
}
|
||||||
|
@ -433,71 +441,12 @@ std::optional<T1> DecodeAny(
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
std::optional<libzcash::PaymentAddress> KeyIO::DecodePaymentAddress(const std::string& str) const
|
||||||
* `raw` MUST be 43 bytes.
|
|
||||||
*/
|
|
||||||
static bool AddSaplingReceiver(void* ua, const unsigned char* raw)
|
|
||||||
{
|
|
||||||
CDataStream ss(
|
|
||||||
reinterpret_cast<const char*>(raw),
|
|
||||||
reinterpret_cast<const char*>(raw + 43),
|
|
||||||
SER_NETWORK,
|
|
||||||
PROTOCOL_VERSION);
|
|
||||||
libzcash::SaplingPaymentAddress receiver;
|
|
||||||
ss >> receiver;
|
|
||||||
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `raw` MUST be 20 bytes.
|
|
||||||
*/
|
|
||||||
static bool AddP2SHReceiver(void* ua, const unsigned char* raw)
|
|
||||||
{
|
|
||||||
CDataStream ss(
|
|
||||||
reinterpret_cast<const char*>(raw),
|
|
||||||
reinterpret_cast<const char*>(raw + 20),
|
|
||||||
SER_NETWORK,
|
|
||||||
PROTOCOL_VERSION);
|
|
||||||
CScriptID receiver;
|
|
||||||
ss >> receiver;
|
|
||||||
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `raw` MUST be 20 bytes.
|
|
||||||
*/
|
|
||||||
static bool AddP2PKHReceiver(void* ua, const unsigned char* raw)
|
|
||||||
{
|
|
||||||
CDataStream ss(
|
|
||||||
reinterpret_cast<const char*>(raw),
|
|
||||||
reinterpret_cast<const char*>(raw + 20),
|
|
||||||
SER_NETWORK,
|
|
||||||
PROTOCOL_VERSION);
|
|
||||||
CKeyID receiver;
|
|
||||||
ss >> receiver;
|
|
||||||
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool AddUnknownReceiver(void* ua, uint32_t typecode, const unsigned char* data, size_t len)
|
|
||||||
{
|
|
||||||
libzcash::UnknownReceiver receiver(typecode, std::vector(data, data + len));
|
|
||||||
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<libzcash::PaymentAddress> KeyIO::DecodePaymentAddress(const std::string& str)
|
|
||||||
{
|
{
|
||||||
// Try parsing as a Unified Address.
|
// Try parsing as a Unified Address.
|
||||||
libzcash::UnifiedAddress ua;
|
auto ua = libzcash::UnifiedAddress::Parse(keyConstants, str);
|
||||||
if (zcash_address_parse_unified(
|
if (ua.has_value()) {
|
||||||
str.c_str(),
|
return ua.value();
|
||||||
keyConstants.NetworkIDString().c_str(),
|
|
||||||
&ua,
|
|
||||||
AddSaplingReceiver,
|
|
||||||
AddP2SHReceiver,
|
|
||||||
AddP2PKHReceiver,
|
|
||||||
AddUnknownReceiver)
|
|
||||||
) {
|
|
||||||
return ua;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try parsing as a Sapling address
|
// Try parsing as a Sapling address
|
||||||
|
@ -535,16 +484,17 @@ std::optional<libzcash::PaymentAddress> KeyIO::DecodePaymentAddress(const std::s
|
||||||
}, DecodeDestination(str));
|
}, DecodeDestination(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KeyIO::IsValidPaymentAddressString(const std::string& str) {
|
bool KeyIO::IsValidPaymentAddressString(const std::string& str) const
|
||||||
|
{
|
||||||
return DecodePaymentAddress(str).has_value();
|
return DecodePaymentAddress(str).has_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string KeyIO::EncodeViewingKey(const libzcash::ViewingKey& vk)
|
std::string KeyIO::EncodeViewingKey(const libzcash::ViewingKey& vk) const
|
||||||
{
|
{
|
||||||
return std::visit(ViewingKeyEncoder(keyConstants), vk);
|
return std::visit(ViewingKeyEncoder(keyConstants), vk);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<libzcash::ViewingKey> KeyIO::DecodeViewingKey(const std::string& str)
|
std::optional<libzcash::ViewingKey> KeyIO::DecodeViewingKey(const std::string& str) const
|
||||||
{
|
{
|
||||||
// Try parsing as a Unified full viewing key
|
// Try parsing as a Unified full viewing key
|
||||||
auto ufvk = libzcash::UnifiedFullViewingKey::Decode(str, keyConstants);
|
auto ufvk = libzcash::UnifiedFullViewingKey::Decode(str, keyConstants);
|
||||||
|
@ -563,12 +513,12 @@ std::optional<libzcash::ViewingKey> KeyIO::DecodeViewingKey(const std::string& s
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string KeyIO::EncodeSpendingKey(const libzcash::SpendingKey& zkey)
|
std::string KeyIO::EncodeSpendingKey(const libzcash::SpendingKey& zkey) const
|
||||||
{
|
{
|
||||||
return std::visit(SpendingKeyEncoder(keyConstants), zkey);
|
return std::visit(SpendingKeyEncoder(keyConstants), zkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<libzcash::SpendingKey> KeyIO::DecodeSpendingKey(const std::string& str)
|
std::optional<libzcash::SpendingKey> KeyIO::DecodeSpendingKey(const std::string& str) const
|
||||||
{
|
{
|
||||||
|
|
||||||
return DecodeAny<libzcash::SpendingKey,
|
return DecodeAny<libzcash::SpendingKey,
|
||||||
|
|
32
src/key_io.h
32
src/key_io.h
|
@ -23,28 +23,28 @@ private:
|
||||||
public:
|
public:
|
||||||
KeyIO(const KeyConstants& keyConstants): keyConstants(keyConstants) { }
|
KeyIO(const KeyConstants& keyConstants): keyConstants(keyConstants) { }
|
||||||
|
|
||||||
CKey DecodeSecret(const std::string& str);
|
CKey DecodeSecret(const std::string& str) const;
|
||||||
std::string EncodeSecret(const CKey& key);
|
std::string EncodeSecret(const CKey& key) const;
|
||||||
|
|
||||||
CExtKey DecodeExtKey(const std::string& str);
|
CExtKey DecodeExtKey(const std::string& str) const;
|
||||||
std::string EncodeExtKey(const CExtKey& extkey);
|
std::string EncodeExtKey(const CExtKey& extkey) const;
|
||||||
CExtPubKey DecodeExtPubKey(const std::string& str);
|
CExtPubKey DecodeExtPubKey(const std::string& str) const;
|
||||||
std::string EncodeExtPubKey(const CExtPubKey& extpubkey);
|
std::string EncodeExtPubKey(const CExtPubKey& extpubkey) const;
|
||||||
|
|
||||||
std::string EncodeDestination(const CTxDestination& dest);
|
std::string EncodeDestination(const CTxDestination& dest) const;
|
||||||
CTxDestination DecodeDestination(const std::string& str);
|
CTxDestination DecodeDestination(const std::string& str) const;
|
||||||
|
|
||||||
bool IsValidDestinationString(const std::string& str);
|
bool IsValidDestinationString(const std::string& str) const;
|
||||||
|
|
||||||
std::string EncodePaymentAddress(const libzcash::PaymentAddress& zaddr);
|
std::string EncodePaymentAddress(const libzcash::PaymentAddress& zaddr) const;
|
||||||
std::optional<libzcash::PaymentAddress> DecodePaymentAddress(const std::string& str);
|
std::optional<libzcash::PaymentAddress> DecodePaymentAddress(const std::string& str) const;
|
||||||
bool IsValidPaymentAddressString(const std::string& str);
|
bool IsValidPaymentAddressString(const std::string& str) const;
|
||||||
|
|
||||||
std::string EncodeViewingKey(const libzcash::ViewingKey& vk);
|
std::string EncodeViewingKey(const libzcash::ViewingKey& vk) const;
|
||||||
std::optional<libzcash::ViewingKey> DecodeViewingKey(const std::string& str);
|
std::optional<libzcash::ViewingKey> DecodeViewingKey(const std::string& str) const;
|
||||||
|
|
||||||
std::string EncodeSpendingKey(const libzcash::SpendingKey& zkey);
|
std::string EncodeSpendingKey(const libzcash::SpendingKey& zkey) const;
|
||||||
std::optional<libzcash::SpendingKey> DecodeSpendingKey(const std::string& str);
|
std::optional<libzcash::SpendingKey> DecodeSpendingKey(const std::string& str) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BITCOIN_KEY_IO_H
|
#endif // BITCOIN_KEY_IO_H
|
||||||
|
|
182
src/keystore.cpp
182
src/keystore.cpp
|
@ -252,6 +252,10 @@ bool CBasicKeyStore::GetSproutViewingKey(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Sapling Keys
|
||||||
|
//
|
||||||
|
|
||||||
bool CBasicKeyStore::GetSaplingFullViewingKey(
|
bool CBasicKeyStore::GetSaplingFullViewingKey(
|
||||||
const libzcash::SaplingIncomingViewingKey &ivk,
|
const libzcash::SaplingIncomingViewingKey &ivk,
|
||||||
libzcash::SaplingExtendedFullViewingKey &extfvkOut) const
|
libzcash::SaplingExtendedFullViewingKey &extfvkOut) const
|
||||||
|
@ -308,14 +312,26 @@ bool CBasicKeyStore::AddUnifiedFullViewingKey(
|
||||||
{
|
{
|
||||||
LOCK(cs_KeyStore);
|
LOCK(cs_KeyStore);
|
||||||
|
|
||||||
|
auto ufvkId = ufvk.GetKeyID();
|
||||||
|
|
||||||
|
// Add the Orchard component of the UFVK to the wallet.
|
||||||
|
auto orchardKey = ufvk.GetOrchardKey();
|
||||||
|
if (orchardKey.has_value()) {
|
||||||
|
auto ivk = orchardKey.value().ToIncomingViewingKey();
|
||||||
|
mapOrchardKeyUnified.insert(std::make_pair(ivk, ufvkId));
|
||||||
|
|
||||||
|
auto ivkInternal = orchardKey.value().ToInternalIncomingViewingKey();
|
||||||
|
mapOrchardKeyUnified.insert(std::make_pair(ivkInternal, ufvkId));
|
||||||
|
}
|
||||||
|
|
||||||
// Add the Sapling component of the UFVK to the wallet.
|
// Add the Sapling component of the UFVK to the wallet.
|
||||||
auto saplingKey = ufvk.GetSaplingKey();
|
auto saplingKey = ufvk.GetSaplingKey();
|
||||||
if (saplingKey.has_value()) {
|
if (saplingKey.has_value()) {
|
||||||
auto ivk = saplingKey.value().ToIncomingViewingKey();
|
auto ivk = saplingKey.value().ToIncomingViewingKey();
|
||||||
mapSaplingKeyUnified.insert(std::make_pair(ivk, ufvk.GetKeyID()));
|
mapSaplingKeyUnified.insert(std::make_pair(ivk, ufvkId));
|
||||||
|
|
||||||
auto changeIvk = saplingKey.value().GetChangeIVK();
|
auto changeIvk = saplingKey.value().GetChangeIVK();
|
||||||
mapSaplingKeyUnified.insert(std::make_pair(changeIvk, ufvk.GetKeyID()));
|
mapSaplingKeyUnified.insert(std::make_pair(changeIvk, ufvkId));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can't reasonably add the transparent component here, because
|
// We can't reasonably add the transparent component here, because
|
||||||
|
@ -325,7 +341,7 @@ bool CBasicKeyStore::AddUnifiedFullViewingKey(
|
||||||
// transparent part of the address must be added to the keystore.
|
// transparent part of the address must be added to the keystore.
|
||||||
|
|
||||||
// Add the UFVK by key identifier.
|
// Add the UFVK by key identifier.
|
||||||
mapUnifiedFullViewingKeys.insert({ufvk.GetKeyID(), ufvk});
|
mapUnifiedFullViewingKeys.insert({ufvkId, ufvk});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -367,12 +383,55 @@ std::optional<libzcash::ZcashdUnifiedFullViewingKey> CBasicKeyStore::GetUnifiedF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
std::optional<AddressUFVKMetadata>
|
||||||
CBasicKeyStore::GetUFVKMetadataForReceiver(const libzcash::Receiver& receiver) const
|
CBasicKeyStore::GetUFVKMetadataForReceiver(const libzcash::Receiver& receiver) const
|
||||||
{
|
{
|
||||||
return std::visit(FindUFVKId(*this), receiver);
|
return std::visit(FindUFVKId(*this), receiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<AddressUFVKMetadata>
|
||||||
|
CBasicKeyStore::GetUFVKMetadataForAddress(const libzcash::UnifiedAddress& addr) const
|
||||||
|
{
|
||||||
|
std::optional<libzcash::UFVKId> ufvkId;
|
||||||
|
std::optional<libzcash::diversifier_index_t> j;
|
||||||
|
bool jConflict = false;
|
||||||
|
for (const auto& receiver : addr) {
|
||||||
|
auto rmeta = GetUFVKMetadataForReceiver(receiver);
|
||||||
|
if (rmeta.has_value()) {
|
||||||
|
// We should never generate unified addresses with internal receivers
|
||||||
|
assert(rmeta.value().IsExternalAddress());
|
||||||
|
|
||||||
|
if (ufvkId.has_value()) {
|
||||||
|
// If the unified address contains receivers that are associated with
|
||||||
|
// different UFVKs, we cannot return a singular value.
|
||||||
|
if (rmeta.value().GetUFVKId() != ufvkId.value()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rmeta.value().GetDiversifierIndex().has_value()) {
|
||||||
|
if (j.has_value()) {
|
||||||
|
if (rmeta.value().GetDiversifierIndex().value() != j.value()) {
|
||||||
|
jConflict = true;
|
||||||
|
j = std::nullopt;
|
||||||
|
}
|
||||||
|
} else if (!jConflict) {
|
||||||
|
j = rmeta.value().GetDiversifierIndex().value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ufvkId = rmeta.value().GetUFVKId();
|
||||||
|
j = rmeta.value().GetDiversifierIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ufvkId.has_value()) {
|
||||||
|
return AddressUFVKMetadata(ufvkId.value(), j, true);
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<libzcash::UFVKId> CBasicKeyStore::GetUFVKIdForViewingKey(const libzcash::ViewingKey& vk) const
|
std::optional<libzcash::UFVKId> CBasicKeyStore::GetUFVKIdForViewingKey(const libzcash::ViewingKey& vk) const
|
||||||
{
|
{
|
||||||
std::optional<libzcash::UFVKId> result;
|
std::optional<libzcash::UFVKId> result;
|
||||||
|
@ -386,6 +445,15 @@ std::optional<libzcash::UFVKId> CBasicKeyStore::GetUFVKIdForViewingKey(const lib
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[&](const libzcash::UnifiedFullViewingKey& ufvk) {
|
[&](const libzcash::UnifiedFullViewingKey& ufvk) {
|
||||||
|
const auto orchardFvk = ufvk.GetOrchardKey();
|
||||||
|
if (orchardFvk.has_value()) {
|
||||||
|
const auto orchardIvk = orchardFvk.value().ToIncomingViewingKey();
|
||||||
|
const auto ufvkId = mapOrchardKeyUnified.find(orchardIvk);
|
||||||
|
if (ufvkId != mapOrchardKeyUnified.end()) {
|
||||||
|
result = ufvkId->second;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
const auto saplingDfvk = ufvk.GetSaplingKey();
|
const auto saplingDfvk = ufvk.GetSaplingKey();
|
||||||
if (saplingDfvk.has_value()) {
|
if (saplingDfvk.has_value()) {
|
||||||
const auto saplingIvk = saplingDfvk.value().ToIncomingViewingKey();
|
const auto saplingIvk = saplingDfvk.value().ToIncomingViewingKey();
|
||||||
|
@ -399,39 +467,81 @@ std::optional<libzcash::UFVKId> CBasicKeyStore::GetUFVKIdForViewingKey(const lib
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
//
|
||||||
FindUFVKId::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
|
// FindUFVKId :: (KeyStore, Receiver) -> std::optional<AddressUFVKMetadata>
|
||||||
const auto saplingIvk = keystore.mapSaplingIncomingViewingKeys.find(saplingAddr);
|
//
|
||||||
if (saplingIvk != keystore.mapSaplingIncomingViewingKeys.end()) {
|
|
||||||
const auto ufvkId = keystore.mapSaplingKeyUnified.find(saplingIvk->second);
|
std::optional<AddressUFVKMetadata> FindUFVKId::operator()(const libzcash::OrchardRawAddress& orchardAddr) const {
|
||||||
if (ufvkId != keystore.mapSaplingKeyUnified.end()) {
|
for (const auto& [k, v] : keystore.mapUnifiedFullViewingKeys) {
|
||||||
return std::make_pair(ufvkId->second, std::nullopt);
|
auto fvk = v.GetOrchardKey();
|
||||||
} else {
|
if (fvk.has_value()) {
|
||||||
return std::nullopt;
|
auto d_idx = fvk.value().DecryptDiversifier(orchardAddr);
|
||||||
|
if (d_idx.has_value()) {
|
||||||
|
return AddressUFVKMetadata(k, d_idx->first, d_idx->second);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
}
|
return std::nullopt;
|
||||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
}
|
||||||
FindUFVKId::operator()(const CScriptID& scriptId) const {
|
std::optional<AddressUFVKMetadata> FindUFVKId::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
|
||||||
const auto metadata = keystore.mapP2SHUnified.find(scriptId);
|
const auto saplingIvk = keystore.mapSaplingIncomingViewingKeys.find(saplingAddr);
|
||||||
if (metadata != keystore.mapP2SHUnified.end()) {
|
if (saplingIvk != keystore.mapSaplingIncomingViewingKeys.end()) {
|
||||||
return metadata->second;
|
// We have either generated this as a receiver via `z_getaddressforaccount` or a
|
||||||
} else {
|
// legacy Sapling address via `z_getnewaddress`, or we have previously detected
|
||||||
return std::nullopt;
|
// this via trial-decryption of a note.
|
||||||
}
|
const auto ufvkId = keystore.mapSaplingKeyUnified.find(saplingIvk->second);
|
||||||
}
|
if (ufvkId != keystore.mapSaplingKeyUnified.end()) {
|
||||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
// We know that we have a UFVK, and that it has a Sapling key that
|
||||||
FindUFVKId::operator()(const CKeyID& keyId) const {
|
// produced this address, so decrypt the diversifier to determine
|
||||||
const auto metadata = keystore.mapP2PKHUnified.find(keyId);
|
// whether it was an internal or external address
|
||||||
if (metadata != keystore.mapP2PKHUnified.end()) {
|
auto ufvk = keystore.GetUnifiedFullViewingKey(ufvkId->second).value();
|
||||||
return metadata->second;
|
auto saplingKey = ufvk.GetSaplingKey().value();
|
||||||
} else {
|
auto d_idx = saplingKey.DecryptDiversifier(saplingAddr).value();
|
||||||
return std::nullopt;
|
return AddressUFVKMetadata(ufvkId->second, d_idx.first, d_idx.second);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
// If we have the addr -> ivk map entry but not the ivk -> UFVK map entry,
|
||||||
FindUFVKId::operator()(const libzcash::UnknownReceiver& receiver) const {
|
// then this is definitely a legacy Sapling address.
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We haven't generated this receiver via `z_getaddressforaccount` (or this is a
|
||||||
|
// recovery from a backed-up mnemonic which doesn't store receiver types selected by
|
||||||
|
// users). Trial-decrypt the diversifier of the Sapling address with every UFVK in the
|
||||||
|
// wallet, to check directly if it belongs to any of them.
|
||||||
|
for (const auto& [k, v] : keystore.mapUnifiedFullViewingKeys) {
|
||||||
|
auto dfvk = v.GetSaplingKey();
|
||||||
|
if (dfvk.has_value()) {
|
||||||
|
auto d_idx = dfvk.value().DecryptDiversifier(saplingAddr);
|
||||||
|
if (d_idx.has_value()) {
|
||||||
|
return AddressUFVKMetadata(k, d_idx->first, d_idx->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We definitely don't know of any UFVK linked to this Sapling address.
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
std::optional<AddressUFVKMetadata> FindUFVKId::operator()(const CScriptID& scriptId) const {
|
||||||
|
const auto metadata = keystore.mapP2SHUnified.find(scriptId);
|
||||||
|
if (metadata != keystore.mapP2SHUnified.end()) {
|
||||||
|
// At present we never generate transparent internal addresses, so this
|
||||||
|
// must be an external address
|
||||||
|
return AddressUFVKMetadata(metadata->second.first, metadata->second.second, true);
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::optional<AddressUFVKMetadata> FindUFVKId::operator()(const CKeyID& keyId) const {
|
||||||
|
const auto metadata = keystore.mapP2PKHUnified.find(keyId);
|
||||||
|
if (metadata != keystore.mapP2PKHUnified.end()) {
|
||||||
|
// At present we never generate transparent internal addresses, so this
|
||||||
|
// must be an external address
|
||||||
|
return AddressUFVKMetadata(metadata->second.first, metadata->second.second, true);
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::optional<AddressUFVKMetadata> FindUFVKId::operator()(const libzcash::UnknownReceiver& receiver) const {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,20 @@
|
||||||
|
|
||||||
#include <boost/signals2/signal.hpp>
|
#include <boost/signals2/signal.hpp>
|
||||||
|
|
||||||
|
class AddressUFVKMetadata {
|
||||||
|
private:
|
||||||
|
libzcash::UFVKId ufvkId;
|
||||||
|
std::optional<libzcash::diversifier_index_t> j;
|
||||||
|
bool externalAddress;
|
||||||
|
public:
|
||||||
|
AddressUFVKMetadata(libzcash::UFVKId ufvkId, std::optional<libzcash::diversifier_index_t> j, bool externalAddress)
|
||||||
|
: ufvkId(ufvkId), j(j), externalAddress(externalAddress) {}
|
||||||
|
|
||||||
|
libzcash::UFVKId GetUFVKId() const { return ufvkId; }
|
||||||
|
std::optional<libzcash::diversifier_index_t> GetDiversifierIndex() const { return j; }
|
||||||
|
bool IsExternalAddress() const { return externalAddress; }
|
||||||
|
};
|
||||||
|
|
||||||
/** A virtual base class for key stores */
|
/** A virtual base class for key stores */
|
||||||
class CKeyStore
|
class CKeyStore
|
||||||
{
|
{
|
||||||
|
@ -125,18 +139,21 @@ public:
|
||||||
const libzcash::UnifiedAddress& ua) = 0;
|
const libzcash::UnifiedAddress& ua) = 0;
|
||||||
|
|
||||||
virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey(
|
virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey(
|
||||||
const libzcash::UFVKId& keyId
|
const libzcash::UFVKId& keyId) const = 0;
|
||||||
) const = 0;
|
|
||||||
|
|
||||||
virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
virtual std::optional<AddressUFVKMetadata> GetUFVKMetadataForReceiver(
|
||||||
GetUFVKMetadataForReceiver(
|
const libzcash::Receiver& receiver) const = 0;
|
||||||
const libzcash::Receiver& receiver
|
|
||||||
) const = 0;
|
|
||||||
|
|
||||||
virtual std::optional<libzcash::UFVKId>
|
/**
|
||||||
GetUFVKIdForViewingKey(
|
* If all the receivers of the specified address correspond to a single
|
||||||
const libzcash::ViewingKey& vk
|
* UFVK, return that key's metadata. If all the receivers correspond to
|
||||||
) const = 0;
|
* the same diversifier index, that diversifier index is also returned.
|
||||||
|
*/
|
||||||
|
virtual std::optional<AddressUFVKMetadata> GetUFVKMetadataForAddress(
|
||||||
|
const libzcash::UnifiedAddress& addr) const = 0;
|
||||||
|
|
||||||
|
virtual std::optional<libzcash::UFVKId> GetUFVKIdForViewingKey(
|
||||||
|
const libzcash::ViewingKey& vk) const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::map<CKeyID, CKey> KeyMap;
|
typedef std::map<CKeyID, CKey> KeyMap;
|
||||||
|
@ -185,6 +202,7 @@ protected:
|
||||||
std::map<CKeyID, std::pair<libzcash::UFVKId, libzcash::diversifier_index_t>> mapP2PKHUnified;
|
std::map<CKeyID, std::pair<libzcash::UFVKId, libzcash::diversifier_index_t>> mapP2PKHUnified;
|
||||||
std::map<CScriptID, std::pair<libzcash::UFVKId, libzcash::diversifier_index_t>> mapP2SHUnified;
|
std::map<CScriptID, std::pair<libzcash::UFVKId, libzcash::diversifier_index_t>> mapP2SHUnified;
|
||||||
std::map<libzcash::SaplingIncomingViewingKey, libzcash::UFVKId> mapSaplingKeyUnified;
|
std::map<libzcash::SaplingIncomingViewingKey, libzcash::UFVKId> mapSaplingKeyUnified;
|
||||||
|
std::map<libzcash::OrchardIncomingViewingKey, libzcash::UFVKId> mapOrchardKeyUnified;
|
||||||
std::map<libzcash::UFVKId, libzcash::ZcashdUnifiedFullViewingKey> mapUnifiedFullViewingKeys;
|
std::map<libzcash::UFVKId, libzcash::ZcashdUnifiedFullViewingKey> mapUnifiedFullViewingKeys;
|
||||||
|
|
||||||
friend class FindUFVKId;
|
friend class FindUFVKId;
|
||||||
|
@ -373,15 +391,34 @@ public:
|
||||||
virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey(
|
virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey(
|
||||||
const libzcash::UFVKId& keyId) const;
|
const libzcash::UFVKId& keyId) const;
|
||||||
|
|
||||||
virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
virtual std::optional<AddressUFVKMetadata> GetUFVKMetadataForReceiver(
|
||||||
GetUFVKMetadataForReceiver(
|
const libzcash::Receiver& receiver) const;
|
||||||
const libzcash::Receiver& receiver
|
|
||||||
) const;
|
|
||||||
|
|
||||||
virtual std::optional<libzcash::UFVKId>
|
std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUFVKForReceiver(
|
||||||
GetUFVKIdForViewingKey(
|
const libzcash::Receiver& receiver) const {
|
||||||
const libzcash::ViewingKey& vk
|
auto ufvkMeta = GetUFVKMetadataForReceiver(receiver);
|
||||||
) const;
|
if (ufvkMeta.has_value()) {
|
||||||
|
return GetUnifiedFullViewingKey(ufvkMeta.value().GetUFVKId());
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::optional<AddressUFVKMetadata> GetUFVKMetadataForAddress(
|
||||||
|
const libzcash::UnifiedAddress& addr) const;
|
||||||
|
|
||||||
|
std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUFVKForAddress(
|
||||||
|
const libzcash::UnifiedAddress& addr) const {
|
||||||
|
auto ufvkMeta = GetUFVKMetadataForAddress(addr);
|
||||||
|
if (ufvkMeta.has_value()) {
|
||||||
|
return GetUnifiedFullViewingKey(ufvkMeta.value().GetUFVKId());
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::optional<libzcash::UFVKId> GetUFVKIdForViewingKey(
|
||||||
|
const libzcash::ViewingKey& vk) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial;
|
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial;
|
||||||
|
@ -398,14 +435,11 @@ private:
|
||||||
public:
|
public:
|
||||||
FindUFVKId(const CBasicKeyStore& keystore): keystore(keystore) {}
|
FindUFVKId(const CBasicKeyStore& keystore): keystore(keystore) {}
|
||||||
|
|
||||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
std::optional<AddressUFVKMetadata> operator()(const libzcash::OrchardRawAddress& orchardAddr) const;
|
||||||
operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const;
|
std::optional<AddressUFVKMetadata> operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const;
|
||||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
std::optional<AddressUFVKMetadata> operator()(const CScriptID& scriptId) const;
|
||||||
operator()(const CScriptID& scriptId) const;
|
std::optional<AddressUFVKMetadata> operator()(const CKeyID& keyId) const;
|
||||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
std::optional<AddressUFVKMetadata> operator()(const libzcash::UnknownReceiver& receiver) const;
|
||||||
operator()(const CKeyID& keyId) const;
|
|
||||||
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
|
|
||||||
operator()(const libzcash::UnknownReceiver& receiver) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BITCOIN_KEYSTORE_H
|
#endif // BITCOIN_KEYSTORE_H
|
||||||
|
|
45
src/main.cpp
45
src/main.cpp
|
@ -66,8 +66,10 @@ BlockMap mapBlockIndex;
|
||||||
CChain chainActive;
|
CChain chainActive;
|
||||||
CBlockIndex *pindexBestHeader = NULL;
|
CBlockIndex *pindexBestHeader = NULL;
|
||||||
static std::atomic<int64_t> nTimeBestReceived(0); // Used only to inform the wallet of when we last received a block
|
static std::atomic<int64_t> nTimeBestReceived(0); // Used only to inform the wallet of when we last received a block
|
||||||
CWaitableCriticalSection csBestBlock;
|
CWaitableCriticalSection g_best_block_mutex;
|
||||||
CConditionVariable cvBlockChange;
|
CConditionVariable g_best_block_cv;
|
||||||
|
uint256 g_best_block;
|
||||||
|
int g_best_block_height;
|
||||||
int nScriptCheckThreads = 0;
|
int nScriptCheckThreads = 0;
|
||||||
std::atomic_bool fImporting(false);
|
std::atomic_bool fImporting(false);
|
||||||
std::atomic_bool fReindex(false);
|
std::atomic_bool fReindex(false);
|
||||||
|
@ -2945,6 +2947,17 @@ static DisconnectResult DisconnectBlock(const CBlock& block, CValidationState& s
|
||||||
view.PopAnchor(SaplingMerkleTree::empty_root(), SAPLING);
|
view.PopAnchor(SaplingMerkleTree::empty_root(), SAPLING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the old best Orchard anchor back. We can get this from the
|
||||||
|
// `hashFinalOrchardRoot` of the last block. However, if the last
|
||||||
|
// block was not on or after the Orchard activation height, this
|
||||||
|
// will be set to `null`. For logical consistency, in this case we
|
||||||
|
// set the last anchor to the empty root.
|
||||||
|
if (chainparams.GetConsensus().NetworkUpgradeActive(pindex->pprev->nHeight, Consensus::UPGRADE_NU5)) {
|
||||||
|
view.PopAnchor(pindex->pprev->hashFinalOrchardRoot, ORCHARD);
|
||||||
|
} else {
|
||||||
|
view.PopAnchor(OrchardMerkleFrontier::empty_root(), ORCHARD);
|
||||||
|
}
|
||||||
|
|
||||||
// This is guaranteed to be filled by LoadBlockIndex.
|
// This is guaranteed to be filled by LoadBlockIndex.
|
||||||
assert(pindex->nCachedBranchId);
|
assert(pindex->nCachedBranchId);
|
||||||
auto consensusBranchId = pindex->nCachedBranchId.value();
|
auto consensusBranchId = pindex->nCachedBranchId.value();
|
||||||
|
@ -3172,7 +3185,19 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||||
assert(view.GetSaplingAnchorAt(view.GetBestAnchor(SAPLING), sapling_tree));
|
assert(view.GetSaplingAnchorAt(view.GetBestAnchor(SAPLING), sapling_tree));
|
||||||
|
|
||||||
OrchardMerkleFrontier orchard_tree;
|
OrchardMerkleFrontier orchard_tree;
|
||||||
assert(view.GetOrchardAnchorAt(view.GetBestAnchor(ORCHARD), orchard_tree));
|
if (pindex->pprev && chainparams.GetConsensus().NetworkUpgradeActive(pindex->pprev->nHeight, Consensus::UPGRADE_NU5)) {
|
||||||
|
// Verify that the view's current state corresponds to the previous block.
|
||||||
|
assert(pindex->pprev->hashFinalOrchardRoot == view.GetBestAnchor(ORCHARD));
|
||||||
|
// We only call ConnectBlock() on top of the active chain's tip.
|
||||||
|
assert(!pindex->pprev->hashFinalOrchardRoot.IsNull());
|
||||||
|
|
||||||
|
assert(view.GetOrchardAnchorAt(pindex->pprev->hashFinalOrchardRoot, orchard_tree));
|
||||||
|
} else {
|
||||||
|
if (pindex->pprev) {
|
||||||
|
assert(pindex->pprev->hashFinalOrchardRoot.IsNull());
|
||||||
|
}
|
||||||
|
assert(view.GetOrchardAnchorAt(OrchardMerkleFrontier::empty_root(), orchard_tree));
|
||||||
|
}
|
||||||
|
|
||||||
// Grab the consensus branch ID for this block and its parent
|
// Grab the consensus branch ID for this block and its parent
|
||||||
auto consensusBranchId = CurrentEpochBranchId(pindex->nHeight, chainparams.GetConsensus());
|
auto consensusBranchId = CurrentEpochBranchId(pindex->nHeight, chainparams.GetConsensus());
|
||||||
|
@ -3823,7 +3848,12 @@ void static UpdateTip(CBlockIndex *pindexNew, const CChainParams& chainParams) {
|
||||||
RenderPoolMetrics("sapling", saplingPool);
|
RenderPoolMetrics("sapling", saplingPool);
|
||||||
RenderPoolMetrics("transparent", transparentPool);
|
RenderPoolMetrics("transparent", transparentPool);
|
||||||
|
|
||||||
cvBlockChange.notify_all();
|
{
|
||||||
|
boost::unique_lock<boost::mutex> lock(g_best_block_mutex);
|
||||||
|
g_best_block = pindexNew->GetBlockHash();
|
||||||
|
g_best_block_height = pindexNew->nHeight;
|
||||||
|
g_best_block_cv.notify_all();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3841,6 +3871,7 @@ bool static DisconnectTip(CValidationState &state, const CChainParams& chainpara
|
||||||
// Apply the block atomically to the chain state.
|
// Apply the block atomically to the chain state.
|
||||||
uint256 sproutAnchorBeforeDisconnect = pcoinsTip->GetBestAnchor(SPROUT);
|
uint256 sproutAnchorBeforeDisconnect = pcoinsTip->GetBestAnchor(SPROUT);
|
||||||
uint256 saplingAnchorBeforeDisconnect = pcoinsTip->GetBestAnchor(SAPLING);
|
uint256 saplingAnchorBeforeDisconnect = pcoinsTip->GetBestAnchor(SAPLING);
|
||||||
|
uint256 orchardAnchorBeforeDisconnect = pcoinsTip->GetBestAnchor(ORCHARD);
|
||||||
int64_t nStart = GetTimeMicros();
|
int64_t nStart = GetTimeMicros();
|
||||||
{
|
{
|
||||||
CCoinsViewCache view(pcoinsTip);
|
CCoinsViewCache view(pcoinsTip);
|
||||||
|
@ -3852,6 +3883,7 @@ bool static DisconnectTip(CValidationState &state, const CChainParams& chainpara
|
||||||
LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001);
|
LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001);
|
||||||
uint256 sproutAnchorAfterDisconnect = pcoinsTip->GetBestAnchor(SPROUT);
|
uint256 sproutAnchorAfterDisconnect = pcoinsTip->GetBestAnchor(SPROUT);
|
||||||
uint256 saplingAnchorAfterDisconnect = pcoinsTip->GetBestAnchor(SAPLING);
|
uint256 saplingAnchorAfterDisconnect = pcoinsTip->GetBestAnchor(SAPLING);
|
||||||
|
uint256 orchardAnchorAfterDisconnect = pcoinsTip->GetBestAnchor(ORCHARD);
|
||||||
// Write the chain state to disk, if necessary.
|
// Write the chain state to disk, if necessary.
|
||||||
if (!FlushStateToDisk(chainparams, state, FLUSH_STATE_IF_NEEDED))
|
if (!FlushStateToDisk(chainparams, state, FLUSH_STATE_IF_NEEDED))
|
||||||
return false;
|
return false;
|
||||||
|
@ -3875,6 +3907,11 @@ bool static DisconnectTip(CValidationState &state, const CChainParams& chainpara
|
||||||
// in which case we don't want to evict from the mempool yet!
|
// in which case we don't want to evict from the mempool yet!
|
||||||
mempool.removeWithAnchor(saplingAnchorBeforeDisconnect, SAPLING);
|
mempool.removeWithAnchor(saplingAnchorBeforeDisconnect, SAPLING);
|
||||||
}
|
}
|
||||||
|
if (orchardAnchorBeforeDisconnect != orchardAnchorAfterDisconnect) {
|
||||||
|
// The anchor may not change between block disconnects,
|
||||||
|
// in which case we don't want to evict from the mempool yet!
|
||||||
|
mempool.removeWithAnchor(orchardAnchorBeforeDisconnect, ORCHARD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update chainActive and related variables.
|
// Update chainActive and related variables.
|
||||||
|
|
15
src/main.h
15
src/main.h
|
@ -160,8 +160,19 @@ extern BlockMap mapBlockIndex;
|
||||||
extern std::optional<uint64_t> last_block_num_txs;
|
extern std::optional<uint64_t> last_block_num_txs;
|
||||||
extern std::optional<uint64_t> last_block_size;
|
extern std::optional<uint64_t> last_block_size;
|
||||||
extern const std::string strMessageMagic;
|
extern const std::string strMessageMagic;
|
||||||
extern CWaitableCriticalSection csBestBlock;
|
|
||||||
extern CConditionVariable cvBlockChange;
|
//! These four variables are used to notify getblocktemplate RPC of new tips.
|
||||||
|
//! When UpdateTip() establishes a new tip (best block), it must awaken a
|
||||||
|
//! waiting getblocktemplate RPC (if there is one) immediately. But upon waking
|
||||||
|
//! up, getblocktemplate cannot call chainActive->Tip() because it does not
|
||||||
|
//! (and cannot) hold cs_main. So the g_best_block_height and g_best_block variables
|
||||||
|
//! (protected by g_best_block_mutex) provide the needed height and block
|
||||||
|
//! hash respectively to getblocktemplate without it requiring cs_main.
|
||||||
|
extern CWaitableCriticalSection g_best_block_mutex;
|
||||||
|
extern CConditionVariable g_best_block_cv;
|
||||||
|
extern int g_best_block_height;
|
||||||
|
extern uint256 g_best_block;
|
||||||
|
|
||||||
extern std::atomic_bool fImporting;
|
extern std::atomic_bool fImporting;
|
||||||
extern std::atomic_bool fReindex;
|
extern std::atomic_bool fReindex;
|
||||||
extern int nScriptCheckThreads;
|
extern int nScriptCheckThreads;
|
||||||
|
|
|
@ -50,6 +50,17 @@ void AtomicTimer::stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AtomicTimer::zeroize()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(mtx);
|
||||||
|
// only zeroize it if there's no more threads (same semantics as start())
|
||||||
|
if (threads < 1) {
|
||||||
|
start_time = 0;
|
||||||
|
total_time = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool AtomicTimer::running()
|
bool AtomicTimer::running()
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(mtx);
|
std::unique_lock<std::mutex> lock(mtx);
|
||||||
|
|
|
@ -52,6 +52,8 @@ public:
|
||||||
*/
|
*/
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
|
void zeroize();
|
||||||
|
|
||||||
bool running();
|
bool running();
|
||||||
|
|
||||||
uint64_t threadCount();
|
uint64_t threadCount();
|
||||||
|
|
118
src/miner.cpp
118
src/miner.cpp
|
@ -214,37 +214,87 @@ public:
|
||||||
return miner_reward + nFees;
|
return miner_reward + nFees;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComputeBindingSig(void* ctx) const {
|
void ComputeBindingSig(void* saplingCtx, std::optional<orchard::UnauthorizedBundle> orchardBundle) const {
|
||||||
// Empty output script.
|
// Empty output script.
|
||||||
uint256 dataToBeSigned;
|
uint256 dataToBeSigned;
|
||||||
CScript scriptCode;
|
|
||||||
try {
|
try {
|
||||||
// This is a shielded coinbase transaction, so the sighash is either pre-v5
|
if (orchardBundle.has_value()) {
|
||||||
// and doesn't use the allPrevOutputs field of PrecomputedTransactionData), or
|
// Orchard is only usable with v5+ transactions.
|
||||||
// v5+ and S.2 of ZIP 244 defers to T.2, causing allPrevOutputs to be ignored.
|
dataToBeSigned = ProduceZip244SignatureHash(mtx, {}, orchardBundle.value());
|
||||||
// We therefore can set it to the empty list here.
|
} else {
|
||||||
PrecomputedTransactionData txdata(mtx, {});
|
CScript scriptCode;
|
||||||
dataToBeSigned = SignatureHash(
|
PrecomputedTransactionData txdata(mtx, {});
|
||||||
scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0,
|
dataToBeSigned = SignatureHash(
|
||||||
CurrentEpochBranchId(nHeight, chainparams.GetConsensus()),
|
scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0,
|
||||||
txdata);
|
CurrentEpochBranchId(nHeight, chainparams.GetConsensus()),
|
||||||
|
txdata);
|
||||||
|
}
|
||||||
} catch (std::logic_error ex) {
|
} catch (std::logic_error ex) {
|
||||||
librustzcash_sapling_proving_ctx_free(ctx);
|
librustzcash_sapling_proving_ctx_free(saplingCtx);
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (orchardBundle.has_value()) {
|
||||||
|
auto authorizedBundle = orchardBundle.value().ProveAndSign({}, dataToBeSigned);
|
||||||
|
if (authorizedBundle.has_value()) {
|
||||||
|
mtx.orchardBundle = authorizedBundle.value();
|
||||||
|
} else {
|
||||||
|
librustzcash_sapling_proving_ctx_free(saplingCtx);
|
||||||
|
throw new std::runtime_error("Failed to create Orchard proof or signatures");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool success = librustzcash_sapling_binding_sig(
|
bool success = librustzcash_sapling_binding_sig(
|
||||||
ctx,
|
saplingCtx,
|
||||||
mtx.valueBalanceSapling,
|
mtx.valueBalanceSapling,
|
||||||
dataToBeSigned.begin(),
|
dataToBeSigned.begin(),
|
||||||
mtx.bindingSig.data());
|
mtx.bindingSig.data());
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
librustzcash_sapling_proving_ctx_free(ctx);
|
librustzcash_sapling_proving_ctx_free(saplingCtx);
|
||||||
throw new std::runtime_error("An error occurred computing the binding signature.");
|
throw new std::runtime_error("An error occurred computing the binding signature.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create Orchard output
|
||||||
|
void operator()(const libzcash::OrchardRawAddress &to) const {
|
||||||
|
auto ctx = librustzcash_sapling_proving_ctx_init();
|
||||||
|
|
||||||
|
// `enableSpends` must be set to `false` for coinbase transactions. This
|
||||||
|
// means the Orchard anchor is unconstrained, so we set it to the empty
|
||||||
|
// tree root via a null (all zeroes) uint256.
|
||||||
|
uint256 orchardAnchor;
|
||||||
|
auto builder = orchard::Builder(false, true, orchardAnchor);
|
||||||
|
|
||||||
|
// Shielded coinbase outputs must be recoverable with an all-zeroes ovk.
|
||||||
|
uint256 ovk;
|
||||||
|
auto miner_reward = SetFoundersRewardAndGetMinerValue(ctx);
|
||||||
|
builder.AddOutput(ovk, to, miner_reward, std::nullopt);
|
||||||
|
|
||||||
|
// orchard::Builder pads to two Actions, but does so using a "no OVK" policy for
|
||||||
|
// dummy outputs, which violates coinbase rules requiring all shielded outputs to
|
||||||
|
// be recoverable. We manually add a dummy output to sidestep this issue.
|
||||||
|
// TODO: If/when we have funding streams going to Orchard recipients, this dummy
|
||||||
|
// output can be removed.
|
||||||
|
RawHDSeed rawSeed(32, 0);
|
||||||
|
GetRandBytes(rawSeed.data(), 32);
|
||||||
|
auto dummyTo = libzcash::OrchardSpendingKey::ForAccount(HDSeed(rawSeed), Params().BIP44CoinType(), 0)
|
||||||
|
.ToFullViewingKey()
|
||||||
|
.ToIncomingViewingKey()
|
||||||
|
.Address(0);
|
||||||
|
builder.AddOutput(ovk, dummyTo, 0, std::nullopt);
|
||||||
|
|
||||||
|
auto bundle = builder.Build();
|
||||||
|
if (!bundle.has_value()) {
|
||||||
|
librustzcash_sapling_proving_ctx_free(ctx);
|
||||||
|
throw new std::runtime_error("Failed to create shielded output for miner");
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputeBindingSig(ctx, std::move(bundle));
|
||||||
|
|
||||||
|
librustzcash_sapling_proving_ctx_free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
// Create shielded output
|
// Create shielded output
|
||||||
void operator()(const libzcash::SaplingPaymentAddress &pa) const {
|
void operator()(const libzcash::SaplingPaymentAddress &pa) const {
|
||||||
auto ctx = librustzcash_sapling_proving_ctx_init();
|
auto ctx = librustzcash_sapling_proving_ctx_init();
|
||||||
|
@ -264,7 +314,7 @@ public:
|
||||||
}
|
}
|
||||||
mtx.vShieldedOutput.push_back(odesc.value());
|
mtx.vShieldedOutput.push_back(odesc.value());
|
||||||
|
|
||||||
ComputeBindingSig(ctx);
|
ComputeBindingSig(ctx, std::nullopt);
|
||||||
|
|
||||||
librustzcash_sapling_proving_ctx_free(ctx);
|
librustzcash_sapling_proving_ctx_free(ctx);
|
||||||
}
|
}
|
||||||
|
@ -283,7 +333,7 @@ public:
|
||||||
mtx.vout[0] = CTxOut(value, coinbaseScript->reserveScript);
|
mtx.vout[0] = CTxOut(value, coinbaseScript->reserveScript);
|
||||||
|
|
||||||
if (mtx.vShieldedOutput.size() > 0) {
|
if (mtx.vShieldedOutput.size() > 0) {
|
||||||
ComputeBindingSig(ctx);
|
ComputeBindingSig(ctx, std::nullopt);
|
||||||
}
|
}
|
||||||
|
|
||||||
librustzcash_sapling_proving_ctx_free(ctx);
|
librustzcash_sapling_proving_ctx_free(ctx);
|
||||||
|
@ -728,23 +778,38 @@ std::optional<MinerAddress> ExtractMinerAddress::operator()(const libzcash::Sapl
|
||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
std::optional<MinerAddress> ExtractMinerAddress::operator()(const libzcash::UnifiedAddress &addr) const {
|
std::optional<MinerAddress> ExtractMinerAddress::operator()(const libzcash::UnifiedAddress &addr) const {
|
||||||
for (const auto& receiver: addr) {
|
auto preferred = addr.GetPreferredRecipientAddress(consensus, height);
|
||||||
if (std::holds_alternative<libzcash::SaplingPaymentAddress>(receiver)) {
|
if (preferred.has_value()) {
|
||||||
return std::get<libzcash::SaplingPaymentAddress>(receiver);
|
std::optional<MinerAddress> ret;
|
||||||
}
|
std::visit(match {
|
||||||
|
[&](const libzcash::OrchardRawAddress addr) { ret = MinerAddress(addr); },
|
||||||
|
[&](const libzcash::SaplingPaymentAddress addr) { ret = MinerAddress(addr); },
|
||||||
|
[&](const CKeyID keyID) { ret = operator()(keyID); },
|
||||||
|
[&](const auto other) { ret = std::nullopt; }
|
||||||
|
}, preferred.value());
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GetMinerAddress(MinerAddress &minerAddress)
|
void GetMinerAddress(std::optional<MinerAddress> &minerAddress)
|
||||||
{
|
{
|
||||||
KeyIO keyIO(Params());
|
KeyIO keyIO(Params());
|
||||||
|
|
||||||
|
// If the user sets a UA miner address with an Orchard component, we want to ensure we
|
||||||
|
// start using it once we reach that height.
|
||||||
|
int height;
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
height = chainActive.Height() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
auto mAddrArg = GetArg("-mineraddress", "");
|
auto mAddrArg = GetArg("-mineraddress", "");
|
||||||
auto zaddr0 = keyIO.DecodePaymentAddress(mAddrArg);
|
auto zaddr0 = keyIO.DecodePaymentAddress(mAddrArg);
|
||||||
if (zaddr0.has_value()) {
|
if (zaddr0.has_value()) {
|
||||||
auto zaddr = std::visit(ExtractMinerAddress(), zaddr0.value());
|
auto zaddr = std::visit(ExtractMinerAddress(Params().GetConsensus(), height), zaddr0.value());
|
||||||
if (zaddr.has_value()) {
|
if (zaddr.has_value()) {
|
||||||
minerAddress = zaddr.value();
|
minerAddress = zaddr.value();
|
||||||
}
|
}
|
||||||
|
@ -815,8 +880,8 @@ void static BitcoinMiner(const CChainParams& chainparams)
|
||||||
// Each thread has its own counter
|
// Each thread has its own counter
|
||||||
unsigned int nExtraNonce = 0;
|
unsigned int nExtraNonce = 0;
|
||||||
|
|
||||||
MinerAddress minerAddress;
|
std::optional<MinerAddress> maybeMinerAddress;
|
||||||
GetMainSignals().AddressForMining(minerAddress);
|
GetMainSignals().AddressForMining(maybeMinerAddress);
|
||||||
|
|
||||||
unsigned int n = chainparams.GetConsensus().nEquihashN;
|
unsigned int n = chainparams.GetConsensus().nEquihashN;
|
||||||
unsigned int k = chainparams.GetConsensus().nEquihashK;
|
unsigned int k = chainparams.GetConsensus().nEquihashK;
|
||||||
|
@ -837,9 +902,10 @@ void static BitcoinMiner(const CChainParams& chainparams)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Throw an error if no address valid for mining was provided.
|
// Throw an error if no address valid for mining was provided.
|
||||||
if (!std::visit(IsValidMinerAddress(), minerAddress)) {
|
if (!(maybeMinerAddress.has_value() && std::visit(IsValidMinerAddress(), maybeMinerAddress.value()))) {
|
||||||
throw std::runtime_error("No miner address available (mining requires a wallet or -mineraddress)");
|
throw std::runtime_error("No miner address available (mining requires a wallet or -mineraddress)");
|
||||||
}
|
}
|
||||||
|
auto minerAddress = maybeMinerAddress.value();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (chainparams.MiningRequiresPeers()) {
|
if (chainparams.MiningRequiresPeers()) {
|
||||||
|
|
13
src/miner.h
13
src/miner.h
|
@ -24,13 +24,18 @@ static const int DEFAULT_GENERATE_THREADS = 1;
|
||||||
static const bool DEFAULT_PRINTPRIORITY = false;
|
static const bool DEFAULT_PRINTPRIORITY = false;
|
||||||
|
|
||||||
typedef std::variant<
|
typedef std::variant<
|
||||||
|
libzcash::OrchardRawAddress,
|
||||||
libzcash::SaplingPaymentAddress,
|
libzcash::SaplingPaymentAddress,
|
||||||
boost::shared_ptr<CReserveScript>> MinerAddress;
|
boost::shared_ptr<CReserveScript>> MinerAddress;
|
||||||
|
|
||||||
class ExtractMinerAddress
|
class ExtractMinerAddress
|
||||||
{
|
{
|
||||||
|
const Consensus::Params& consensus;
|
||||||
|
int height;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ExtractMinerAddress() {}
|
ExtractMinerAddress(const Consensus::Params& consensus, int height) :
|
||||||
|
consensus(consensus), height(height) {}
|
||||||
|
|
||||||
std::optional<MinerAddress> operator()(const CKeyID &keyID) const;
|
std::optional<MinerAddress> operator()(const CKeyID &keyID) const;
|
||||||
std::optional<MinerAddress> operator()(const CScriptID &addr) const;
|
std::optional<MinerAddress> operator()(const CScriptID &addr) const;
|
||||||
|
@ -44,6 +49,7 @@ class KeepMinerAddress
|
||||||
public:
|
public:
|
||||||
KeepMinerAddress() {}
|
KeepMinerAddress() {}
|
||||||
|
|
||||||
|
void operator()(const libzcash::OrchardRawAddress &addr) const {}
|
||||||
void operator()(const libzcash::SaplingPaymentAddress &pa) const {}
|
void operator()(const libzcash::SaplingPaymentAddress &pa) const {}
|
||||||
void operator()(const boost::shared_ptr<CReserveScript> &coinbaseScript) const {
|
void operator()(const boost::shared_ptr<CReserveScript> &coinbaseScript) const {
|
||||||
coinbaseScript->KeepScript();
|
coinbaseScript->KeepScript();
|
||||||
|
@ -57,6 +63,9 @@ class IsValidMinerAddress
|
||||||
public:
|
public:
|
||||||
IsValidMinerAddress() {}
|
IsValidMinerAddress() {}
|
||||||
|
|
||||||
|
bool operator()(const libzcash::OrchardRawAddress &addr) const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
bool operator()(const libzcash::SaplingPaymentAddress &pa) const {
|
bool operator()(const libzcash::SaplingPaymentAddress &pa) const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -89,7 +98,7 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const MinerAddre
|
||||||
|
|
||||||
#ifdef ENABLE_MINING
|
#ifdef ENABLE_MINING
|
||||||
/** Get -mineraddress */
|
/** Get -mineraddress */
|
||||||
void GetMinerAddress(MinerAddress &minerAddress);
|
void GetMinerAddress(std::optional<MinerAddress> &minerAddress);
|
||||||
/** Modify the extranonce in a block */
|
/** Modify the extranonce in a block */
|
||||||
void IncrementExtraNonce(
|
void IncrementExtraNonce(
|
||||||
CBlockTemplate* pblocktemplate,
|
CBlockTemplate* pblocktemplate,
|
||||||
|
|
|
@ -9,11 +9,15 @@
|
||||||
|
|
||||||
#include <amount.h>
|
#include <amount.h>
|
||||||
#include <rust/orchard.h>
|
#include <rust/orchard.h>
|
||||||
|
#include <rust/orchard/wallet.h>
|
||||||
|
#include "zcash/address/orchard.hpp"
|
||||||
|
|
||||||
class OrchardMerkleFrontier;
|
class OrchardMerkleFrontier;
|
||||||
|
class OrchardWallet;
|
||||||
|
namespace orchard { class UnauthorizedBundle; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Orchard component of a transaction.
|
* The Orchard component of an authorized transaction.
|
||||||
*/
|
*/
|
||||||
class OrchardBundle
|
class OrchardBundle
|
||||||
{
|
{
|
||||||
|
@ -22,7 +26,11 @@ private:
|
||||||
/// Memory is allocated by Rust.
|
/// Memory is allocated by Rust.
|
||||||
std::unique_ptr<OrchardBundlePtr, decltype(&orchard_bundle_free)> inner;
|
std::unique_ptr<OrchardBundlePtr, decltype(&orchard_bundle_free)> inner;
|
||||||
|
|
||||||
|
OrchardBundle(OrchardBundlePtr* bundle) : inner(bundle, orchard_bundle_free) {}
|
||||||
|
|
||||||
friend class OrchardMerkleFrontier;
|
friend class OrchardMerkleFrontier;
|
||||||
|
friend class OrchardWallet;
|
||||||
|
friend class orchard::UnauthorizedBundle;
|
||||||
public:
|
public:
|
||||||
OrchardBundle() : inner(nullptr, orchard_bundle_free) {}
|
OrchardBundle() : inner(nullptr, orchard_bundle_free) {}
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,11 @@ std::string SaplingOutPoint::ToString() const
|
||||||
return strprintf("SaplingOutPoint(%s, %u)", hash.ToString().substr(0, 10), n);
|
return strprintf("SaplingOutPoint(%s, %u)", hash.ToString().substr(0, 10), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string OrchardOutPoint::ToString() const
|
||||||
|
{
|
||||||
|
return strprintf("OrchardOutPoint(%s, %u)", hash.ToString().substr(0, 10), n);
|
||||||
|
}
|
||||||
|
|
||||||
CTxIn::CTxIn(COutPoint prevoutIn, CScript scriptSigIn, uint32_t nSequenceIn)
|
CTxIn::CTxIn(COutPoint prevoutIn, CScript scriptSigIn, uint32_t nSequenceIn)
|
||||||
{
|
{
|
||||||
prevout = prevoutIn;
|
prevout = prevoutIn;
|
||||||
|
|
|
@ -523,6 +523,16 @@ public:
|
||||||
std::string ToString() const;
|
std::string ToString() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** An outpoint - a combination of a txid and an index n into its orchard
|
||||||
|
* actions */
|
||||||
|
class OrchardOutPoint : public BaseOutPoint
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OrchardOutPoint() : BaseOutPoint() {};
|
||||||
|
OrchardOutPoint(uint256 hashIn, uint32_t nIn) : BaseOutPoint(hashIn, nIn) {};
|
||||||
|
std::string ToString() const;
|
||||||
|
};
|
||||||
|
|
||||||
/** An input of a transaction. It contains the location of the previous
|
/** An input of a transaction. It contains the location of the previous
|
||||||
* transaction's output that it claims and a signature that matches the
|
* transaction's output that it claims and a signature that matches the
|
||||||
* output's public key.
|
* output's public key.
|
||||||
|
|
|
@ -1247,6 +1247,13 @@ UniValue z_gettreestate(const UniValue& params, bool fHelp)
|
||||||
" \"finalRoot\": \"hex\", (string)\n"
|
" \"finalRoot\": \"hex\", (string)\n"
|
||||||
" \"finalState\": \"hex\" (string)\n"
|
" \"finalState\": \"hex\" (string)\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
|
" },\n"
|
||||||
|
" \"orchard\": {\n"
|
||||||
|
" \"skipHash\": \"hash\", (string) hash of most recent block with more information\n"
|
||||||
|
" \"commitments\": {\n"
|
||||||
|
" \"finalRoot\": \"hex\", (string)\n"
|
||||||
|
" \"finalState\": \"hex\" (string)\n"
|
||||||
|
" }\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
"\nExamples:\n"
|
"\nExamples:\n"
|
||||||
|
@ -1327,6 +1334,31 @@ UniValue z_gettreestate(const UniValue& params, bool fHelp)
|
||||||
res.pushKV("sapling", sapling_result);
|
res.pushKV("sapling", sapling_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// orchard
|
||||||
|
{
|
||||||
|
UniValue orchard_result(UniValue::VOBJ);
|
||||||
|
UniValue orchard_commitments(UniValue::VOBJ);
|
||||||
|
orchard_commitments.pushKV("finalRoot", pindex->hashFinalOrchardRoot.GetHex());
|
||||||
|
bool need_skiphash = false;
|
||||||
|
OrchardMerkleFrontier tree;
|
||||||
|
if (pcoinsTip->GetOrchardAnchorAt(pindex->hashFinalOrchardRoot, tree)) {
|
||||||
|
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
s << tree;
|
||||||
|
orchard_commitments.pushKV("finalState", HexStr(s.begin(), s.end()));
|
||||||
|
} else {
|
||||||
|
// Set skipHash to the most recent block that has a finalState.
|
||||||
|
const CBlockIndex* pindex_skip = pindex->pprev;
|
||||||
|
while (pindex_skip && !pcoinsTip->GetOrchardAnchorAt(pindex_skip->hashFinalOrchardRoot, tree)) {
|
||||||
|
pindex_skip = pindex_skip->pprev;
|
||||||
|
}
|
||||||
|
if (pindex_skip) {
|
||||||
|
orchard_result.pushKV("skipHash", pindex_skip->GetBlockHash().GetHex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orchard_result.pushKV("commitments", orchard_commitments);
|
||||||
|
res.pushKV("orchard", orchard_result);
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -134,7 +134,6 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||||
{ "z_sendmany", 1},
|
{ "z_sendmany", 1},
|
||||||
{ "z_sendmany", 2},
|
{ "z_sendmany", 2},
|
||||||
{ "z_sendmany", 3},
|
{ "z_sendmany", 3},
|
||||||
{ "z_sendmany", 4},
|
|
||||||
{ "z_shieldcoinbase", 2},
|
{ "z_shieldcoinbase", 2},
|
||||||
{ "z_shieldcoinbase", 3},
|
{ "z_shieldcoinbase", 3},
|
||||||
{ "z_getoperationstatus", 0},
|
{ "z_getoperationstatus", 0},
|
||||||
|
|
|
@ -186,19 +186,25 @@ UniValue generate(const UniValue& params, bool fHelp)
|
||||||
int nHeight = 0;
|
int nHeight = 0;
|
||||||
int nGenerate = params[0].get_int();
|
int nGenerate = params[0].get_int();
|
||||||
|
|
||||||
MinerAddress minerAddress;
|
std::optional<MinerAddress> maybeMinerAddress;
|
||||||
GetMainSignals().AddressForMining(minerAddress);
|
GetMainSignals().AddressForMining(maybeMinerAddress);
|
||||||
|
|
||||||
// If the keypool is exhausted, no script is returned at all. Catch this.
|
|
||||||
auto resv = std::get_if<boost::shared_ptr<CReserveScript>>(&minerAddress);
|
|
||||||
if (resv && !resv->get()) {
|
|
||||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throw an error if no address valid for mining was provided.
|
// Throw an error if no address valid for mining was provided.
|
||||||
if (!std::visit(IsValidMinerAddress(), minerAddress)) {
|
if (!maybeMinerAddress.has_value()) {
|
||||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "No miner address available (mining requires a wallet or -mineraddress)");
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "No miner address available (mining requires a wallet or -mineraddress)");
|
||||||
|
} else {
|
||||||
|
// Detect and handle keypool exhaustion separately from IsValidMinerAddress().
|
||||||
|
auto resv = std::get_if<boost::shared_ptr<CReserveScript>>(&maybeMinerAddress.value());
|
||||||
|
if (resv && !resv->get()) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catch any other invalid miner address issues.
|
||||||
|
if (!std::visit(IsValidMinerAddress(), maybeMinerAddress.value())) {
|
||||||
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "Miner address is invalid");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
auto minerAddress = maybeMinerAddress.value();
|
||||||
|
|
||||||
{ // Don't keep cs_main locked
|
{ // Don't keep cs_main locked
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
|
@ -569,9 +575,14 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
|
||||||
if (IsInitialBlockDownload(Params().GetConsensus()))
|
if (IsInitialBlockDownload(Params().GetConsensus()))
|
||||||
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Zcash is downloading blocks...");
|
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Zcash is downloading blocks...");
|
||||||
|
|
||||||
|
std::optional<MinerAddress> maybeMinerAddress;
|
||||||
|
GetMainSignals().AddressForMining(maybeMinerAddress);
|
||||||
|
|
||||||
MinerAddress minerAddress;
|
// Throw an error if no address valid for mining was provided.
|
||||||
GetMainSignals().AddressForMining(minerAddress);
|
if (!(maybeMinerAddress.has_value() && std::visit(IsValidMinerAddress(), maybeMinerAddress.value()))) {
|
||||||
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "No miner address available (getblocktemplate requires a wallet or -mineraddress)");
|
||||||
|
}
|
||||||
|
auto minerAddress = maybeMinerAddress.value();
|
||||||
|
|
||||||
static unsigned int nTransactionsUpdatedLast;
|
static unsigned int nTransactionsUpdatedLast;
|
||||||
static std::optional<CMutableTransaction> cached_next_cb_mtx;
|
static std::optional<CMutableTransaction> cached_next_cb_mtx;
|
||||||
|
@ -607,15 +618,15 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
|
||||||
nTransactionsUpdatedLastLP = nTransactionsUpdatedLast;
|
nTransactionsUpdatedLastLP = nTransactionsUpdatedLast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Release the main lock while waiting
|
||||||
|
// Don't call chainActive->Tip() without holding cs_main
|
||||||
|
LEAVE_CRITICAL_SECTION(cs_main);
|
||||||
{
|
{
|
||||||
checktxtime = boost::get_system_time() + boost::posix_time::seconds(10);
|
checktxtime = boost::get_system_time() + boost::posix_time::seconds(10);
|
||||||
|
|
||||||
boost::unique_lock<boost::mutex> lock(csBestBlock);
|
boost::unique_lock<boost::mutex> lock(g_best_block_mutex);
|
||||||
while (chainActive.Tip()->GetBlockHash() == hashWatchedChain && IsRPCRunning())
|
while (g_best_block == hashWatchedChain && IsRPCRunning())
|
||||||
{
|
{
|
||||||
// Release the main lock while waiting
|
|
||||||
LEAVE_CRITICAL_SECTION(cs_main);
|
|
||||||
|
|
||||||
// Before waiting, generate the coinbase for the block following the next
|
// Before waiting, generate the coinbase for the block following the next
|
||||||
// block (since this is cpu-intensive), so that when next block arrives,
|
// block (since this is cpu-intensive), so that when next block arrives,
|
||||||
// we can quickly respond with a template for following block.
|
// we can quickly respond with a template for following block.
|
||||||
|
@ -628,12 +639,11 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
|
||||||
Params(), CAmount{0}, minerAddress, cached_next_cb_height);
|
Params(), CAmount{0}, minerAddress, cached_next_cb_height);
|
||||||
next_cb_mtx = cached_next_cb_mtx;
|
next_cb_mtx = cached_next_cb_mtx;
|
||||||
}
|
}
|
||||||
bool timedout = !cvBlockChange.timed_wait(lock, checktxtime);
|
bool timedout = !g_best_block_cv.timed_wait(lock, checktxtime);
|
||||||
ENTER_CRITICAL_SECTION(cs_main);
|
|
||||||
|
|
||||||
// Optimization: even if timed out, a new block may have arrived
|
// Optimization: even if timed out, a new block may have arrived
|
||||||
// while waiting for cs_main; if so, don't discard next_cb_mtx.
|
// while waiting for cs_main; if so, don't discard next_cb_mtx.
|
||||||
if (chainActive.Tip()->GetBlockHash() != hashWatchedChain) break;
|
if (g_best_block != hashWatchedChain) break;
|
||||||
|
|
||||||
// Timeout: Check transactions for update
|
// Timeout: Check transactions for update
|
||||||
if (timedout && mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP) {
|
if (timedout && mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP) {
|
||||||
|
@ -643,11 +653,12 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
|
||||||
}
|
}
|
||||||
checktxtime += boost::posix_time::seconds(10);
|
checktxtime += boost::posix_time::seconds(10);
|
||||||
}
|
}
|
||||||
if (chainActive.Tip()->nHeight != nHeight + 1) {
|
if (g_best_block_height != nHeight + 1) {
|
||||||
// Unexpected height (reorg or >1 blocks arrived while waiting) invalidates coinbase tx.
|
// Unexpected height (reorg or >1 blocks arrived while waiting) invalidates coinbase tx.
|
||||||
next_cb_mtx = nullopt;
|
next_cb_mtx = nullopt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ENTER_CRITICAL_SECTION(cs_main);
|
||||||
|
|
||||||
if (!IsRPCRunning())
|
if (!IsRPCRunning())
|
||||||
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down");
|
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down");
|
||||||
|
|
|
@ -431,8 +431,7 @@ void JSONRequest::parse(const UniValue& valRequest)
|
||||||
if (!valMethod.isStr())
|
if (!valMethod.isStr())
|
||||||
throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string");
|
throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string");
|
||||||
strMethod = valMethod.get_str();
|
strMethod = valMethod.get_str();
|
||||||
if (strMethod != "getblocktemplate")
|
LogPrint("rpc", "ThreadRPCServer method=%s\n", SanitizeString(strMethod));
|
||||||
LogPrint("rpc", "ThreadRPCServer method=%s\n", SanitizeString(strMethod));
|
|
||||||
|
|
||||||
// Parse params
|
// Parse params
|
||||||
UniValue valParams = find_value(request, "params");
|
UniValue valParams = find_value(request, "params");
|
||||||
|
|
|
@ -6,6 +6,11 @@ the `zcashd` full node.
|
||||||
The FFI API does not have any stability guarantees, and will change as required
|
The FFI API does not have any stability guarantees, and will change as required
|
||||||
by `zcashd`.
|
by `zcashd`.
|
||||||
|
|
||||||
|
# zcashd-wallet-tool
|
||||||
|
|
||||||
|
`zcashd-wallet-tool` is a command-line tool that allows confirming to a `zcashd`
|
||||||
|
node that the emergency recovery phrase of the node's wallet has been backed up.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Licensed under either of
|
Licensed under either of
|
||||||
|
|
|
@ -0,0 +1,646 @@
|
||||||
|
use std::cmp::min;
|
||||||
|
use std::env::{self, consts::EXE_EXTENSION};
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, BufRead, Stdin, Write};
|
||||||
|
use std::iter;
|
||||||
|
use std::panic;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::{self, ChildStdin, Command, Output, Stdio};
|
||||||
|
use std::str::from_utf8;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use anyhow::{self, Context};
|
||||||
|
use backtrace::Backtrace;
|
||||||
|
use gumdrop::{Options, ParsingStyle};
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
use secrecy::{ExposeSecret, SecretString};
|
||||||
|
use thiserror::Error;
|
||||||
|
use time::macros::format_description;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
use tracing::debug;
|
||||||
|
use tracing_subscriber::{fmt, EnvFilter};
|
||||||
|
|
||||||
|
#[derive(Debug, Options)]
|
||||||
|
struct CliOptions {
|
||||||
|
#[options(no_short, help = "Print this help output")]
|
||||||
|
help: bool,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
no_short,
|
||||||
|
help = "Specify configuration filename, relative to the data directory (default: zcash.conf)",
|
||||||
|
meta = "FILENAME"
|
||||||
|
)]
|
||||||
|
conf: Option<String>,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
no_short,
|
||||||
|
help = "Specify data directory (this path cannot use '~')",
|
||||||
|
meta = "PATH"
|
||||||
|
)]
|
||||||
|
datadir: Option<String>,
|
||||||
|
|
||||||
|
#[options(no_short, help = "Use the test chain")]
|
||||||
|
testnet: bool,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
no_short,
|
||||||
|
help = "Send commands to node running on IPADDR (default: 127.0.0.1)",
|
||||||
|
meta = "IPADDR"
|
||||||
|
)]
|
||||||
|
rpcconnect: Option<String>,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
no_short,
|
||||||
|
help = "Connect to JSON-RPC on PORT (default: 8232 or testnet: 18232)",
|
||||||
|
meta = "PORT"
|
||||||
|
)]
|
||||||
|
rpcport: Option<u16>,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
no_short,
|
||||||
|
help = "Username for JSON-RPC connections",
|
||||||
|
meta = "USERNAME"
|
||||||
|
)]
|
||||||
|
rpcuser: Option<String>,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
no_short,
|
||||||
|
help = "Password for JSON-RPC connections",
|
||||||
|
meta = "PASSWORD"
|
||||||
|
)]
|
||||||
|
rpcpassword: Option<String>,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
no_short,
|
||||||
|
help = "Timeout in seconds during HTTP requests, or 0 for no timeout. (default: 900)",
|
||||||
|
meta = "SECONDS"
|
||||||
|
)]
|
||||||
|
rpcclienttimeout: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CliOptions {
|
||||||
|
fn to_zcash_cli_options(&self) -> Vec<String> {
|
||||||
|
iter::empty::<String>()
|
||||||
|
.chain(self.conf.as_ref().map(|o| format!("-conf={}", o)))
|
||||||
|
.chain(self.datadir.as_ref().map(|o| format!("-datadir={}", o)))
|
||||||
|
.chain(if self.testnet {
|
||||||
|
Some("-testnet".into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.chain(
|
||||||
|
self.rpcconnect
|
||||||
|
.as_ref()
|
||||||
|
.map(|o| format!("-rpcconnect={}", o)),
|
||||||
|
)
|
||||||
|
.chain(self.rpcport.as_ref().map(|o| format!("-rpcport={}", o)))
|
||||||
|
.chain(self.rpcuser.as_ref().map(|o| format!("-rpcuser={}", o)))
|
||||||
|
.chain(
|
||||||
|
self.rpcpassword
|
||||||
|
.as_ref()
|
||||||
|
.map(|o| format!("-rpcpassword={}", o)),
|
||||||
|
)
|
||||||
|
.chain(
|
||||||
|
self.rpcclienttimeout
|
||||||
|
.as_ref()
|
||||||
|
.map(|o| format!("-rpcclienttimeout={}", o)),
|
||||||
|
)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
enum WalletToolError {
|
||||||
|
#[error("zcash-cli executable not found")]
|
||||||
|
ZcashCliNotFound,
|
||||||
|
|
||||||
|
#[error("Unexpected response from zcash-cli or zcashd")]
|
||||||
|
UnexpectedResponse,
|
||||||
|
|
||||||
|
#[error("Could not connect to zcashd")]
|
||||||
|
ZcashdConnection,
|
||||||
|
|
||||||
|
#[error("zcashd -exportdir option not set")]
|
||||||
|
ExportDirNotSet,
|
||||||
|
|
||||||
|
#[error("Could not parse a recovery phrase from the export file")]
|
||||||
|
RecoveryPhraseNotFound,
|
||||||
|
|
||||||
|
#[error("Unexpected EOF in input")]
|
||||||
|
UnexpectedEof,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
// Log to stderr, configured by the RUST_LOG environment variable.
|
||||||
|
fmt()
|
||||||
|
.with_writer(io::stderr)
|
||||||
|
.with_env_filter(EnvFilter::from_default_env())
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// Allow either Bitcoin-style or GNU-style arguments.
|
||||||
|
let mut args = env::args();
|
||||||
|
let command = args.next().expect("argv[0] should exist");
|
||||||
|
let args: Vec<_> = args
|
||||||
|
.map(|s| {
|
||||||
|
if s.starts_with('-') && !s.starts_with("--") {
|
||||||
|
format!("-{}", s)
|
||||||
|
} else {
|
||||||
|
s
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
const USAGE_NOTE: &str = concat!(
|
||||||
|
"Options can be given in GNU style (`--conf=CONF` or `--conf CONF`),\n",
|
||||||
|
"or in Bitcoin style with a single hyphen (`-conf=CONF`).\n\n",
|
||||||
|
"The environment variable RUST_LOG controls debug output, e.g.\n",
|
||||||
|
"`RUST_LOG=debug`.\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
let opts = CliOptions::parse_args(&args, ParsingStyle::default()).unwrap_or_else(|e| {
|
||||||
|
eprintln!(
|
||||||
|
"{}: {}\n\nUsage: {} [OPTIONS]\n\n{}\n\n{}",
|
||||||
|
command,
|
||||||
|
e,
|
||||||
|
command,
|
||||||
|
CliOptions::usage(),
|
||||||
|
USAGE_NOTE,
|
||||||
|
);
|
||||||
|
process::exit(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
if opts.help_requested() {
|
||||||
|
println!(
|
||||||
|
"Usage: {} [OPTIONS]\n\n{}\n\n{}",
|
||||||
|
command,
|
||||||
|
opts.self_usage(),
|
||||||
|
USAGE_NOTE
|
||||||
|
);
|
||||||
|
process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = run(&opts) {
|
||||||
|
eprintln!("{}: {}", command, e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(opts: &CliOptions) -> anyhow::Result<()> {
|
||||||
|
let cli_options: Vec<String> = opts.to_zcash_cli_options();
|
||||||
|
|
||||||
|
println!(concat!(
|
||||||
|
"To reduce the risk of loss of funds, we're going to confirm that the\n",
|
||||||
|
"zcashd wallet is backed up reliably.\n\n",
|
||||||
|
" 👛 ➜ 🗃️ \n"
|
||||||
|
));
|
||||||
|
|
||||||
|
println!("Checking that we can connect to zcashd...");
|
||||||
|
let zcash_cli = zcash_cli_path()?;
|
||||||
|
|
||||||
|
// Pass an invalid filename, "\x01", and use the error message to distinguish
|
||||||
|
// whether zcashd is running with the -exportdir option, running without that
|
||||||
|
// option, or not running / cannot connect.
|
||||||
|
let mut cli_args = cli_options.clone();
|
||||||
|
cli_args.extend_from_slice(&["z_exportwallet".to_string(), "\x01".to_string()]);
|
||||||
|
let out = exec(&zcash_cli, &cli_args, None)?;
|
||||||
|
let cli_err: Vec<_> = from_utf8(&out.stderr)
|
||||||
|
.with_context(|| "Output from zcash-cli was not UTF-8")?
|
||||||
|
.lines()
|
||||||
|
.map(|s| s.trim_end_matches('\r'))
|
||||||
|
.collect();
|
||||||
|
debug!("stderr {:?}", cli_err);
|
||||||
|
|
||||||
|
if !cli_err.is_empty() {
|
||||||
|
if cli_err[0].starts_with("Error reading configuration file") {
|
||||||
|
println!(
|
||||||
|
"\nNo, we could not read the zcashd configuration file, expected to be at\n{:?}.",
|
||||||
|
Path::new(opts.datadir.as_ref().map_or("~/.zcash", |s| &s[..])).join(Path::new(
|
||||||
|
opts.conf.as_ref().map_or("zcash.conf", |s| &s[..])
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
println!(concat!(
|
||||||
|
"If it is not at that path, please try again with the '-datadir' and/or\n",
|
||||||
|
"'-conf' options set correctly (see '--help' for details). Also make sure\n",
|
||||||
|
"that the current user has permission to read the configuration file.\n",
|
||||||
|
));
|
||||||
|
return Err(WalletToolError::ZcashdConnection.into());
|
||||||
|
}
|
||||||
|
if cli_err[0].starts_with("error: couldn't connect") {
|
||||||
|
println!(concat!(
|
||||||
|
"\nNo, we could not connect. zcashd might not be running; in that case\n",
|
||||||
|
"please start it. The '-exportdir' option should be set to the absolute\n",
|
||||||
|
"path of the directory you want to save the wallet export file to.\n\n",
|
||||||
|
"(Don't forget to restart zcashd without '-exportdir' after finishing\n",
|
||||||
|
"the backup, if running it long-term with that option is not desired\n",
|
||||||
|
"or would be a security hazard in your environment.)\n\n",
|
||||||
|
"If you believe zcashd is running, it might be using an unexpected port,\n",
|
||||||
|
"address, or authentication options for the RPC interface, for example.\n",
|
||||||
|
"In that case try to connect to it using zcash-cli, and if successful,\n",
|
||||||
|
"use the same connection options for zcashd-wallet-tool (see '--help' for\n",
|
||||||
|
"accepted options) as for zcash-cli.\n"
|
||||||
|
));
|
||||||
|
return Err(WalletToolError::ZcashdConnection.into());
|
||||||
|
}
|
||||||
|
if cli_err[0] == "error code: -28" {
|
||||||
|
println!(concat!(
|
||||||
|
"\nNo, we could not connect. zcashd seems to be initializing; please try\n",
|
||||||
|
"again once it has finished.\n",
|
||||||
|
));
|
||||||
|
return Err(WalletToolError::ZcashdConnection.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const REMINDER_MSG: &str = concat!(
|
||||||
|
"\n\nPlease start or restart zcashd with '-exportdir' set to the absolute\n",
|
||||||
|
"path of the directory you want to save the wallet export file to.\n",
|
||||||
|
"(Don't forget to restart zcashd without '-exportdir' after finishing\n",
|
||||||
|
"the backup, if running it long-term with that option is not desired\n",
|
||||||
|
"or would be a security hazard in your environment.)\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
if cli_err.len() >= 3
|
||||||
|
&& cli_err[0] == "error code: -4"
|
||||||
|
&& cli_err[2].contains("zcashd -exportdir")
|
||||||
|
{
|
||||||
|
println!(
|
||||||
|
"\nIt looks like zcashd is running without the '-exportdir' option.{}",
|
||||||
|
REMINDER_MSG
|
||||||
|
);
|
||||||
|
return Err(WalletToolError::ExportDirNotSet.into());
|
||||||
|
}
|
||||||
|
if !(cli_err.len() >= 3
|
||||||
|
&& cli_err[0] == "error code: -4"
|
||||||
|
&& cli_err[2].starts_with("Filename is invalid"))
|
||||||
|
{
|
||||||
|
println!(
|
||||||
|
"\nThere was an unexpected response from zcash-cli or zcashd:\n> {}{}",
|
||||||
|
cli_err.join("\n> "),
|
||||||
|
REMINDER_MSG,
|
||||||
|
);
|
||||||
|
return Err(WalletToolError::UnexpectedResponse.into());
|
||||||
|
}
|
||||||
|
println!("Yes, and it is running with the '-exportdir' option as required.");
|
||||||
|
|
||||||
|
let mut stdin = io::stdin();
|
||||||
|
|
||||||
|
let base = default_filename_base();
|
||||||
|
let mut r = 0u32;
|
||||||
|
let out = loop {
|
||||||
|
let default_filename = if r != 0 {
|
||||||
|
format!("{}r{}", base, r)
|
||||||
|
} else {
|
||||||
|
base.to_string()
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
concat!(
|
||||||
|
"\nEnter the filename for the wallet export file, using only characters\n",
|
||||||
|
"a-z, A-Z and 0-9 (default '{}')."
|
||||||
|
),
|
||||||
|
default_filename
|
||||||
|
);
|
||||||
|
let response = prompt(&mut stdin)?;
|
||||||
|
let response = strip(&response);
|
||||||
|
let filename = if response.is_empty() {
|
||||||
|
r = r.saturating_add(1);
|
||||||
|
&default_filename
|
||||||
|
} else {
|
||||||
|
response
|
||||||
|
};
|
||||||
|
debug!("Using filename {:?}", filename);
|
||||||
|
|
||||||
|
let mut cli_args = cli_options.clone();
|
||||||
|
cli_args.extend_from_slice(&["z_exportwallet".to_string(), filename.to_string()]);
|
||||||
|
let out = exec(&zcash_cli, &cli_args, None)?;
|
||||||
|
let cli_err: Vec<_> = from_utf8(&out.stderr)
|
||||||
|
.with_context(|| "Output from zcash-cli was not UTF-8")?
|
||||||
|
.lines()
|
||||||
|
.map(|s| s.trim_end_matches('\r'))
|
||||||
|
.collect();
|
||||||
|
debug!("stderr {:?}", cli_err);
|
||||||
|
|
||||||
|
if cli_err.len() >= 3
|
||||||
|
&& cli_err[0] == "error code: -8"
|
||||||
|
&& cli_err[1] == "error message:"
|
||||||
|
&& cli_err[2].contains("overwrite existing")
|
||||||
|
{
|
||||||
|
println!(concat!(
|
||||||
|
"That file already exists. Please pick a unique filename in the\n",
|
||||||
|
"directory specified by the '-exportdir' option to zcashd."
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let cli_out: Vec<_> = from_utf8(&out.stdout)
|
||||||
|
.with_context(|| "Output from zcash-cli was not UTF-8")?
|
||||||
|
.lines()
|
||||||
|
.map(|s| s.trim_end_matches('\r'))
|
||||||
|
.collect();
|
||||||
|
debug!("stdout {:?}", cli_out);
|
||||||
|
|
||||||
|
if cli_out.is_empty() {
|
||||||
|
return Err(WalletToolError::UnexpectedResponse.into());
|
||||||
|
}
|
||||||
|
let export_path = cli_out[0];
|
||||||
|
|
||||||
|
println!("\nSaved the export file to '{}'.", export_path);
|
||||||
|
println!("IMPORTANT: This file contains secrets that allow spending all wallet funds.\n");
|
||||||
|
|
||||||
|
let export_file = File::open(export_path)
|
||||||
|
.with_context(|| format!("Could not open {:?} for reading", export_path))?;
|
||||||
|
|
||||||
|
// TODO: ensure the buffer will be zeroized (#5650)
|
||||||
|
let phrase_line: Vec<_> = io::BufReader::new(export_file)
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.map(SecretString::new))
|
||||||
|
.filter(|s| {
|
||||||
|
s.as_ref()
|
||||||
|
.map(|t| t.expose_secret().starts_with("# - recovery_phrase=\""))
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let phrase = match &phrase_line[..] {
|
||||||
|
[Ok(line)] => line
|
||||||
|
.expose_secret()
|
||||||
|
.trim_start_matches("# - recovery_phrase=\"")
|
||||||
|
.trim_end_matches('"'),
|
||||||
|
_ => return Err(WalletToolError::RecoveryPhraseNotFound.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// This panic hook allows us to make a best effort to clear the screen (and then print
|
||||||
|
// another reminder about secrets in the export file) even if a panic occurs.
|
||||||
|
|
||||||
|
let old_hook = panic::take_hook();
|
||||||
|
{
|
||||||
|
let export_path = export_path.to_string();
|
||||||
|
panic::set_hook(Box::new(move |panic_info| {
|
||||||
|
clear_and_show_cautions(&export_path);
|
||||||
|
|
||||||
|
let s = panic_info.payload().downcast_ref::<&str>().unwrap_or(&"");
|
||||||
|
eprintln!("\nPanic: {}\n{:?}", s, Backtrace::new());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = (|| -> anyhow::Result<()> {
|
||||||
|
println!("The recovery phrase is:\n");
|
||||||
|
|
||||||
|
const WORDS_PER_LINE: usize = 3;
|
||||||
|
|
||||||
|
let words: Vec<_> = phrase.split(' ').collect();
|
||||||
|
let max_len = words.iter().map(|w| w.len()).max().unwrap_or(0);
|
||||||
|
|
||||||
|
for (i, word) in words.iter().enumerate() {
|
||||||
|
print!("{0:2}: {1:2$}", i + 1, word, max_len + 2);
|
||||||
|
if (i + 1) % WORDS_PER_LINE == 0 {
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if words.len() % WORDS_PER_LINE != 0 {
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(concat!(
|
||||||
|
"\nPlease write down this phrase (including the numbering of words) on\n",
|
||||||
|
"something durable that you will keep in a secure location.\n",
|
||||||
|
"Press Enter when finished; then the phrase will disappear and you'll be\n",
|
||||||
|
"asked to re-enter a selection of words from it."
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut stdin = io::stdin();
|
||||||
|
prompt(&mut stdin)?;
|
||||||
|
|
||||||
|
// The only reliable and portable way to make sure the recovery phrase
|
||||||
|
// is no longer displayed is to clear the whole terminal (including
|
||||||
|
// scrollback, if possible). The text is only printed if clearing fails.
|
||||||
|
try_to_clear(concat!(
|
||||||
|
"\n\n\n\n\n\n\n\n\n\n\n\n",
|
||||||
|
"Please adjust the terminal window so that you can't see the\n",
|
||||||
|
"recovery phrase above. After finishing the backup, close the\n",
|
||||||
|
"terminal window or clear it"
|
||||||
|
));
|
||||||
|
|
||||||
|
println!("\nNow we're going to confirm that you backed up the recovery phrase.");
|
||||||
|
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let mut unconfirmed: Vec<usize> = (0..words.len()).collect();
|
||||||
|
for _ in 0..min(3, words.len()) {
|
||||||
|
let index: usize = rng.gen_range(0..unconfirmed.len());
|
||||||
|
let n = unconfirmed[index];
|
||||||
|
unconfirmed[index] = unconfirmed[unconfirmed.len() - 1];
|
||||||
|
unconfirmed.pop().expect("should be nonempty");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
println!("\nPlease enter the {} word:", ordinal(n + 1));
|
||||||
|
let line = prompt(&mut stdin)?;
|
||||||
|
if words[n] == strip(&line) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
println!("That's not correct, please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})();
|
||||||
|
|
||||||
|
panic::set_hook(old_hook);
|
||||||
|
clear_and_show_cautions(export_path);
|
||||||
|
res?;
|
||||||
|
|
||||||
|
let mut cli_args = cli_options;
|
||||||
|
cli_args.extend_from_slice(&["-stdin".to_string(), "walletconfirmbackup".to_string()]);
|
||||||
|
exec(&zcash_cli, &cli_args, Some(phrase))
|
||||||
|
.and_then(|out| {
|
||||||
|
let cli_err: Vec<_> = from_utf8(&out.stderr)
|
||||||
|
.with_context(|| "Output from zcash-cli was not UTF-8")?
|
||||||
|
.lines()
|
||||||
|
.map(|s| s.trim_end_matches('\r'))
|
||||||
|
.collect();
|
||||||
|
debug!("stderr {:?}", cli_err);
|
||||||
|
|
||||||
|
if !cli_err.is_empty() {
|
||||||
|
if cli_err[0].starts_with("error: couldn't connect") {
|
||||||
|
println!("\nWe could not connect to zcashd; it may have exited.");
|
||||||
|
return Err(WalletToolError::ZcashdConnection.into());
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"\nThere was an unexpected response from zcash-cli or zcashd:\n> {}",
|
||||||
|
cli_err.join("\n> "),
|
||||||
|
);
|
||||||
|
return Err(WalletToolError::UnexpectedResponse.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!(concat!(
|
||||||
|
"\nThe backup of the emergency recovery phrase for the zcashd\n",
|
||||||
|
"wallet has been successfully confirmed 🙂. You can now use the\n",
|
||||||
|
"zcashd RPC methods that create keys and addresses in that wallet.\n\n",
|
||||||
|
"If you use other wallets, their recovery information will need\n",
|
||||||
|
"to be backed up separately.\n"
|
||||||
|
));
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.map_err(|e| {
|
||||||
|
println!(concat!(
|
||||||
|
"\nzcash-wallet-tool was unable to communicate to zcashd that the\n",
|
||||||
|
"backup was confirmed. This can happen if zcashd stopped, in which\n",
|
||||||
|
"case you should try again. If zcashd is still running, please seek\n",
|
||||||
|
"help or try to use 'zcash-cli -stdin walletconfirmbackup' manually.\n"
|
||||||
|
));
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_USER_INPUT_LEN: usize = 100;
|
||||||
|
|
||||||
|
fn prompt(input: &mut Stdin) -> anyhow::Result<SecretString> {
|
||||||
|
let mut buf = String::with_capacity(MAX_USER_INPUT_LEN);
|
||||||
|
let res = input
|
||||||
|
.read_line(&mut buf)
|
||||||
|
.with_context(|| "Error reading from stdin");
|
||||||
|
|
||||||
|
// Ensure the buffer is zeroized even on error.
|
||||||
|
let line = SecretString::new(buf);
|
||||||
|
res.and_then(|_| {
|
||||||
|
if line.expose_secret().ends_with('\n') {
|
||||||
|
Ok(line)
|
||||||
|
} else {
|
||||||
|
Err(WalletToolError::UnexpectedEof.into())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strip(input: &SecretString) -> &str {
|
||||||
|
input
|
||||||
|
.expose_secret()
|
||||||
|
.trim_end_matches(|c| c == '\r' || c == '\n')
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ordinal(num: usize) -> String {
|
||||||
|
let suffix = if (11..=13).contains(&(num % 100)) {
|
||||||
|
"th"
|
||||||
|
} else {
|
||||||
|
match num % 10 {
|
||||||
|
1 => "st",
|
||||||
|
2 => "nd",
|
||||||
|
3 => "rd",
|
||||||
|
_ => "th",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
format!("{}{}", num, suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zcash_cli_path() -> anyhow::Result<PathBuf> {
|
||||||
|
// First look for `zcash_cli[.exe]` as a sibling of the executable.
|
||||||
|
let mut exe = env::current_exe()
|
||||||
|
.with_context(|| "Cannot determine the path of the running executable")?;
|
||||||
|
exe.set_file_name("zcash-cli");
|
||||||
|
exe.set_extension(EXE_EXTENSION);
|
||||||
|
|
||||||
|
debug!("Testing for zcash-cli at {:?}", exe);
|
||||||
|
if exe.exists() {
|
||||||
|
return Ok(exe);
|
||||||
|
}
|
||||||
|
// If not found there, look in `../src/zcash_cli[.exe]` provided
|
||||||
|
// that `src` is a sibling of `target`.
|
||||||
|
exe.pop(); // strip filename
|
||||||
|
exe.pop(); // ..
|
||||||
|
if exe.file_name() != Some(OsStr::new("target")) {
|
||||||
|
// or in `../../src/zcash_cli[.exe]` under the same proviso
|
||||||
|
exe.pop(); // ../..
|
||||||
|
if exe.file_name() != Some(OsStr::new("target")) {
|
||||||
|
return Err(WalletToolError::ZcashCliNotFound.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Replace 'target/' with 'src/'.
|
||||||
|
exe.set_file_name("src");
|
||||||
|
exe.push("zcash-cli");
|
||||||
|
exe.set_extension(EXE_EXTENSION);
|
||||||
|
|
||||||
|
debug!("Testing for zcash-cli at {:?}", exe);
|
||||||
|
if !exe.exists() {
|
||||||
|
return Err(WalletToolError::ZcashCliNotFound.into());
|
||||||
|
}
|
||||||
|
Ok(exe)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exec(exe_path: &Path, args: &[String], stdin: Option<&str>) -> anyhow::Result<Output> {
|
||||||
|
debug!("Running {:?} {:?}", exe_path, args);
|
||||||
|
let mut cmd = Command::new(exe_path);
|
||||||
|
let cli = cmd.args(args);
|
||||||
|
match stdin {
|
||||||
|
None => Ok(cli.output()?),
|
||||||
|
Some(data) => {
|
||||||
|
let mut cli_process = cli
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
cli_process
|
||||||
|
.stdin
|
||||||
|
.take()
|
||||||
|
.with_context(|| "Could not open pipe to zcash-cli's stdin")
|
||||||
|
.and_then(|mut pipe: ChildStdin| -> anyhow::Result<()> {
|
||||||
|
pipe.write_all(data.as_bytes())?;
|
||||||
|
pipe.write_all("\n".as_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.with_context(|| "Could not write to zcash-cli's stdin")?;
|
||||||
|
Ok(cli_process.wait_with_output()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_filename_base() -> String {
|
||||||
|
let format = format_description!("export[year][month][day]");
|
||||||
|
|
||||||
|
// We use the UTC date because there is a security issue in obtaining the local date
|
||||||
|
// from either `chrono` or `time`: <https://github.com/chronotope/chrono/issues/602>.
|
||||||
|
// We could use the approach in
|
||||||
|
// <https://github.com/ArekPiekarz/rusty-tax-break/commit/3aac8f0c26fd96b7365619509a544f78b59627fe>
|
||||||
|
// if it were important, but it isn't worth the dependency on `tz-rs`.
|
||||||
|
|
||||||
|
OffsetDateTime::from(SystemTime::now())
|
||||||
|
.format(&format)
|
||||||
|
.unwrap_or_else(|_| "export".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_and_show_cautions(export_path: &str) {
|
||||||
|
try_to_clear(concat!(
|
||||||
|
"\nCAUTION: This terminal window might be showing secrets (or have\n",
|
||||||
|
"them in the scrollback). Please copy any useful information and\n",
|
||||||
|
"then close it, or clear it"
|
||||||
|
));
|
||||||
|
|
||||||
|
println!(
|
||||||
|
concat!(
|
||||||
|
"\nIMPORTANT: Secrets that allow spending all zcashd wallet funds\n",
|
||||||
|
"have been left in the file '{}'.\n\n",
|
||||||
|
"Don't forget to restart zcashd without '-exportdir', if running it\n",
|
||||||
|
"long-term with that option is not desired or would be a security\n",
|
||||||
|
"hazard in your environment.\n\n",
|
||||||
|
"When choosing a location for the physical backup of your emergency\n",
|
||||||
|
"recovery phrase, please make sure to consider both risk of theft,\n",
|
||||||
|
"and your long-term ability to remember where it is kept."
|
||||||
|
),
|
||||||
|
export_path,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_to_clear(error_blurb: &str) {
|
||||||
|
if let Err(e) = clearscreen::clear() {
|
||||||
|
eprintln!("Unable to clear screen: {}.", e);
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
const HOW_TO_CLEAR: &str = "using 'cls'";
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
const HOW_TO_CLEAR: &str = "by pressing Command + K";
|
||||||
|
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
|
||||||
|
const HOW_TO_CLEAR: &str = "using 'clear'";
|
||||||
|
|
||||||
|
println!("{} {}.", error_blurb, HOW_TO_CLEAR);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,10 +5,13 @@
|
||||||
#ifndef ZCASH_RUST_INCLUDE_RUST_ADDRESS_H
|
#ifndef ZCASH_RUST_INCLUDE_RUST_ADDRESS_H
|
||||||
#define ZCASH_RUST_INCLUDE_RUST_ADDRESS_H
|
#define ZCASH_RUST_INCLUDE_RUST_ADDRESS_H
|
||||||
|
|
||||||
|
#include "rust/orchard/keys.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
typedef bool (*orchard_receiver_t)(void* ua, OrchardRawAddressPtr* addr);
|
||||||
typedef bool (*raw_to_receiver_t)(void* ua, const unsigned char* raw);
|
typedef bool (*raw_to_receiver_t)(void* ua, const unsigned char* raw);
|
||||||
typedef bool (*unknown_receiver_t)(
|
typedef bool (*unknown_receiver_t)(
|
||||||
void* ua,
|
void* ua,
|
||||||
|
@ -24,6 +27,7 @@ bool zcash_address_parse_unified(
|
||||||
const char* str,
|
const char* str,
|
||||||
const char* network,
|
const char* network,
|
||||||
void* ua,
|
void* ua,
|
||||||
|
orchard_receiver_t orchard_cb,
|
||||||
raw_to_receiver_t sapling_cb,
|
raw_to_receiver_t sapling_cb,
|
||||||
raw_to_receiver_t p2sh_cb,
|
raw_to_receiver_t p2sh_cb,
|
||||||
raw_to_receiver_t p2pkh_cb,
|
raw_to_receiver_t p2pkh_cb,
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
// Copyright (c) 2022 The Zcash developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||||
|
|
||||||
|
#ifndef ZCASH_RUST_INCLUDE_RUST_BUILDER_H
|
||||||
|
#define ZCASH_RUST_INCLUDE_RUST_BUILDER_H
|
||||||
|
|
||||||
|
#include "rust/orchard.h"
|
||||||
|
#include "rust/orchard/keys.h"
|
||||||
|
#include "rust/transaction.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// A type-safe pointer to a Rust-allocated struct containing the information
|
||||||
|
/// needed to spend an Orchard note.
|
||||||
|
struct OrchardSpendInfoPtr;
|
||||||
|
typedef struct OrchardSpendInfoPtr OrchardSpendInfoPtr;
|
||||||
|
|
||||||
|
/// Pointer to Rust-allocated Orchard bundle builder.
|
||||||
|
struct OrchardBuilderPtr;
|
||||||
|
typedef struct OrchardBuilderPtr OrchardBuilderPtr;
|
||||||
|
|
||||||
|
/// Pointer to Rust-allocated Orchard bundle without proofs
|
||||||
|
/// or authorizing data.
|
||||||
|
struct OrchardUnauthorizedBundlePtr;
|
||||||
|
typedef struct OrchardUnauthorizedBundlePtr OrchardUnauthorizedBundlePtr;
|
||||||
|
|
||||||
|
/// Frees the memory associated with an Orchard spend info struct that was
|
||||||
|
/// allocated by Rust.
|
||||||
|
void orchard_spend_info_free(OrchardSpendInfoPtr* ptr);
|
||||||
|
|
||||||
|
/// Construct a new Orchard transaction builder.
|
||||||
|
///
|
||||||
|
/// If `anchor` is `null`, the root of the empty Orchard commitment tree is used.
|
||||||
|
OrchardBuilderPtr* orchard_builder_new(
|
||||||
|
bool spends_enabled,
|
||||||
|
bool outputs_enabled,
|
||||||
|
const unsigned char* anchor);
|
||||||
|
|
||||||
|
/// Frees an Orchard builder returned from `orchard_builder_new`.
|
||||||
|
void orchard_builder_free(OrchardBuilderPtr* ptr);
|
||||||
|
|
||||||
|
/// Adds a note to be spent in this bundle.
|
||||||
|
///
|
||||||
|
/// Returns `false` if the Merkle path in `spend_info` does not have the
|
||||||
|
/// required anchor.
|
||||||
|
///
|
||||||
|
/// `spend_info` is always freed by this method, whether or not it succeeds.
|
||||||
|
bool orchard_builder_add_spend(
|
||||||
|
OrchardBuilderPtr* ptr,
|
||||||
|
OrchardSpendInfoPtr* spend_info);
|
||||||
|
|
||||||
|
/// Adds an address which will receive funds in this bundle.
|
||||||
|
///
|
||||||
|
/// `ovk` is a pointer to the outgoing viewing key to make this recipient recoverable by,
|
||||||
|
/// or `null` to make the recipient unrecoverable by the sender.
|
||||||
|
///
|
||||||
|
/// `memo` is a pointer to the 512-byte memo field encoding, or `null` for "no memo".
|
||||||
|
bool orchard_builder_add_recipient(
|
||||||
|
OrchardBuilderPtr* ptr,
|
||||||
|
const unsigned char* ovk,
|
||||||
|
const OrchardRawAddressPtr* recipient,
|
||||||
|
uint64_t value,
|
||||||
|
const unsigned char* memo);
|
||||||
|
|
||||||
|
/// Builds a bundle containing the given spent notes and recipients.
|
||||||
|
///
|
||||||
|
/// Returns `null` if an error occurs.
|
||||||
|
///
|
||||||
|
/// `builder` is always freed by this method.
|
||||||
|
OrchardUnauthorizedBundlePtr* orchard_builder_build(OrchardBuilderPtr* builder);
|
||||||
|
|
||||||
|
/// Frees an Orchard bundle returned from `orchard_bundle_build`.
|
||||||
|
void orchard_unauthorized_bundle_free(OrchardUnauthorizedBundlePtr* bundle);
|
||||||
|
|
||||||
|
/// Adds proofs and signatures to the bundle.
|
||||||
|
///
|
||||||
|
/// Returns `null` if an error occurs.
|
||||||
|
///
|
||||||
|
/// `bundle` is always freed by this method.
|
||||||
|
OrchardBundlePtr* orchard_unauthorized_bundle_prove_and_sign(
|
||||||
|
OrchardUnauthorizedBundlePtr* bundle,
|
||||||
|
const OrchardSpendingKeyPtr** keys,
|
||||||
|
size_t keys_len,
|
||||||
|
const unsigned char* sighash);
|
||||||
|
|
||||||
|
/// Calculates a ZIP 244 shielded signature digest for the given under-construction
|
||||||
|
/// transaction.
|
||||||
|
///
|
||||||
|
/// Returns `false` if any of the parameters are invalid; in this case, `sighash_ret`
|
||||||
|
/// will be unaltered.
|
||||||
|
///
|
||||||
|
/// `preTx` is always freed by this method.
|
||||||
|
bool zcash_builder_zip244_shielded_signature_digest(
|
||||||
|
PrecomputedTxParts* preTx,
|
||||||
|
const OrchardUnauthorizedBundlePtr* bundle,
|
||||||
|
unsigned char* sighash_ret);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // ZCASH_RUST_INCLUDE_RUST_BUILDER_H
|
|
@ -13,6 +13,7 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// Typesafe pointer to a Rust-allocated orchard::bundle::Bundle value
|
||||||
struct OrchardBundlePtr;
|
struct OrchardBundlePtr;
|
||||||
typedef struct OrchardBundlePtr OrchardBundlePtr;
|
typedef struct OrchardBundlePtr OrchardBundlePtr;
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ void orchard_merkle_frontier_root(
|
||||||
// The total number of leaves that have been appended to obtain
|
// The total number of leaves that have been appended to obtain
|
||||||
// the current state of the frontier. Subtract 1 from this value
|
// the current state of the frontier. Subtract 1 from this value
|
||||||
// to obtain the position of the most recently appended leaf.
|
// to obtain the position of the most recently appended leaf.
|
||||||
size_t orchard_merkle_frontier_num_leaves(
|
uint64_t orchard_merkle_frontier_num_leaves(
|
||||||
const OrchardMerkleFrontierPtr* tree_ptr);
|
const OrchardMerkleFrontierPtr* tree_ptr);
|
||||||
|
|
||||||
// Estimate the amount of memory consumed by the merkle frontier.
|
// Estimate the amount of memory consumed by the merkle frontier.
|
||||||
|
|
|
@ -32,6 +32,43 @@ OrchardRawAddressPtr* orchard_address_clone(
|
||||||
*/
|
*/
|
||||||
void orchard_address_free(OrchardRawAddressPtr* ptr);
|
void orchard_address_free(OrchardRawAddressPtr* ptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses Orchard raw address bytes from the given stream.
|
||||||
|
*
|
||||||
|
* - If the key does not parse correctly, the returned pointer will be null.
|
||||||
|
*/
|
||||||
|
OrchardRawAddressPtr* orchard_raw_address_parse(
|
||||||
|
void* stream,
|
||||||
|
read_callback_t read_cb);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes Orchard raw address bytes to the given stream.
|
||||||
|
*
|
||||||
|
* This will return `false` and leave the stream unmodified if
|
||||||
|
* `raw_address == nullptr`;
|
||||||
|
*/
|
||||||
|
bool orchard_raw_address_serialize(
|
||||||
|
const OrchardRawAddressPtr* raw_address,
|
||||||
|
void* stream,
|
||||||
|
write_callback_t write_cb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the "equal" operation for comparing two Orchard addresses.
|
||||||
|
*/
|
||||||
|
bool orchard_address_eq(
|
||||||
|
const OrchardRawAddressPtr* k0,
|
||||||
|
const OrchardRawAddressPtr* k1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the "less than" operation `k0 < k1` for comparing two Orchard
|
||||||
|
* addresses. This is a comparison of the raw bytes, only useful for cases
|
||||||
|
* where a semantically irrelevant ordering is needed (such as for map keys).
|
||||||
|
*/
|
||||||
|
bool orchard_address_lt(
|
||||||
|
const OrchardRawAddressPtr* k0,
|
||||||
|
const OrchardRawAddressPtr* k1);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Incoming Viewing Keys
|
// Incoming Viewing Keys
|
||||||
//
|
//
|
||||||
|
@ -60,6 +97,18 @@ OrchardRawAddressPtr* orchard_incoming_viewing_key_to_address(
|
||||||
const OrchardIncomingViewingKeyPtr* incoming_viewing_key,
|
const OrchardIncomingViewingKeyPtr* incoming_viewing_key,
|
||||||
const unsigned char* j);
|
const unsigned char* j);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the diversifier component of an Orchard raw address with the
|
||||||
|
* specified IVK, and verifies that the address was derived from that IVK.
|
||||||
|
*
|
||||||
|
* Returns `false` and leaves the `j_ret` parameter unmodified if the address
|
||||||
|
* was not derived from the specified IVK.
|
||||||
|
*/
|
||||||
|
bool orchard_incoming_viewing_key_decrypt_diversifier(
|
||||||
|
const OrchardIncomingViewingKeyPtr* incoming_viewing_key,
|
||||||
|
const OrchardRawAddressPtr* addr,
|
||||||
|
uint8_t *j_ret);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an Orchard incoming viewing key from the given stream.
|
* Parses an Orchard incoming viewing key from the given stream.
|
||||||
*
|
*
|
||||||
|
@ -69,8 +118,12 @@ OrchardIncomingViewingKeyPtr* orchard_incoming_viewing_key_parse(
|
||||||
void* stream,
|
void* stream,
|
||||||
read_callback_t read_cb);
|
read_callback_t read_cb);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes an Orchard incoming viewing key to the given stream.
|
* Serializes an Orchard incoming viewing key to the given stream.
|
||||||
|
*
|
||||||
|
* This will return `false` and leave the stream unmodified if
|
||||||
|
* `incoming_viewing_key == nullptr`.
|
||||||
*/
|
*/
|
||||||
bool orchard_incoming_viewing_key_serialize(
|
bool orchard_incoming_viewing_key_serialize(
|
||||||
const OrchardIncomingViewingKeyPtr* incoming_viewing_key,
|
const OrchardIncomingViewingKeyPtr* incoming_viewing_key,
|
||||||
|
@ -123,6 +176,9 @@ OrchardFullViewingKeyPtr* orchard_full_viewing_key_parse(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes an Orchard full viewing key to the given stream.
|
* Serializes an Orchard full viewing key to the given stream.
|
||||||
|
*
|
||||||
|
* This will return `false` and leave the stream unmodified if
|
||||||
|
* `full_viewing_key == nullptr`.
|
||||||
*/
|
*/
|
||||||
bool orchard_full_viewing_key_serialize(
|
bool orchard_full_viewing_key_serialize(
|
||||||
const OrchardFullViewingKeyPtr* full_viewing_key,
|
const OrchardFullViewingKeyPtr* full_viewing_key,
|
||||||
|
@ -135,6 +191,30 @@ bool orchard_full_viewing_key_serialize(
|
||||||
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_incoming_viewing_key(
|
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_incoming_viewing_key(
|
||||||
const OrchardFullViewingKeyPtr* key);
|
const OrchardFullViewingKeyPtr* key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the internal incoming viewing key for the specified full viewing key.
|
||||||
|
*/
|
||||||
|
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_internal_incoming_viewing_key(
|
||||||
|
const OrchardFullViewingKeyPtr* key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the external outgoing viewing key for the specified full viewing key.
|
||||||
|
*
|
||||||
|
* `ovk_ret` must be 32 bytes.
|
||||||
|
*/
|
||||||
|
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_external_outgoing_viewing_key(
|
||||||
|
const OrchardFullViewingKeyPtr* fvk,
|
||||||
|
uint8_t *ovk_ret);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the internal outgoing viewing key for the specified full viewing key.
|
||||||
|
*
|
||||||
|
* `ovk_ret` must be 32 bytes.
|
||||||
|
*/
|
||||||
|
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_internal_outgoing_viewing_key(
|
||||||
|
const OrchardFullViewingKeyPtr* fvk,
|
||||||
|
uint8_t *ovk_ret);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements equality testing between full viewing keys.
|
* Implements equality testing between full viewing keys.
|
||||||
*/
|
*/
|
||||||
|
@ -173,25 +253,11 @@ OrchardSpendingKeyPtr* orchard_spending_key_clone(
|
||||||
*/
|
*/
|
||||||
void orchard_spending_key_free(OrchardSpendingKeyPtr* ptr);
|
void orchard_spending_key_free(OrchardSpendingKeyPtr* ptr);
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses an Orchard spending key from the given stream.
|
|
||||||
*
|
|
||||||
* - If the key does not parse correctly, the returned pointer will be null.
|
|
||||||
*/
|
|
||||||
OrchardSpendingKeyPtr* orchard_spending_key_parse(
|
|
||||||
void* stream,
|
|
||||||
read_callback_t read_cb);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializes an Orchard spending key to the given stream.
|
|
||||||
*/
|
|
||||||
bool orchard_spending_key_serialize(
|
|
||||||
const OrchardSpendingKeyPtr* spending_key,
|
|
||||||
void* stream,
|
|
||||||
write_callback_t write_cb);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the full viewing key for the specified spending key.
|
* Returns the full viewing key for the specified spending key.
|
||||||
|
*
|
||||||
|
* The resulting pointer must be ultimately freed by the caller
|
||||||
|
* using `orchard_full_viewing_key_free`.
|
||||||
*/
|
*/
|
||||||
OrchardFullViewingKeyPtr* orchard_spending_key_to_full_viewing_key(
|
OrchardFullViewingKeyPtr* orchard_spending_key_to_full_viewing_key(
|
||||||
const OrchardSpendingKeyPtr* key);
|
const OrchardSpendingKeyPtr* key);
|
||||||
|
|
|
@ -0,0 +1,379 @@
|
||||||
|
// Copyright (c) 2021 The Zcash developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||||
|
|
||||||
|
#ifndef ZCASH_RUST_INCLUDE_RUST_ORCHARD_WALLET_H
|
||||||
|
#define ZCASH_RUST_INCLUDE_RUST_ORCHARD_WALLET_H
|
||||||
|
|
||||||
|
#include "rust/orchard/keys.h"
|
||||||
|
#include "rust/builder.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type-safe pointer type for an Orchard wallet.
|
||||||
|
*/
|
||||||
|
struct OrchardWalletPtr;
|
||||||
|
typedef struct OrchardWalletPtr OrchardWalletPtr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new empty Orchard wallet and return a pointer to it.
|
||||||
|
* Memory is allocated by Rust and must be manually freed using
|
||||||
|
* `orchard_wallet_free`.
|
||||||
|
*/
|
||||||
|
OrchardWalletPtr* orchard_wallet_new();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees the memory associated with an Orchard wallet that was allocated
|
||||||
|
* by Rust.
|
||||||
|
*/
|
||||||
|
void orchard_wallet_free(OrchardWalletPtr* wallet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the state of the wallet to be suitable for rescan from the NU5 activation
|
||||||
|
* height. This removes all witness and spentness information from the wallet. The
|
||||||
|
* keystore is unmodified and decrypted note, nullifier, and conflict data are left
|
||||||
|
* in place with the expectation that they will be overwritten and/or updated in
|
||||||
|
* the rescan process.
|
||||||
|
*/
|
||||||
|
bool orchard_wallet_reset(OrchardWalletPtr* wallet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checkpoint the note commitment tree. This returns `false` and leaves the note
|
||||||
|
* commitment tree unmodified if the block height specified is not the successor
|
||||||
|
* to the last block height checkpointed.
|
||||||
|
*/
|
||||||
|
bool orchard_wallet_checkpoint(
|
||||||
|
OrchardWalletPtr* wallet,
|
||||||
|
uint32_t blockHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the wallet has any checkpointed state to which it can rewind.
|
||||||
|
* If so, `blockHeightRet` will be modified to contain the last block height at which a
|
||||||
|
* checkpoint was created.
|
||||||
|
*/
|
||||||
|
bool orchard_wallet_get_last_checkpoint(
|
||||||
|
const OrchardWalletPtr* wallet,
|
||||||
|
uint32_t* blockHeightRet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewinds to the most recent checkpoint, and marks as unspent any notes previously
|
||||||
|
* identified as having been spent by transactions in the latest block.
|
||||||
|
*
|
||||||
|
* The `blockHeight` argument provides the height to which the witness tree should be
|
||||||
|
* rewound, such that after the rewind this height corresponds to the latest block
|
||||||
|
* appended to the tree. The number of blocks that were removed from the witness
|
||||||
|
* tree in the rewind process is returned via `blocksRewoundRet`.
|
||||||
|
*
|
||||||
|
* Returns `true` if the rewind is successful, in which case the number of blocks that were
|
||||||
|
* removed from the witness tree in the rewind process is returned via `blocksRewoundRet`;
|
||||||
|
* this returns `false` and leaves `blocksRewoundRet` unmodified.
|
||||||
|
*/
|
||||||
|
bool orchard_wallet_rewind(
|
||||||
|
OrchardWalletPtr* wallet,
|
||||||
|
uint32_t blockHeight,
|
||||||
|
uint32_t* blocksRewoundRet
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A C struct used to transfer action_idx/IVK pairs back from Rust across the FFI
|
||||||
|
* boundary. This must have the same in-memory representation as the `FFIActionIVK` type
|
||||||
|
* in orchard_ffi/wallet.rs.
|
||||||
|
*
|
||||||
|
* Values of the `ivk` pointer must be freed manually; the best way to do this is to
|
||||||
|
* wrap this pointer in an `OrchardIncomingViewingKey` which handles deallocation
|
||||||
|
* in the object destructor.
|
||||||
|
*/
|
||||||
|
struct RawOrchardActionIVK {
|
||||||
|
uint64_t actionIdx;
|
||||||
|
OrchardIncomingViewingKeyPtr* ivk;
|
||||||
|
};
|
||||||
|
static_assert(
|
||||||
|
sizeof(RawOrchardActionIVK) == 16,
|
||||||
|
"RawOrchardActionIVK struct should have exactly a 128-bit in-memory representation.");
|
||||||
|
static_assert(alignof(RawOrchardActionIVK) == 8, "RawOrchardActionIVK struct alignment is not 64 bits.");
|
||||||
|
|
||||||
|
typedef void (*push_action_ivk_callback_t)(void* rec, const RawOrchardActionIVK actionIvk);
|
||||||
|
|
||||||
|
typedef void (*push_spend_action_idx_callback_t)(void* rec, uint32_t actionIdx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the provided bundle for notes that are visible to the specified wallet's
|
||||||
|
* incoming viewing keys, and adds those notes to the wallet. For each note decryptable
|
||||||
|
* by one of the wallet's keys, this method will insert a `RawOrchardActionIVK` value into
|
||||||
|
* the provided `callbackReceiver` referent using the `push_cb` callback. Note that
|
||||||
|
* this callback can perform transformations on the provided RawOrchardActionIVK in this
|
||||||
|
* process. For each action spending one of the wallet's notes, this method will pass
|
||||||
|
* a `uint32_t` action index corresponding to that action to the `callbackReceiver` referent;
|
||||||
|
* using the specified callback; usually, this will push the value into a result vector owned
|
||||||
|
* by the caller.
|
||||||
|
*
|
||||||
|
* The provided bundle must be a component of the transaction from which `txid` was
|
||||||
|
* derived.
|
||||||
|
*
|
||||||
|
* Returns `true` if the bundle is involved with the wallet; i.e. if it contains
|
||||||
|
* notes spendable by the wallet, or spends any of the wallet's notes.
|
||||||
|
*/
|
||||||
|
bool orchard_wallet_add_notes_from_bundle(
|
||||||
|
OrchardWalletPtr* wallet,
|
||||||
|
const unsigned char txid[32],
|
||||||
|
const OrchardBundlePtr* bundle,
|
||||||
|
void* callbackReceiver,
|
||||||
|
push_action_ivk_callback_t push_cb,
|
||||||
|
push_spend_action_idx_callback_t spend_cb
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts a selection of notes from the bundle with specified incoming viewing
|
||||||
|
* keys, and adds those notes to the wallet.
|
||||||
|
*
|
||||||
|
* The provided bundle must be a component of the transaction from which
|
||||||
|
* `txid` was derived.
|
||||||
|
*
|
||||||
|
* The value the `blockHeight` pointer points to be set to the height at which the
|
||||||
|
* transaction was mined, or `nullptr` if the transaction is not in the main chain.
|
||||||
|
*/
|
||||||
|
bool orchard_wallet_load_bundle(
|
||||||
|
OrchardWalletPtr* wallet,
|
||||||
|
const uint32_t* blockHeight,
|
||||||
|
const unsigned char txid[32],
|
||||||
|
const OrchardBundlePtr* bundle,
|
||||||
|
const RawOrchardActionIVK* actionIvks,
|
||||||
|
size_t actionIvksLen,
|
||||||
|
const uint32_t* actionsSpendingWalletNotes,
|
||||||
|
size_t actionsSpendingWalletNotesLen
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the note commitment values for the specified bundle to the wallet's note
|
||||||
|
* commitment tree, and mark any Orchard notes that belong to the wallet so
|
||||||
|
* that we can construct authentication paths to these notes in the future.
|
||||||
|
*
|
||||||
|
* This requires the block height and the index of the block within the
|
||||||
|
* transaction in order to guarantee that note commitments are appended in the
|
||||||
|
* correct order. Returns `false` if the provided bundle is not in the correct
|
||||||
|
* position to have its note commitments appended to the note commitment tree.
|
||||||
|
*/
|
||||||
|
bool orchard_wallet_append_bundle_commitments(
|
||||||
|
OrchardWalletPtr* wallet,
|
||||||
|
const uint32_t block_height,
|
||||||
|
const size_t block_tx_idx,
|
||||||
|
const unsigned char txid[32],
|
||||||
|
const OrchardBundlePtr* bundle
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the root of the wallet's note commitment tree.
|
||||||
|
*/
|
||||||
|
void orchard_wallet_commitment_tree_root(
|
||||||
|
const OrchardWalletPtr* wallet,
|
||||||
|
unsigned char* root_ret);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the specified transaction involves any Orchard notes that belong to
|
||||||
|
* this wallet.
|
||||||
|
*/
|
||||||
|
bool orchard_wallet_tx_involves_my_notes(
|
||||||
|
const OrchardWalletPtr* wallet,
|
||||||
|
const unsigned char txid[32]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the specified spending key to the wallet's key store. This will also compute and
|
||||||
|
* add the associated full and incoming viewing keys.
|
||||||
|
*/
|
||||||
|
void orchard_wallet_add_spending_key(
|
||||||
|
OrchardWalletPtr* wallet,
|
||||||
|
const OrchardSpendingKeyPtr* sk);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the specified full viewing key to the wallet's key store. This will also compute
|
||||||
|
* and add the associated incoming viewing key.
|
||||||
|
*/
|
||||||
|
void orchard_wallet_add_full_viewing_key(
|
||||||
|
OrchardWalletPtr* wallet,
|
||||||
|
const OrchardFullViewingKeyPtr* fvk);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the specified raw address to the wallet's key store, associated with the incoming
|
||||||
|
* viewing key from which that address was derived.
|
||||||
|
*/
|
||||||
|
bool orchard_wallet_add_raw_address(
|
||||||
|
OrchardWalletPtr* wallet,
|
||||||
|
const OrchardRawAddressPtr* addr,
|
||||||
|
const OrchardIncomingViewingKeyPtr* ivk);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a pointer to the Orchard spending key corresponding to the specified raw
|
||||||
|
* address, if it is known to the wallet, or `nullptr` otherwise.
|
||||||
|
*
|
||||||
|
* Memory is allocated by Rust and must be manually freed using
|
||||||
|
* `orchard_spending_key_free`.
|
||||||
|
*/
|
||||||
|
OrchardSpendingKeyPtr* orchard_wallet_get_spending_key_for_address(
|
||||||
|
const OrchardWalletPtr* wallet,
|
||||||
|
const OrchardRawAddressPtr* addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a pointer to the Orchard incoming viewing key corresponding to the specified
|
||||||
|
* raw address, if it is known to the wallet, or `nullptr` otherwise.
|
||||||
|
*
|
||||||
|
* Memory is allocated by Rust and must be manually freed using
|
||||||
|
* `orchard_incoming_viewing_key_free`.
|
||||||
|
*/
|
||||||
|
OrchardIncomingViewingKeyPtr* orchard_wallet_get_ivk_for_address(
|
||||||
|
const OrchardWalletPtr* wallet,
|
||||||
|
const OrchardRawAddressPtr* addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A C struct used to transfer note metadata information across the Rust FFI boundary.
|
||||||
|
* This must have the same in-memory representation as the `FFINoteMetadata` type in
|
||||||
|
* orchard_ffi/wallet.rs.
|
||||||
|
*/
|
||||||
|
struct RawOrchardNoteMetadata {
|
||||||
|
unsigned char txid[32];
|
||||||
|
uint32_t actionIdx;
|
||||||
|
OrchardRawAddressPtr* addr;
|
||||||
|
CAmount noteValue;
|
||||||
|
unsigned char memo[512];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void (*push_note_callback_t)(void* resultVector, const RawOrchardNoteMetadata noteMeta);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds notes that belong to the wallet that were sent to addresses derived from the
|
||||||
|
* specified incoming viewing key, subject to the specified flags, and uses the provided
|
||||||
|
* callback to push RawOrchardNoteMetadata values corresponding to those notes on to the
|
||||||
|
* provided result vector. Note that the push_cb callback can perform any necessary
|
||||||
|
* conversion from a RawOrchardNoteMetadata value in addition to modifying the provided
|
||||||
|
* result vector.
|
||||||
|
*
|
||||||
|
* If `ivk` is null, all notes belonging to the wallet will be returned. The
|
||||||
|
* `RawOrchardNoteMetadata::addr` pointers for values provided to the callback must be
|
||||||
|
* manually freed by the caller.
|
||||||
|
*/
|
||||||
|
void orchard_wallet_get_filtered_notes(
|
||||||
|
const OrchardWalletPtr* wallet,
|
||||||
|
const OrchardIncomingViewingKeyPtr* ivk,
|
||||||
|
bool ignoreMined,
|
||||||
|
bool requireSpendingKey,
|
||||||
|
void* resultVector,
|
||||||
|
push_note_callback_t push_cb
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A C struct used to transfer Orchard action spend information across the FFI boundary.
|
||||||
|
* This must have the same in-memory representation as the `FFIActionSpend` type in
|
||||||
|
* orchard_ffi/wallet.rs.
|
||||||
|
*/
|
||||||
|
struct RawOrchardActionSpend {
|
||||||
|
uint32_t spendActionIdx;
|
||||||
|
unsigned char outpointTxId[32];
|
||||||
|
uint32_t outpointActionIdx;
|
||||||
|
OrchardRawAddressPtr* receivedAt;
|
||||||
|
CAmount noteValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A C struct used to transfer Orchard action output information across the FFI boundary.
|
||||||
|
* This must have the same in-memory representation as the `FFIActionOutput` type in
|
||||||
|
* orchard_ffi/wallet.rs.
|
||||||
|
*/
|
||||||
|
struct RawOrchardActionOutput {
|
||||||
|
uint32_t outputActionIdx;
|
||||||
|
OrchardRawAddressPtr* recipient;
|
||||||
|
CAmount noteValue;
|
||||||
|
unsigned char memo[512];
|
||||||
|
bool isOutgoing;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void (*push_spend_t)(void* callbackReceiver, const RawOrchardActionSpend data);
|
||||||
|
|
||||||
|
typedef void (*push_output_t)(void* callbackReceiver, const RawOrchardActionOutput data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trial-decrypts the specfied Orchard bundle, and uses the provided callbacks to pass
|
||||||
|
* `RawOrchardActionSpend` and `RawOrchardActionOutput` values (corresponding to the
|
||||||
|
* actions of that bundle) to the provided result receiver.
|
||||||
|
*
|
||||||
|
* Note that the callbacks can perform any necessary conversion from a
|
||||||
|
* `RawOrchardActionSpend` or `RawOrchardActionOutput` value in addition to modifying the
|
||||||
|
* provided result receiver.
|
||||||
|
*
|
||||||
|
* `raw_ovks` must be a pointer to an array of `unsigned char[32]`.
|
||||||
|
*
|
||||||
|
* The `recipient` pointer for each `RawOrchardActionOutput` value, and the `receivedAt`
|
||||||
|
* pointer for each `RawOrchardActionSpend` value, must be freed using
|
||||||
|
* `orchard_address_free`.
|
||||||
|
*/
|
||||||
|
bool orchard_wallet_get_txdata(
|
||||||
|
const OrchardWalletPtr* wallet,
|
||||||
|
const OrchardBundlePtr* bundle,
|
||||||
|
const unsigned char* raw_ovks,
|
||||||
|
size_t raw_ovks_len,
|
||||||
|
void* callbackReceiver,
|
||||||
|
push_spend_t push_spend_cb,
|
||||||
|
push_output_t push_output_cb
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef void (*push_txid_callback_t)(void* resultVector, unsigned char txid[32]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a vector of transaction IDs for transactions that have been observed as
|
||||||
|
* spending the given outpoint (transaction ID and action index) by using the `push_cb`
|
||||||
|
* callback to push transaction IDs onto the provided result vector.
|
||||||
|
*/
|
||||||
|
void orchard_wallet_get_potential_spends(
|
||||||
|
const OrchardWalletPtr* wallet,
|
||||||
|
const unsigned char txid[32],
|
||||||
|
const uint32_t action_idx,
|
||||||
|
void* resultVector,
|
||||||
|
push_txid_callback_t push_cb
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the information needed to spend the wallet note at the given outpoint,
|
||||||
|
* relative to the current root known to the wallet of the Orchard commitment
|
||||||
|
* tree.
|
||||||
|
*
|
||||||
|
* Returns `null` if the outpoint is not known to the wallet, or the Orchard
|
||||||
|
* bundle containing the note has not been passed to
|
||||||
|
* `orchard_wallet_append_bundle_commitments`.
|
||||||
|
*/
|
||||||
|
OrchardSpendInfoPtr* orchard_wallet_get_spend_info(
|
||||||
|
const OrchardWalletPtr* wallet,
|
||||||
|
const unsigned char txid[32],
|
||||||
|
uint32_t action_idx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the garbage collection operation on the wallet's note commitment
|
||||||
|
* tree.
|
||||||
|
*/
|
||||||
|
void orchard_wallet_gc_note_commitment_tree(OrchardWalletPtr* wallet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the wallet's note commitment tree to the provided stream.
|
||||||
|
*/
|
||||||
|
bool orchard_wallet_write_note_commitment_tree(
|
||||||
|
const OrchardWalletPtr* wallet,
|
||||||
|
void* stream,
|
||||||
|
write_callback_t write_cb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a note commitment tree from the provided stream, and update the wallet's internal
|
||||||
|
* note commitment tree state to equal the value that was read.
|
||||||
|
*/
|
||||||
|
bool orchard_wallet_load_note_commitment_tree(
|
||||||
|
OrchardWalletPtr* wallet,
|
||||||
|
void* stream,
|
||||||
|
read_callback_t read_cb);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // ZCASH_RUST_INCLUDE_RUST_ORCHARD_WALLET_H
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,22 @@ bool unified_full_viewing_key_read_sapling(
|
||||||
const UnifiedFullViewingKeyPtr* full_viewing_key,
|
const UnifiedFullViewingKeyPtr* full_viewing_key,
|
||||||
unsigned char* skeyout);
|
unsigned char* skeyout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the Orchard component of a unified full viewing key.
|
||||||
|
*
|
||||||
|
* `skeyout` must be of length 96.
|
||||||
|
*
|
||||||
|
* Returns `true` if the UFVK contained an Orchard component, `false` otherwise.
|
||||||
|
* The bytes of the Orchard Raw Full Viewing Key, in the encoding given in
|
||||||
|
* section 5.6.4.4 of the Zcash Protocol Specification, will be copied to
|
||||||
|
* `skeyout` if `true` is returned.
|
||||||
|
*
|
||||||
|
* If `false` is returned then `skeyout` will be unchanged.
|
||||||
|
*/
|
||||||
|
bool unified_full_viewing_key_read_orchard(
|
||||||
|
const UnifiedFullViewingKeyPtr* full_viewing_key,
|
||||||
|
unsigned char* skeyout);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a unified full viewing key from the binary encodings
|
* Constructs a unified full viewing key from the binary encodings
|
||||||
* of its constituent parts.
|
* of its constituent parts.
|
||||||
|
@ -101,7 +117,8 @@ bool unified_full_viewing_key_read_sapling(
|
||||||
*/
|
*/
|
||||||
UnifiedFullViewingKeyPtr* unified_full_viewing_key_from_components(
|
UnifiedFullViewingKeyPtr* unified_full_viewing_key_from_components(
|
||||||
const unsigned char* t_key,
|
const unsigned char* t_key,
|
||||||
const unsigned char* sapling_key);
|
const unsigned char* sapling_key,
|
||||||
|
const unsigned char* orchard_key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derive the internal and external OVKs for the binary encoding
|
* Derive the internal and external OVKs for the binary encoding
|
||||||
|
|
|
@ -12,6 +12,8 @@ use zcash_address::{
|
||||||
use zcash_primitives::sapling;
|
use zcash_primitives::sapling;
|
||||||
|
|
||||||
pub type UnifiedAddressObj = NonNull<c_void>;
|
pub type UnifiedAddressObj = NonNull<c_void>;
|
||||||
|
pub type AddOrchardReceiverCb =
|
||||||
|
unsafe extern "C" fn(ua: Option<UnifiedAddressObj>, orchard: *const orchard::Address) -> bool;
|
||||||
pub type AddReceiverCb =
|
pub type AddReceiverCb =
|
||||||
unsafe extern "C" fn(ua: Option<UnifiedAddressObj>, raw: *const u8) -> bool;
|
unsafe extern "C" fn(ua: Option<UnifiedAddressObj>, raw: *const u8) -> bool;
|
||||||
pub type UnknownReceiverCb = unsafe extern "C" fn(
|
pub type UnknownReceiverCb = unsafe extern "C" fn(
|
||||||
|
@ -53,10 +55,12 @@ impl FromAddress for UnifiedAddressHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnifiedAddressHelper {
|
impl UnifiedAddressHelper {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn into_cpp(
|
fn into_cpp(
|
||||||
self,
|
self,
|
||||||
network: Network,
|
network: Network,
|
||||||
ua_obj: Option<UnifiedAddressObj>,
|
ua_obj: Option<UnifiedAddressObj>,
|
||||||
|
orchard_cb: Option<AddOrchardReceiverCb>,
|
||||||
sapling_cb: Option<AddReceiverCb>,
|
sapling_cb: Option<AddReceiverCb>,
|
||||||
p2sh_cb: Option<AddReceiverCb>,
|
p2sh_cb: Option<AddReceiverCb>,
|
||||||
p2pkh_cb: Option<AddReceiverCb>,
|
p2pkh_cb: Option<AddReceiverCb>,
|
||||||
|
@ -79,16 +83,13 @@ impl UnifiedAddressHelper {
|
||||||
// ZIP 316: Consumers MUST reject Unified Addresses/Viewing Keys in
|
// ZIP 316: Consumers MUST reject Unified Addresses/Viewing Keys in
|
||||||
// which any constituent Item does not meet the validation
|
// which any constituent Item does not meet the validation
|
||||||
// requirements of its encoding.
|
// requirements of its encoding.
|
||||||
if orchard::Address::from_raw_address_bytes(&data)
|
let addr = orchard::Address::from_raw_address_bytes(&data);
|
||||||
.is_none()
|
if addr.is_none().into() {
|
||||||
.into()
|
|
||||||
{
|
|
||||||
tracing::error!("Unified Address contains invalid Orchard receiver");
|
tracing::error!("Unified Address contains invalid Orchard receiver");
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
unsafe {
|
unsafe {
|
||||||
// TODO: Replace with Orchard support.
|
(orchard_cb.unwrap())(ua_obj, Box::into_raw(Box::new(addr.unwrap())))
|
||||||
(unknown_cb.unwrap())(ua_obj, 0x03, data.as_ptr(), data.len())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,6 +123,7 @@ pub extern "C" fn zcash_address_parse_unified(
|
||||||
encoded: *const c_char,
|
encoded: *const c_char,
|
||||||
network: *const c_char,
|
network: *const c_char,
|
||||||
ua_obj: Option<UnifiedAddressObj>,
|
ua_obj: Option<UnifiedAddressObj>,
|
||||||
|
orchard_cb: Option<AddOrchardReceiverCb>,
|
||||||
sapling_cb: Option<AddReceiverCb>,
|
sapling_cb: Option<AddReceiverCb>,
|
||||||
p2sh_cb: Option<AddReceiverCb>,
|
p2sh_cb: Option<AddReceiverCb>,
|
||||||
p2pkh_cb: Option<AddReceiverCb>,
|
p2pkh_cb: Option<AddReceiverCb>,
|
||||||
|
@ -149,7 +151,9 @@ pub extern "C" fn zcash_address_parse_unified(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ua.into_cpp(network, ua_obj, sapling_cb, p2sh_cb, p2pkh_cb, unknown_cb)
|
ua.into_cpp(
|
||||||
|
network, ua_obj, orchard_cb, sapling_cb, p2sh_cb, p2pkh_cb, unknown_cb,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
@ -171,14 +175,9 @@ pub extern "C" fn zcash_address_serialize_unified(
|
||||||
Ok(
|
Ok(
|
||||||
match unsafe { (typecode_cb.unwrap())(ua_obj, i) }.try_into()? {
|
match unsafe { (typecode_cb.unwrap())(ua_obj, i) }.try_into()? {
|
||||||
unified::Typecode::Orchard => {
|
unified::Typecode::Orchard => {
|
||||||
// TODO: Replace with Orchard support.
|
let mut data = [0; 43];
|
||||||
let data_len = unsafe { (receiver_len_cb.unwrap())(ua_obj, i) };
|
unsafe { (receiver_cb.unwrap())(ua_obj, i, data.as_mut_ptr(), data.len()) };
|
||||||
let mut data = vec![0; data_len];
|
unified::Receiver::Orchard(data)
|
||||||
unsafe { (receiver_cb.unwrap())(ua_obj, i, data.as_mut_ptr(), data_len) };
|
|
||||||
unified::Receiver::Unknown {
|
|
||||||
typecode: 0x03,
|
|
||||||
data,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
unified::Typecode::Sapling => {
|
unified::Typecode::Sapling => {
|
||||||
let mut data = [0; 43];
|
let mut data = [0; 43];
|
||||||
|
|
|
@ -0,0 +1,232 @@
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::ptr;
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
|
use incrementalmerkletree::Hashable;
|
||||||
|
use libc::size_t;
|
||||||
|
use orchard::keys::SpendingKey;
|
||||||
|
use orchard::{
|
||||||
|
builder::{Builder, InProgress, Unauthorized, Unproven},
|
||||||
|
bundle::{Authorized, Flags},
|
||||||
|
keys::{FullViewingKey, OutgoingViewingKey},
|
||||||
|
tree::{MerkleHashOrchard, MerklePath},
|
||||||
|
value::NoteValue,
|
||||||
|
Bundle, Note,
|
||||||
|
};
|
||||||
|
use rand_core::OsRng;
|
||||||
|
use tracing::error;
|
||||||
|
use zcash_primitives::transaction::{
|
||||||
|
components::{sapling, Amount},
|
||||||
|
sighash::SignableInput,
|
||||||
|
sighash_v5::v5_signature_hash,
|
||||||
|
txid::TxIdDigester,
|
||||||
|
Authorization, TransactionData, TxVersion,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
transaction_ffi::{PrecomputedTxParts, TransparentAuth},
|
||||||
|
ORCHARD_PK,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct OrchardSpendInfo {
|
||||||
|
fvk: FullViewingKey,
|
||||||
|
note: Note,
|
||||||
|
merkle_path: MerklePath,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OrchardSpendInfo {
|
||||||
|
pub fn from_parts(fvk: FullViewingKey, note: Note, merkle_path: MerklePath) -> Self {
|
||||||
|
OrchardSpendInfo {
|
||||||
|
fvk,
|
||||||
|
note,
|
||||||
|
merkle_path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_spend_info_free(spend_info: *mut OrchardSpendInfo) {
|
||||||
|
if !spend_info.is_null() {
|
||||||
|
drop(unsafe { Box::from_raw(spend_info) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_builder_new(
|
||||||
|
spends_enabled: bool,
|
||||||
|
outputs_enabled: bool,
|
||||||
|
anchor: *const [u8; 32],
|
||||||
|
) -> *mut Builder {
|
||||||
|
let anchor = unsafe { anchor.as_ref() }
|
||||||
|
.map(|a| orchard::Anchor::from_bytes(*a).unwrap())
|
||||||
|
.unwrap_or_else(|| MerkleHashOrchard::empty_root(32.into()).into());
|
||||||
|
Box::into_raw(Box::new(Builder::new(
|
||||||
|
Flags::from_parts(spends_enabled, outputs_enabled),
|
||||||
|
anchor,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_builder_add_spend(
|
||||||
|
builder: *mut Builder,
|
||||||
|
orchard_spend_info: *mut OrchardSpendInfo,
|
||||||
|
) -> bool {
|
||||||
|
let builder = unsafe { builder.as_mut() }.expect("Builder may not be null.");
|
||||||
|
let orchard_spend_info = unsafe { Box::from_raw(orchard_spend_info) };
|
||||||
|
|
||||||
|
match builder.add_spend(
|
||||||
|
orchard_spend_info.fvk,
|
||||||
|
orchard_spend_info.note,
|
||||||
|
orchard_spend_info.merkle_path,
|
||||||
|
) {
|
||||||
|
Ok(()) => true,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to add Orchard spend: {}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_builder_add_recipient(
|
||||||
|
builder: *mut Builder,
|
||||||
|
ovk: *const [u8; 32],
|
||||||
|
recipient: *const orchard::Address,
|
||||||
|
value: u64,
|
||||||
|
memo: *const [u8; 512],
|
||||||
|
) -> bool {
|
||||||
|
let builder = unsafe { builder.as_mut() }.expect("Builder may not be null.");
|
||||||
|
let ovk = unsafe { ovk.as_ref() }
|
||||||
|
.copied()
|
||||||
|
.map(OutgoingViewingKey::from);
|
||||||
|
let recipient = unsafe { recipient.as_ref() }.expect("Recipient may not be null.");
|
||||||
|
let value = NoteValue::from_raw(value);
|
||||||
|
let memo = unsafe { memo.as_ref() }.copied();
|
||||||
|
|
||||||
|
match builder.add_recipient(ovk, *recipient, value, memo) {
|
||||||
|
Ok(()) => true,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to add Orchard recipient: {}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_builder_free(builder: *mut Builder) {
|
||||||
|
if !builder.is_null() {
|
||||||
|
drop(unsafe { Box::from_raw(builder) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_builder_build(
|
||||||
|
builder: *mut Builder,
|
||||||
|
) -> *mut Bundle<InProgress<Unproven, Unauthorized>, Amount> {
|
||||||
|
if builder.is_null() {
|
||||||
|
error!("Called with null builder");
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
let builder = unsafe { Box::from_raw(builder) };
|
||||||
|
|
||||||
|
match builder.build(OsRng) {
|
||||||
|
Ok(bundle) => Box::into_raw(Box::new(bundle)),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to build Orchard bundle: {:?}", e);
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_unauthorized_bundle_free(
|
||||||
|
bundle: *mut Bundle<InProgress<Unproven, Unauthorized>, Amount>,
|
||||||
|
) {
|
||||||
|
if !bundle.is_null() {
|
||||||
|
drop(unsafe { Box::from_raw(bundle) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_unauthorized_bundle_prove_and_sign(
|
||||||
|
bundle: *mut Bundle<InProgress<Unproven, Unauthorized>, Amount>,
|
||||||
|
keys: *const *const SpendingKey,
|
||||||
|
keys_len: size_t,
|
||||||
|
sighash: *const [u8; 32],
|
||||||
|
) -> *mut Bundle<Authorized, Amount> {
|
||||||
|
let bundle = unsafe { Box::from_raw(bundle) };
|
||||||
|
let keys = unsafe { slice::from_raw_parts(keys, keys_len) };
|
||||||
|
let sighash = unsafe { sighash.as_ref() }.expect("sighash pointer may not be null.");
|
||||||
|
let pk = unsafe { ORCHARD_PK.as_ref() }.unwrap();
|
||||||
|
|
||||||
|
let signing_keys = keys
|
||||||
|
.iter()
|
||||||
|
.map(|sk| {
|
||||||
|
unsafe { sk.as_ref() }
|
||||||
|
.expect("SpendingKey pointers must not be null")
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let res = bundle
|
||||||
|
.create_proof(pk, &mut rng)
|
||||||
|
.and_then(|b| b.apply_signatures(&mut rng, *sighash, &signing_keys));
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(signed) => Box::into_raw(Box::new(signed)),
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"An error occurred while authorizing the orchard bundle: {:?}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates a ZIP 244 shielded signature digest for the given under-construction
|
||||||
|
/// transaction.
|
||||||
|
///
|
||||||
|
/// Returns `false` if any of the parameters are invalid; in this case, `sighash_ret`
|
||||||
|
/// will be unaltered.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn zcash_builder_zip244_shielded_signature_digest(
|
||||||
|
precomputed_tx: *mut PrecomputedTxParts,
|
||||||
|
bundle: *const Bundle<InProgress<Unproven, Unauthorized>, Amount>,
|
||||||
|
sighash_ret: *mut [u8; 32],
|
||||||
|
) -> bool {
|
||||||
|
let precomputed_tx = if !precomputed_tx.is_null() {
|
||||||
|
unsafe { Box::from_raw(precomputed_tx) }
|
||||||
|
} else {
|
||||||
|
error!("Invalid precomputed transaction");
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if matches!(
|
||||||
|
precomputed_tx.tx.version(),
|
||||||
|
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling,
|
||||||
|
) {
|
||||||
|
error!("Cannot calculate ZIP 244 digest for pre-v5 transaction");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let bundle = unsafe { bundle.as_ref().unwrap() };
|
||||||
|
|
||||||
|
struct Signable {}
|
||||||
|
impl Authorization for Signable {
|
||||||
|
type TransparentAuth = TransparentAuth;
|
||||||
|
type SaplingAuth = sapling::Authorized;
|
||||||
|
type OrchardAuth = InProgress<Unproven, Unauthorized>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let txdata: TransactionData<Signable> =
|
||||||
|
precomputed_tx
|
||||||
|
.tx
|
||||||
|
.map_bundles(|b| b, |b| b, |_| Some(bundle.clone()));
|
||||||
|
let txid_parts = txdata.digest(TxIdDigester);
|
||||||
|
|
||||||
|
let sighash = v5_signature_hash(&txdata, &SignableInput::Shielded, &txid_parts);
|
||||||
|
|
||||||
|
// `v5_signature_hash` output is always 32 bytes.
|
||||||
|
*unsafe { &mut *sighash_ret } = sighash.as_ref().try_into().unwrap();
|
||||||
|
true
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
|
||||||
|
use incrementalmerkletree::{
|
||||||
|
bridgetree::{BridgeTree, Checkpoint},
|
||||||
|
Hashable,
|
||||||
|
};
|
||||||
|
use zcash_encoding::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,
|
||||||
|
},
|
||||||
|
HashSer,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn write_checkpoint_v1<H: HashSer + Ord, W: Write>(
|
||||||
|
mut writer: W,
|
||||||
|
checkpoint: &Checkpoint<H>,
|
||||||
|
) -> 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.forgotten().iter(),
|
||||||
|
|mut w, ((pos, leaf_value), idx)| {
|
||||||
|
write_position(&mut w, *pos)?;
|
||||||
|
leaf_value.write(&mut w)?;
|
||||||
|
write_usize_leu64(&mut w, *idx)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_checkpoint_v1<H: HashSer + Ord, R: Read>(mut reader: R) -> io::Result<Checkpoint<H>> {
|
||||||
|
Ok(Checkpoint::from_parts(
|
||||||
|
read_leu64_usize(&mut reader)?,
|
||||||
|
reader.read_u8()? == 1,
|
||||||
|
Vector::read_collected(&mut reader, |mut r| {
|
||||||
|
Ok((
|
||||||
|
(read_position(&mut r)?, H::read(&mut r)?),
|
||||||
|
read_leu64_usize(&mut r)?,
|
||||||
|
))
|
||||||
|
})?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_tree_v1<H: Hashable + HashSer + Ord, W: Write>(
|
||||||
|
mut writer: W,
|
||||||
|
tree: &BridgeTree<H, 32>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
Vector::write(&mut writer, tree.bridges(), |w, b| write_bridge_v1(w, b))?;
|
||||||
|
Vector::write_sized(
|
||||||
|
&mut writer,
|
||||||
|
tree.witnessed_indices().iter(),
|
||||||
|
|mut w, ((pos, a), i)| {
|
||||||
|
write_position(&mut w, *pos)?;
|
||||||
|
a.write(&mut w)?;
|
||||||
|
write_usize_leu64(&mut w, *i)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
Vector::write(&mut writer, tree.checkpoints(), |w, c| {
|
||||||
|
write_checkpoint_v1(w, c)
|
||||||
|
})?;
|
||||||
|
write_usize_leu64(&mut writer, tree.max_checkpoints())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::redundant_closure)]
|
||||||
|
pub fn read_tree_v1<H: Hashable + HashSer + Ord + Clone, R: Read>(
|
||||||
|
mut reader: R,
|
||||||
|
) -> io::Result<BridgeTree<H, 32>> {
|
||||||
|
BridgeTree::from_parts(
|
||||||
|
Vector::read(&mut reader, |r| read_bridge_v1(r))?,
|
||||||
|
Vector::read_collected(&mut reader, |mut r| {
|
||||||
|
Ok((
|
||||||
|
(read_position(&mut r)?, H::read(&mut r)?),
|
||||||
|
read_leu64_usize(&mut r)?,
|
||||||
|
))
|
||||||
|
})?,
|
||||||
|
Vector::read(&mut reader, |r| read_checkpoint_v1(r))?,
|
||||||
|
read_leu64_usize(&mut reader)?,
|
||||||
|
)
|
||||||
|
.map_err(|err| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
format!(
|
||||||
|
"Consistency violation found when attempting to deserialize Merkle tree: {:?}",
|
||||||
|
err
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_tree<H: Hashable + HashSer + Ord, W: Write>(
|
||||||
|
mut writer: W,
|
||||||
|
tree: &BridgeTree<H, 32>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
writer.write_u8(SER_V1)?;
|
||||||
|
write_tree_v1(&mut writer, tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_tree<H: Hashable + HashSer + Ord + Clone, R: Read>(
|
||||||
|
mut reader: R,
|
||||||
|
) -> io::Result<BridgeTree<H, 32>> {
|
||||||
|
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),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
|
@ -123,13 +123,13 @@ pub extern "C" fn orchard_merkle_frontier_root(
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn orchard_merkle_frontier_num_leaves(
|
pub extern "C" fn orchard_merkle_frontier_num_leaves(
|
||||||
tree: *const bridgetree::Frontier<MerkleHashOrchard, MERKLE_DEPTH>,
|
tree: *const bridgetree::Frontier<MerkleHashOrchard, MERKLE_DEPTH>,
|
||||||
) -> usize {
|
) -> u64 {
|
||||||
let tree = unsafe {
|
let tree = unsafe {
|
||||||
tree.as_ref()
|
tree.as_ref()
|
||||||
.expect("Orchard note commitment tree pointer may not be null.")
|
.expect("Orchard note commitment tree pointer may not be null.")
|
||||||
};
|
};
|
||||||
|
|
||||||
tree.position().map_or(0, |p| (<u64>::from(p) + 1) as usize)
|
tree.position().map_or(0, |p| <u64>::from(p) + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
|
|
@ -2,10 +2,15 @@ use std::io::{Read, Write};
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use orchard::keys::{DiversifierIndex, FullViewingKey, IncomingViewingKey, SpendingKey};
|
use orchard::{
|
||||||
use orchard::Address;
|
keys::{DiversifierIndex, FullViewingKey, IncomingViewingKey, OutgoingViewingKey, SpendingKey},
|
||||||
|
Address,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb};
|
use crate::{
|
||||||
|
streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb},
|
||||||
|
zcashd_orchard::OrderedAddress,
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Addresses
|
// Addresses
|
||||||
|
@ -25,6 +30,63 @@ pub extern "C" fn orchard_address_free(addr: *mut Address) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_raw_address_parse(
|
||||||
|
stream: Option<StreamObj>,
|
||||||
|
read_cb: Option<ReadCb>,
|
||||||
|
) -> *mut Address {
|
||||||
|
let mut reader = CppStreamReader::from_raw_parts(stream, read_cb.unwrap());
|
||||||
|
|
||||||
|
let mut buf = [0u8; 43];
|
||||||
|
match reader.read_exact(&mut buf) {
|
||||||
|
Err(e) => {
|
||||||
|
error!("Stream failure reading bytes of Orchard raw address: {}", e);
|
||||||
|
std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
Ok(()) => {
|
||||||
|
let read = Address::from_raw_address_bytes(&buf);
|
||||||
|
if read.is_some().into() {
|
||||||
|
Box::into_raw(Box::new(read.unwrap()))
|
||||||
|
} else {
|
||||||
|
error!("Failed to parse Orchard raw address.");
|
||||||
|
std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_raw_address_serialize(
|
||||||
|
key: *const Address,
|
||||||
|
stream: Option<StreamObj>,
|
||||||
|
write_cb: Option<WriteCb>,
|
||||||
|
) -> bool {
|
||||||
|
let key = unsafe { key.as_ref() }.expect("Orchard raw address pointer may not be null.");
|
||||||
|
|
||||||
|
let mut writer = CppStreamWriter::from_raw_parts(stream, write_cb.unwrap());
|
||||||
|
match writer.write_all(&key.to_raw_address_bytes()) {
|
||||||
|
Ok(()) => true,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Stream failure writing Orchard raw address: {}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_address_eq(a0: *const Address, a1: *const Address) -> bool {
|
||||||
|
let a0 = unsafe { a0.as_ref() };
|
||||||
|
let a1 = unsafe { a1.as_ref() };
|
||||||
|
a0 == a1
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_address_lt(a0: *const Address, a1: *const Address) -> bool {
|
||||||
|
let a0 = unsafe { a0.as_ref() };
|
||||||
|
let a1 = unsafe { a1.as_ref() };
|
||||||
|
a0.map(|a| OrderedAddress::new(*a)) < a1.map(|a| OrderedAddress::new(*a))
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Incoming viewing keys
|
// Incoming viewing keys
|
||||||
//
|
//
|
||||||
|
@ -85,6 +147,26 @@ pub extern "C" fn orchard_incoming_viewing_key_to_address(
|
||||||
Box::into_raw(Box::new(key.address_at(diversifier_index)))
|
Box::into_raw(Box::new(key.address_at(diversifier_index)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_incoming_viewing_key_decrypt_diversifier(
|
||||||
|
key: *const IncomingViewingKey,
|
||||||
|
addr: *const Address,
|
||||||
|
j_ret: *mut [u8; 11],
|
||||||
|
) -> bool {
|
||||||
|
let key =
|
||||||
|
unsafe { key.as_ref() }.expect("Orchard incoming viewing key pointer may not be null.");
|
||||||
|
let addr = unsafe { addr.as_ref() }.expect("Orchard raw address pointer may not be null.");
|
||||||
|
let j_ret = unsafe { j_ret.as_mut() }.expect("j_ret may not be null.");
|
||||||
|
|
||||||
|
match key.diversifier_index(addr) {
|
||||||
|
Some(j) => {
|
||||||
|
j_ret.copy_from_slice(j.to_bytes());
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn orchard_incoming_viewing_key_serialize(
|
pub extern "C" fn orchard_incoming_viewing_key_serialize(
|
||||||
key: *const IncomingViewingKey,
|
key: *const IncomingViewingKey,
|
||||||
|
@ -190,6 +272,43 @@ pub extern "C" fn orchard_full_viewing_key_to_incoming_viewing_key(
|
||||||
.unwrap_or(std::ptr::null_mut())
|
.unwrap_or(std::ptr::null_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_full_viewing_key_to_internal_incoming_viewing_key(
|
||||||
|
fvk: *const FullViewingKey,
|
||||||
|
) -> *mut IncomingViewingKey {
|
||||||
|
unsafe { fvk.as_ref() }
|
||||||
|
.map(|fvk| {
|
||||||
|
let internal_fvk = fvk.derive_internal();
|
||||||
|
Box::into_raw(Box::new(IncomingViewingKey::from(&internal_fvk)))
|
||||||
|
})
|
||||||
|
.unwrap_or(std::ptr::null_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_full_viewing_key_to_external_outgoing_viewing_key(
|
||||||
|
fvk: *const FullViewingKey,
|
||||||
|
ovk_ret: *mut [u8; 32],
|
||||||
|
) {
|
||||||
|
let fvk = unsafe { fvk.as_ref() }.expect("fvk must not be null");
|
||||||
|
let ovk_ret = unsafe { ovk_ret.as_mut() }.expect("ovk_ret must not be null");
|
||||||
|
|
||||||
|
let ovk = OutgoingViewingKey::from(fvk);
|
||||||
|
*ovk_ret = *ovk.as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_full_viewing_key_to_internal_outgoing_viewing_key(
|
||||||
|
fvk: *const FullViewingKey,
|
||||||
|
ovk_ret: *mut [u8; 32],
|
||||||
|
) {
|
||||||
|
let fvk = unsafe { fvk.as_ref() }.expect("fvk must not be null");
|
||||||
|
let ovk_ret = unsafe { ovk_ret.as_mut() }.expect("ovk_ret must not be null");
|
||||||
|
|
||||||
|
let internal_fvk = fvk.derive_internal();
|
||||||
|
let ovk = OutgoingViewingKey::from(&internal_fvk);
|
||||||
|
*ovk_ret = *ovk.as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn orchard_full_viewing_key_eq(
|
pub extern "C" fn orchard_full_viewing_key_eq(
|
||||||
k0: *const FullViewingKey,
|
k0: *const FullViewingKey,
|
||||||
|
|
|
@ -29,6 +29,7 @@ use std::fs::File;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
use std::sync::Once;
|
||||||
use subtle::CtOption;
|
use subtle::CtOption;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
|
@ -69,14 +70,18 @@ mod ed25519;
|
||||||
mod metrics_ffi;
|
mod metrics_ffi;
|
||||||
mod streams_ffi;
|
mod streams_ffi;
|
||||||
mod tracing_ffi;
|
mod tracing_ffi;
|
||||||
|
mod zcashd_orchard;
|
||||||
|
|
||||||
mod address_ffi;
|
mod address_ffi;
|
||||||
|
mod builder_ffi;
|
||||||
mod history_ffi;
|
mod history_ffi;
|
||||||
|
mod incremental_merkle_tree;
|
||||||
mod incremental_merkle_tree_ffi;
|
mod incremental_merkle_tree_ffi;
|
||||||
mod orchard_ffi;
|
mod orchard_ffi;
|
||||||
mod orchard_keys_ffi;
|
mod orchard_keys_ffi;
|
||||||
mod transaction_ffi;
|
mod transaction_ffi;
|
||||||
mod unified_keys_ffi;
|
mod unified_keys_ffi;
|
||||||
|
mod wallet;
|
||||||
mod zip339_ffi;
|
mod zip339_ffi;
|
||||||
|
|
||||||
mod test_harness_ffi;
|
mod test_harness_ffi;
|
||||||
|
@ -84,6 +89,7 @@ mod test_harness_ffi;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
static PROOF_PARAMETERS_LOADED: Once = Once::new();
|
||||||
static mut SAPLING_SPEND_VK: Option<PreparedVerifyingKey<Bls12>> = None;
|
static mut SAPLING_SPEND_VK: Option<PreparedVerifyingKey<Bls12>> = None;
|
||||||
static mut SAPLING_OUTPUT_VK: Option<PreparedVerifyingKey<Bls12>> = None;
|
static mut SAPLING_OUTPUT_VK: Option<PreparedVerifyingKey<Bls12>> = None;
|
||||||
static mut SPROUT_GROTH16_VK: Option<PreparedVerifyingKey<Bls12>> = None;
|
static mut SPROUT_GROTH16_VK: Option<PreparedVerifyingKey<Bls12>> = None;
|
||||||
|
@ -127,64 +133,66 @@ pub extern "C" fn librustzcash_init_zksnark_params(
|
||||||
#[cfg(target_os = "windows")] sprout_path: *const u16,
|
#[cfg(target_os = "windows")] sprout_path: *const u16,
|
||||||
sprout_path_len: usize,
|
sprout_path_len: usize,
|
||||||
) {
|
) {
|
||||||
#[cfg(not(target_os = "windows"))]
|
PROOF_PARAMETERS_LOADED.call_once(|| {
|
||||||
let (spend_path, output_path, sprout_path) = {
|
#[cfg(not(target_os = "windows"))]
|
||||||
(
|
let (spend_path, output_path, sprout_path) = {
|
||||||
OsStr::from_bytes(unsafe { slice::from_raw_parts(spend_path, spend_path_len) }),
|
(
|
||||||
OsStr::from_bytes(unsafe { slice::from_raw_parts(output_path, output_path_len) }),
|
OsStr::from_bytes(unsafe { slice::from_raw_parts(spend_path, spend_path_len) }),
|
||||||
if sprout_path.is_null() {
|
OsStr::from_bytes(unsafe { slice::from_raw_parts(output_path, output_path_len) }),
|
||||||
None
|
if sprout_path.is_null() {
|
||||||
} else {
|
None
|
||||||
Some(OsStr::from_bytes(unsafe {
|
} else {
|
||||||
slice::from_raw_parts(sprout_path, sprout_path_len)
|
Some(OsStr::from_bytes(unsafe {
|
||||||
}))
|
slice::from_raw_parts(sprout_path, sprout_path_len)
|
||||||
},
|
}))
|
||||||
)
|
},
|
||||||
};
|
)
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let (spend_path, output_path, sprout_path) = {
|
let (spend_path, output_path, sprout_path) = {
|
||||||
(
|
(
|
||||||
OsString::from_wide(unsafe { slice::from_raw_parts(spend_path, spend_path_len) }),
|
OsString::from_wide(unsafe { slice::from_raw_parts(spend_path, spend_path_len) }),
|
||||||
OsString::from_wide(unsafe { slice::from_raw_parts(output_path, output_path_len) }),
|
OsString::from_wide(unsafe { slice::from_raw_parts(output_path, output_path_len) }),
|
||||||
if sprout_path.is_null() {
|
if sprout_path.is_null() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(OsString::from_wide(unsafe {
|
Some(OsString::from_wide(unsafe {
|
||||||
slice::from_raw_parts(sprout_path, sprout_path_len)
|
slice::from_raw_parts(sprout_path, sprout_path_len)
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let (spend_path, output_path, sprout_path) = (
|
let (spend_path, output_path, sprout_path) = (
|
||||||
Path::new(&spend_path),
|
Path::new(&spend_path),
|
||||||
Path::new(&output_path),
|
Path::new(&output_path),
|
||||||
sprout_path.as_ref().map(|p| Path::new(p)),
|
sprout_path.as_ref().map(Path::new),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Load params
|
// Load params
|
||||||
let params = load_parameters(spend_path, output_path, sprout_path);
|
let params = load_parameters(spend_path, output_path, sprout_path);
|
||||||
|
|
||||||
// Generate Orchard parameters.
|
// Generate Orchard parameters.
|
||||||
info!(target: "main", "Loading Orchard parameters");
|
info!(target: "main", "Loading Orchard parameters");
|
||||||
let orchard_pk = orchard::circuit::ProvingKey::build();
|
let orchard_pk = orchard::circuit::ProvingKey::build();
|
||||||
let orchard_vk = orchard::circuit::VerifyingKey::build();
|
let orchard_vk = orchard::circuit::VerifyingKey::build();
|
||||||
|
|
||||||
// Caller is responsible for calling this function once, so
|
// Caller is responsible for calling this function once, so
|
||||||
// these global mutations are safe.
|
// these global mutations are safe.
|
||||||
unsafe {
|
unsafe {
|
||||||
SAPLING_SPEND_PARAMS = Some(params.spend_params);
|
SAPLING_SPEND_PARAMS = Some(params.spend_params);
|
||||||
SAPLING_OUTPUT_PARAMS = Some(params.output_params);
|
SAPLING_OUTPUT_PARAMS = Some(params.output_params);
|
||||||
SPROUT_GROTH16_PARAMS_PATH = sprout_path.map(|p| p.to_owned());
|
SPROUT_GROTH16_PARAMS_PATH = sprout_path.map(|p| p.to_owned());
|
||||||
|
|
||||||
SAPLING_SPEND_VK = Some(params.spend_vk);
|
SAPLING_SPEND_VK = Some(params.spend_vk);
|
||||||
SAPLING_OUTPUT_VK = Some(params.output_vk);
|
SAPLING_OUTPUT_VK = Some(params.output_vk);
|
||||||
SPROUT_GROTH16_VK = params.sprout_vk;
|
SPROUT_GROTH16_VK = params.sprout_vk;
|
||||||
|
|
||||||
ORCHARD_PK = Some(orchard_pk);
|
ORCHARD_PK = Some(orchard_pk);
|
||||||
ORCHARD_VK = Some(orchard_vk);
|
ORCHARD_VK = Some(orchard_vk);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the "uncommitted" note value for empty leaves of the Merkle tree.
|
/// Writes the "uncommitted" note value for empty leaves of the Merkle tree.
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue