Merge pull request #1397 from Electric-Coin-Company/1359-expose-proposals

Expose APIs for working with transaction proposals
This commit is contained in:
Kris Nuttycombe 2024-03-08 13:15:38 -07:00 committed by GitHub
commit 5b04cbc579
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 961 additions and 332 deletions

View File

@ -6,15 +6,32 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [2.0.7] - 2024-03-08
### Fixed
- `Synchronizer.sendToAddress` and `Synchronizer.shieldFunds` now throw an
exception if the created transaction successfully reaches `lightwalletd` but
fails to reach its backing full node's mempool.
### Changed
- `WalletBalance` now contains new fields `changePending` and `valuePending`. Fields `total` and `pending` are
still provided. See more in the class documentation
`sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletBalance.kt`
- `Synchronizer.transparentBalances: WalletBalance` to `Synchronizer.transparentBalance: Zatoshi`
- `WalletSnapshot.transparentBalance: WalletBalance` to `WalletSnapshot.transparentBalance: Zatoshi`
- `WalletSnapshot.transparentBalance: WalletBalance` to `WalletSnapshot.transparentBalance: Zatoshi`
- `Memo.MAX_MEMO_LENGTH_BYTES` is now available in public API
- `Synchronizer.sendToAddress` and `Synchronizer.shieldFunds` have been
deprecated, and will be removed in 2.1.0 (which will create multiple
transactions at once for some recipients).
### Added
- APIs that enable constructing a proposal for transferring or shielding funds,
and then creating transactions from a proposal. The intermediate proposal can
be used to determine the required fee, before committing to producing
transactions.
- `Synchronizer.proposeTransfer`
- `Synchronizer.proposeShielding`
- `Synchronizer.createProposedTransactions`
- `WalletBalanceFixture` class with mock values that are supposed to be used only for testing purposes
- `Memo.countLength(memoString: String)` to count memo length in bytes
- `PersistableWallet.toSafeString` is a safe alternative for the regular [toString] function that prints only

316
backend-lib/Cargo.lock generated
View File

@ -29,9 +29,9 @@ dependencies = [
[[package]]
name = "aes"
version = "0.8.3"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
@ -40,9 +40,9 @@ dependencies = [
[[package]]
name = "ahash"
version = "0.8.7"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
@ -67,9 +67,9 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "anyhow"
version = "1.0.79"
version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
[[package]]
name = "arrayref"
@ -163,12 +163,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.2"
@ -243,9 +237,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.14.0"
version = "3.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
[[package]]
name = "byteorder"
@ -270,12 +264,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.83"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
[[package]]
name = "cesu8"
@ -351,9 +342,9 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
version = "0.5.11"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95"
dependencies = [
"crossbeam-utils",
]
@ -448,7 +439,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.52",
]
[[package]]
@ -462,14 +453,15 @@ dependencies = [
[[package]]
name = "either"
version = "1.9.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
name = "equihash"
version = "0.2.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab579d7cf78477773b03e80bc2f89702ef02d7112c711d54ca93dcdce68533d5"
dependencies = [
"blake2b_simd",
"byteorder",
@ -494,7 +486,8 @@ dependencies = [
[[package]]
name = "f4jumble"
version = "0.1.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a83e8d7fd0c526af4aad893b7c9fe41e2699ed8a776a6c74aecdeafe05afc75"
dependencies = [
"blake2b_simd",
]
@ -707,9 +700,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.3.4"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hex"
@ -746,9 +739,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.2.1"
version = "2.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b"
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
dependencies = [
"equivalent",
"hashbrown",
@ -802,9 +795,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "js-sys"
version = "0.3.67"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
@ -843,9 +836,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.152"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libm"
@ -878,9 +871,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]]
name = "log"
version = "0.4.20"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "log-panics"
@ -924,9 +917,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
dependencies = [
"adler",
]
@ -984,20 +977,25 @@ dependencies = [
]
[[package]]
name = "num-integer"
version = "0.1.45"
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.17"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
dependencies = [
"autocfg",
]
@ -1029,15 +1027,15 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "opaque-debug"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "orchard"
version = "0.7.0"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92c801aeaccd19bb6916d71f25694b62d223061872900e8022221c1ad8dcad2d"
checksum = "1fb255c3ffdccd3c84fe9ebed72aef64fdc72e6a3e4180dd411002d47abaad42"
dependencies = [
"aes",
"bitvec",
@ -1152,9 +1150,9 @@ checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pkg-config"
version = "0.3.29"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "poly1305"
@ -1186,7 +1184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5"
dependencies = [
"proc-macro2",
"syn 2.0.48",
"syn 2.0.52",
]
[[package]]
@ -1225,7 +1223,7 @@ dependencies = [
"prost",
"prost-types",
"regex",
"syn 2.0.48",
"syn 2.0.52",
"tempfile",
"which",
]
@ -1240,7 +1238,7 @@ dependencies = [
"itertools",
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.52",
]
[[package]]
@ -1299,9 +1297,9 @@ dependencies = [
[[package]]
name = "rayon"
version = "1.8.1"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd"
dependencies = [
"either",
"rayon-core",
@ -1348,15 +1346,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "regex"
version = "1.10.3"
@ -1371,9 +1360,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [
"aho-corasick",
"memchr",
@ -1416,7 +1405,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2"
dependencies = [
"bitflags 2.4.2",
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
@ -1433,11 +1422,11 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustix"
version = "0.38.30"
version = "0.38.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [
"bitflags 2.4.2",
"bitflags",
"errno",
"libc",
"linux-raw-sys",
@ -1455,9 +1444,9 @@ dependencies = [
[[package]]
name = "sapling-crypto"
version = "0.1.0"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f5de898a7cdb7f6d9c8fb888341b6ae6e2aeae88227b7f435f1dda49ecf9e62"
checksum = "d183012062dfdde85f7e3e758328fcf6e9846d8dd3fce35b04d0efcb6677b0e0"
dependencies = [
"aes",
"bellman",
@ -1537,22 +1526,22 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.196"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.196"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.52",
]
[[package]]
@ -1581,7 +1570,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf20c7a2747d9083092e3a3eeb9a7ed75577ae364896bebbc5e0bdcd4e97735"
dependencies = [
"bitflags 2.4.2",
"bitflags",
"either",
"incrementalmerkletree",
"tracing",
@ -1624,9 +1613,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.48"
version = "2.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
dependencies = [
"proc-macro2",
"quote",
@ -1653,42 +1642,41 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.9.0"
version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall",
"rustix",
"windows-sys 0.52.0",
]
[[package]]
name = "thiserror"
version = "1.0.56"
version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.56"
version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.52",
]
[[package]]
name = "thread_local"
version = "1.1.7"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
@ -1696,12 +1684,13 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.31"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde",
"time-core",
@ -1716,10 +1705,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
dependencies = [
"num-conv",
"time-core",
]
@ -1748,7 +1738,7 @@ dependencies = [
"proc-macro2",
"prost-build",
"quote",
"syn 2.0.48",
"syn 2.0.52",
]
[[package]]
@ -1770,7 +1760,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.52",
]
[[package]]
@ -1834,9 +1824,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
dependencies = [
"tinyvec",
]
@ -1889,9 +1879,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
@ -1905,9 +1895,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.90"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@ -1915,24 +1905,24 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.90"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.52",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.90"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -1940,28 +1930,28 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.90"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.52",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.90"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "web-sys"
version = "0.3.67"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
dependencies = [
"js-sys",
"wasm-bindgen",
@ -2025,7 +2015,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
"windows-targets 0.52.4",
]
[[package]]
@ -2045,17 +2035,17 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
"windows_aarch64_gnullvm 0.52.4",
"windows_aarch64_msvc 0.52.4",
"windows_i686_gnu 0.52.4",
"windows_i686_msvc 0.52.4",
"windows_x86_64_gnu 0.52.4",
"windows_x86_64_gnullvm 0.52.4",
"windows_x86_64_msvc 0.52.4",
]
[[package]]
@ -2066,9 +2056,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
@ -2078,9 +2068,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
@ -2090,9 +2080,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
@ -2102,9 +2092,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_x86_64_gnu"
@ -2114,9 +2104,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
@ -2126,9 +2116,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
@ -2138,9 +2128,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]]
name = "wyz"
@ -2169,7 +2159,6 @@ dependencies = [
"jni",
"libc",
"log-panics",
"orchard",
"paranoid-android",
"prost",
"rayon",
@ -2189,19 +2178,22 @@ dependencies = [
[[package]]
name = "zcash_address"
version = "0.3.1"
source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "827c17a1f7e3a69f0d44e991ff610c7a842228afdc9dc2325ffdd1a67fee01e9"
dependencies = [
"bech32",
"bs58",
"f4jumble",
"zcash_encoding",
"zcash_protocol",
]
[[package]]
name = "zcash_client_backend"
version = "0.10.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "185913267d824529b9547c933674963fca2b5bd84ad377a59d0f8ab6159ce798"
dependencies = [
"base64",
"bech32",
@ -2239,8 +2231,9 @@ dependencies = [
[[package]]
name = "zcash_client_sqlite"
version = "0.8.1"
source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e883405989b8d7275a0e1180000b7568bb3fa33e36b4806c174eb802678e2dbf"
dependencies = [
"bs58",
"byteorder",
@ -2250,7 +2243,6 @@ dependencies = [
"incrementalmerkletree",
"jubjub",
"maybe-rayon",
"orchard",
"prost",
"rusqlite",
"sapling-crypto",
@ -2271,7 +2263,8 @@ dependencies = [
[[package]]
name = "zcash_encoding"
version = "0.2.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f03391b81727875efa6ac0661a20883022b6fba92365dc121c48fa9b00c5aac0"
dependencies = [
"byteorder",
"nonempty",
@ -2279,8 +2272,9 @@ dependencies = [
[[package]]
name = "zcash_keys"
version = "0.0.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f22d3407fdd6992b49f037f23862ab376be6013be6f2d0bc85948a635edc1f5"
dependencies = [
"bech32",
"bls12_381",
@ -2317,8 +2311,9 @@ dependencies = [
[[package]]
name = "zcash_primitives"
version = "0.13.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9070e084570bb78aed4f8d71fd6254492e62c87a5d01e084183980e98117092d"
dependencies = [
"aes",
"bip0039",
@ -2354,8 +2349,9 @@ dependencies = [
[[package]]
name = "zcash_proofs"
version = "0.13.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8a02eb1f151d9b9a6e16408d2c55ff440bd2fb232b7377277146d0fa2df9bc8"
dependencies = [
"bellman",
"blake2b_simd",
@ -2374,6 +2370,16 @@ dependencies = [
"zcash_primitives",
]
[[package]]
name = "zcash_protocol"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c651f9f95d319cd1e5211178108dcd3aa73063806d09af54b799e1a329a575bf"
dependencies = [
"document-features",
"memuse",
]
[[package]]
name = "zcash_spec"
version = "0.1.0"
@ -2400,7 +2406,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.52",
]
[[package]]
@ -2420,7 +2426,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.52",
]
[[package]]

View File

@ -23,11 +23,10 @@ schemer = "0.2"
secp256k1 = "0.26"
secrecy = "0.8"
zcash_address = "0.3"
zcash_client_backend = { version = "0.10", features = ["transparent-inputs", "unstable"] }
zcash_client_sqlite = { version = "^0.8.1", features = ["transparent-inputs", "unstable"] }
zcash_primitives = "0.13"
zcash_proofs = "0.13"
orchard = { version = "0.7", default-features = false }
zcash_client_backend = { version = "0.11", features = ["transparent-inputs", "unstable"] }
zcash_client_sqlite = { version = "0.9", features = ["transparent-inputs", "unstable"] }
zcash_primitives = "0.14"
zcash_proofs = "0.14"
# Initialization
rayon = "1.7"
@ -62,11 +61,3 @@ libc = "0.2"
name = "zcashwalletsdk"
path = "src/main/rust/lib.rs"
crate-type = ["staticlib", "cdylib"]
[patch.crates-io]
# Tag `ecc_sdk-20240130a`
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" }
zcash_client_backend = { git = "https://github.com/zcash/librustzcash.git", rev = "9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" }
zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash.git", rev = "9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" }
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" }

View File

@ -30,13 +30,15 @@ interface Backend {
suspend fun proposeShielding(
account: Int,
memo: ByteArray? = byteArrayOf()
): ProposalUnsafe
shieldingThreshold: Long,
memo: ByteArray? = byteArrayOf(),
transparentReceiver: String? = null
): ProposalUnsafe?
suspend fun createProposedTransaction(
suspend fun createProposedTransactions(
proposal: ProposalUnsafe,
unifiedSpendingKey: ByteArray
): ByteArray
): List<ByteArray>
suspend fun decryptAndStoreTransaction(tx: ByteArray)

View File

@ -307,34 +307,40 @@ class RustBackend private constructor(
override suspend fun proposeShielding(
account: Int,
memo: ByteArray?
): ProposalUnsafe {
shieldingThreshold: Long,
memo: ByteArray?,
transparentReceiver: String?
): ProposalUnsafe? {
return withContext(SdkDispatchers.DATABASE_IO) {
ProposalUnsafe.parse(
proposeShielding(
dataDbFile.absolutePath,
account,
memo ?: ByteArray(0),
networkId = networkId,
useZip317Fees = IS_USE_ZIP_317_FEES
proposeShielding(
dataDbFile.absolutePath,
account,
shieldingThreshold,
memo ?: ByteArray(0),
transparentReceiver,
networkId = networkId,
useZip317Fees = IS_USE_ZIP_317_FEES
)?.let {
ProposalUnsafe.parse(
it
)
)
}
}
}
override suspend fun createProposedTransaction(
override suspend fun createProposedTransactions(
proposal: ProposalUnsafe,
unifiedSpendingKey: ByteArray
): ByteArray =
): List<ByteArray> =
withContext(SdkDispatchers.DATABASE_IO) {
createProposedTransaction(
createProposedTransactions(
dataDbFile.absolutePath,
proposal.toByteArray(),
unifiedSpendingKey,
spendParamsPath = saplingSpendFile.absolutePath,
outputParamsPath = saplingOutputFile.absolutePath,
networkId = networkId
)
).asList()
}
override suspend fun putUtxo(
@ -584,21 +590,23 @@ class RustBackend private constructor(
private external fun proposeShielding(
dbDataPath: String,
account: Int,
shieldingThreshold: Long,
memo: ByteArray,
transparentReceiver: String?,
networkId: Int,
useZip317Fees: Boolean
): ByteArray
): ByteArray?
@JvmStatic
@Suppress("LongParameterList")
private external fun createProposedTransaction(
private external fun createProposedTransactions(
dbDataPath: String,
proposal: ByteArray,
usk: ByteArray,
spendParamsPath: String,
outputParamsPath: String,
networkId: Int
): ByteArray
): Array<ByteArray>
@JvmStatic
private external fun branchIdForHeight(

View File

@ -38,9 +38,21 @@ class ProposalUnsafe(
}
/**
* Returns the fee required by this proposal.
* Returns the number of transactions that this proposal will create.
*
* This is equal to the number of `TransactionSubmitResult`s that will be returned
* from `Synchronizer.createProposedTransactions`.
*
* Proposals always create at least one transaction.
*/
fun feeRequired(): Long {
return inner.balance.feeRequired
fun transactionCount(): Int {
return inner.stepsCount
}
/**
* Returns the total fee required by this proposal for its transactions.
*/
fun totalFeeRequired(): Long {
return inner.stepsList.fold(0) { acc, step -> acc + step.balance.feeRequired }
}
}

View File

@ -6,12 +6,29 @@ syntax = "proto3";
package cash.z.wallet.sdk.ffi;
option java_package = "cash.z.wallet.sdk.internal.ffi";
// A data structure that describes a series of transactions to be created.
message Proposal {
// The version of this serialization format.
uint32 protoVersion = 1;
// The fee rule used in constructing this proposal
FeeRule feeRule = 2;
// The target height for which the proposal was constructed
//
// The chain must contain at least this many blocks in order for the proposal to
// be executed.
uint32 minTargetHeight = 3;
// The series of transactions to be created.
repeated ProposalStep steps = 4;
}
// A data structure that describes the inputs to be consumed and outputs to
// be produced in a proposed transaction.
message Proposal {
uint32 protoVersion = 1;
message ProposalStep {
// ZIP 321 serialized transaction request
string transactionRequest = 2;
string transactionRequest = 1;
// The vector of selected payment index / output pool mappings. Payment index
// 0 corresponds to the payment with no explicit index.
repeated PaymentOutputPool paymentOutputPools = 2;
// The anchor height to be used in creating the transaction, if any.
// Setting the anchor height to zero will disallow the use of any shielded
// inputs.
@ -21,16 +38,9 @@ message Proposal {
// The total value, fee value, and change outputs of the proposed
// transaction
TransactionBalance balance = 5;
// The fee rule used in constructing this proposal
FeeRule feeRule = 6;
// The target height for which the proposal was constructed
//
// The chain must contain at least this many blocks in order for the proposal to
// be executed.
uint32 minTargetHeight = 7;
// A flag indicating whether the proposal is for a shielding transaction,
// A flag indicating whether the step is for a shielding transaction,
// used for determining which OVK to select for wallet-internal outputs.
bool isShielding = 8;
bool isShielding = 6;
}
enum ValuePool {
@ -47,14 +57,45 @@ enum ValuePool {
Orchard = 3;
}
// The unique identifier and value for each proposed input.
message ProposedInput {
// A mapping from ZIP 321 payment index to the output pool that has been chosen
// for that payment, based upon the payment address and the selected inputs to
// the transaction.
message PaymentOutputPool {
uint32 paymentIndex = 1;
ValuePool valuePool = 2;
}
// The unique identifier and value for each proposed input that does not
// require a back-reference to a prior step of the proposal.
message ReceivedOutput {
bytes txid = 1;
ValuePool valuePool = 2;
uint32 index = 3;
uint64 value = 4;
}
// A reference a payment in a prior step of the proposal. This payment must
// belong to the wallet.
message PriorStepOutput {
uint32 stepIndex = 1;
uint32 paymentIndex = 2;
}
// A reference a change output from a prior step of the proposal.
message PriorStepChange {
uint32 stepIndex = 1;
uint32 changeIndex = 2;
}
// The unique identifier and value for an input to be used in the transaction.
message ProposedInput {
oneof value {
ReceivedOutput receivedOutput = 1;
PriorStepOutput priorStepOutput = 2;
PriorStepChange priorStepChange = 3;
}
}
// The fee rule used in constructing a Proposal
enum FeeRule {
// Protobuf requires that enums have a zero discriminant as the default
@ -72,15 +113,21 @@ enum FeeRule {
// The proposed change outputs and fee value.
message TransactionBalance {
// A list of change output values.
repeated ChangeValue proposedChange = 1;
// The fee to be paid by the proposed transaction, in zatoshis.
uint64 feeRequired = 2;
}
// A proposed change output. If the transparent value pool is selected,
// the `memo` field must be null.
message ChangeValue {
// The value of a change output to be created, in zatoshis.
uint64 value = 1;
// The value pool in which the change output should be created.
ValuePool valuePool = 2;
// The optional memo that should be associated with the newly created change output.
// Memos must not be present for transparent change outputs.
MemoBytes memo = 3;
}

View File

@ -24,7 +24,7 @@ use zcash_client_backend::{
chain::{scan_cached_blocks, CommitmentTreeRoot, ScanSummary},
scanning::{ScanPriority, ScanRange},
wallet::{
create_proposed_transaction, decrypt_and_store_transaction,
create_proposed_transactions, decrypt_and_store_transaction,
input_selection::GreedyInputSelector, propose_shielding, propose_transfer,
},
AccountBalance, AccountBirthday, InputSource, WalletCommitmentTrees, WalletRead,
@ -524,10 +524,10 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getTransp
if let Some(taddr) = ua.0.transparent() {
let taddr = match taddr {
TransparentAddress::PublicKey(data) => {
TransparentAddress::PublicKeyHash(data) => {
ZcashAddress::from_transparent_p2pkh(network, *data)
}
TransparentAddress::Script(data) => {
TransparentAddress::ScriptHash(data) => {
ZcashAddress::from_transparent_p2sh(network, *data)
}
};
@ -1145,7 +1145,7 @@ fn encode_account_balance<'a>(
/// pending.
fn encode_wallet_summary<'a>(
env: &mut JNIEnv<'a>,
summary: WalletSummary,
summary: WalletSummary<AccountId>,
) -> jni::errors::Result<JObject<'a>> {
let account_balances = utils::rust_vec_to_java(
env,
@ -1397,7 +1397,7 @@ fn zip317_helper<DbT>(
StandardFeeRule::PreZip313
};
GreedyInputSelector::new(
SingleOutputChangeStrategy::new(fee_rule, change_memo),
SingleOutputChangeStrategy::new(fee_rule, change_memo, ShieldedProtocol::Sapling),
DustOutputPolicy::default(),
)
}
@ -1463,13 +1463,13 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeTr
)
.map_err(|e| format_err!("Error creating transaction proposal: {}", e))?;
utils::rust_bytes_to_java(
Ok(utils::rust_bytes_to_java(
&env,
Proposal::from_standard_proposal(&network, &proposal)
.encode_to_vec()
.as_ref(),
)
.map(|arr| arr.into_raw())
)?
.into_raw())
});
unwrap_exc_or(&mut env, res, ptr::null_mut())
}
@ -1480,7 +1480,9 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeSh
_: JClass<'local>,
db_data: JString<'local>,
account: jint,
shielding_threshold: jlong,
memo: JByteArray<'local>,
transparent_receiver: JString<'local>,
network_id: jint,
use_zip317_fees: jboolean,
) -> jbyteArray {
@ -1489,12 +1491,40 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeSh
let network = parse_network(network_id as u32)?;
let mut db_data = wallet_db(env, network, db_data)?;
let account = account_id_from_jint(account)?;
let shielding_threshold = NonNegativeAmount::from_nonnegative_i64(shielding_threshold)
.map_err(|()| format_err!("Invalid shielding threshold, out of range"))?;
let memo_bytes = env.convert_byte_array(memo).unwrap();
let transparent_receiver =
match utils::java_nullable_string_to_rust(env, &transparent_receiver) {
None => Ok(None),
Some(addr) => match Address::decode(&network, &addr) {
None => Err(format_err!("Transparent receiver is for the wrong network")),
Some(addr) => match addr {
Address::Sapling(_) | Address::Unified(_) => Err(format_err!(
"Transparent receiver is not a transparent address"
)),
Address::Transparent(addr) => {
if db_data
.get_transparent_receivers(account)?
.contains_key(&addr)
{
Ok(Some(addr))
} else {
Err(format_err!(
"Transparent receiver does not belong to account {}",
u32::from(account),
))
}
}
},
},
}?;
let min_confirmations = 0;
let min_confirmations_for_heights = NonZeroU32::new(1).unwrap();
let from_addrs: Vec<TransparentAddress> = db_data
let account_receivers = db_data
.get_target_and_anchor_heights(min_confirmations_for_heights)
.map_err(|e| format_err!("Error while fetching anchor height: {}", e))
.and_then(|opt_anchor| {
@ -1512,16 +1542,28 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeSh
e
)
})
})?
.into_keys()
.collect();
})?;
let from_addrs = if let Some((addr, _)) = transparent_receiver.map_or_else(||
if account_receivers.len() > 1 {
Err(format_err!(
"Account has more than one transparent receiver with funds to shield; this is not yet supported by the SDK. Provide a specific transparent receiver to shield funds from."
))
} else {
Ok(account_receivers.iter().next().map(|(a, v)| (*a, *v)))
},
|addr| Ok(account_receivers.get(&addr).map(|value| (addr, *value)))
)?.filter(|(_, value)| *value >= shielding_threshold.into()) {
[addr]
} else {
// There are no transparent funds to shield; don't create a proposal.
return Ok(ptr::null_mut());
};
let memo = Memo::from_bytes(&memo_bytes).unwrap();
let input_selector = zip317_helper(Some(MemoBytes::from(&memo)), use_zip317_fees);
let shielding_threshold = NonNegativeAmount::from_u64(100000).unwrap();
let proposal = propose_shielding::<_, _, _, Infallible>(
&mut db_data,
&network,
@ -1532,19 +1574,19 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeSh
)
.map_err(|e| format_err!("Error while shielding transaction: {}", e))?;
utils::rust_bytes_to_java(
Ok(utils::rust_bytes_to_java(
&env,
Proposal::from_standard_proposal(&network, &proposal)
.encode_to_vec()
.as_ref(),
)
.map(|arr| arr.into_raw())
)?
.into_raw())
});
unwrap_exc_or(&mut env, res, ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createProposedTransaction<
pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createProposedTransactions<
'local,
>(
mut env: JNIEnv<'local>,
@ -1555,7 +1597,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createPro
spend_params: JString<'local>,
output_params: JString<'local>,
network_id: jint,
) -> jbyteArray {
) -> jobjectArray {
let res = catch_unwind(&mut env, |env| {
let _span = tracing::info_span!("RustBackend.createProposedTransaction").entered();
let network = parse_network(network_id as u32)?;
@ -1570,7 +1612,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createPro
.map_err(|e| format_err!("Invalid proposal: {}", e))?
.try_into_standard_proposal(&network, &db_data)?;
let txid = create_proposed_transaction::<_, _, Infallible, _, _>(
let txids = create_proposed_transactions::<_, _, Infallible, _, _>(
&mut db_data,
&network,
&prover,
@ -1579,9 +1621,16 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createPro
OvkPolicy::Sender,
&proposal,
)
.map_err(|e| format_err!("Error while creating transaction: {}", e))?;
.map_err(|e| format_err!("Error while creating transactions: {}", e))?;
utils::rust_bytes_to_java(&env, txid.as_ref()).map(|arr| arr.into_raw())
Ok(utils::rust_vec_to_java(
env,
txids.into(),
"[B",
|env, txid| utils::rust_bytes_to_java(env, txid.as_ref()),
|env| env.new_byte_array(32),
)?
.into_raw())
});
unwrap_exc_or(&mut env, res, ptr::null_mut())
}
@ -1657,10 +1706,10 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_listTrans
.iter()
.map(|(taddr, _)| {
let taddr = match taddr {
TransparentAddress::PublicKey(data) => {
TransparentAddress::PublicKeyHash(data) => {
ZcashAddress::from_transparent_p2pkh(zcash_network, *data)
}
TransparentAddress::Script(data) => {
TransparentAddress::ScriptHash(data) => {
ZcashAddress::from_transparent_p2sh(zcash_network, *data)
}
};

View File

@ -38,10 +38,11 @@ pub(crate) fn java_string_to_rust(env: &mut JNIEnv, jstring: &JString) -> String
.into()
}
pub(crate) fn rust_bytes_to_java<'a>(
env: &JNIEnv<'a>,
data: &[u8],
) -> Result<JByteArray<'a>, failure::Error> {
pub(crate) fn java_nullable_string_to_rust(env: &mut JNIEnv, jstring: &JString) -> Option<String> {
(!jstring.is_null()).then(|| java_string_to_rust(env, jstring))
}
pub(crate) fn rust_bytes_to_java<'a>(env: &JNIEnv<'a>, data: &[u8]) -> JNIResult<JByteArray<'a>> {
// SAFETY: jbyte (i8) has the same size and alignment as u8, and a well-defined
// twos-complement representation with no "trap representations".
let buf = unsafe { slice::from_raw_parts(data.as_ptr().cast(), data.len()) };

View File

@ -105,7 +105,15 @@ class TestWallet(
memo: String = "",
amount: Zatoshi = Zatoshi(500L)
): TestWallet {
synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo)
synchronizer.createProposedTransactions(
synchronizer.proposeTransfer(
shieldedSpendingKey.account,
address,
amount,
memo
),
shieldedSpendingKey
)
return this
}
@ -121,7 +129,12 @@ class TestWallet(
synchronizer.getTransparentBalance(transparentAddress).let { walletBalance ->
if (walletBalance.value > 0L) {
synchronizer.shieldFunds(shieldedSpendingKey)
synchronizer.proposeShielding(shieldedSpendingKey.account, Zatoshi(100000))?.let {
synchronizer.createProposedTransactions(
it,
shieldedSpendingKey
)
}
}
}

View File

@ -189,7 +189,15 @@ class SampleCodeTest {
ZcashNetwork.Mainnet,
Account.DEFAULT
)
synchronizer.sendToAddress(spendingKey, amount, address, memo)
synchronizer.createProposedTransactions(
synchronizer.proposeTransfer(
spendingKey.account,
address,
amount,
memo
),
spendingKey
)
}
// /////////////////////////////////////////////////////

View File

@ -47,6 +47,7 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
reportTraceEvent(SyncBlockchainBenchmarkTrace.Event.BALANCE_SCREEN_END)
}
@Suppress("MagicNumber")
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
@ -60,13 +61,20 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
binding.shield.apply {
setOnClickListener {
lifecycleScope.launch {
sharedViewModel.synchronizerFlow.value?.shieldFunds(
val usk =
DerivationTool.getInstance().deriveUnifiedSpendingKey(
seed,
network,
Account.DEFAULT
)
)
sharedViewModel.synchronizerFlow.value?.let { synchronizer ->
synchronizer.proposeShielding(usk.account, Zatoshi(100000))?.let { it1 ->
synchronizer.createProposedTransactions(
it1,
usk
)
}
}
}
}
}

View File

@ -145,12 +145,17 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
val amount = amountInput.text.toString().toDouble().convertZecToZatoshi()
val toAddress = addressInput.text.toString().trim()
lifecycleScope.launch {
sharedViewModel.synchronizerFlow.value?.sendToAddress(
spendingKey,
amount,
toAddress,
"Funds from Demo App"
)
sharedViewModel.synchronizerFlow.value?.let { synchronizer ->
synchronizer.createProposedTransactions(
synchronizer.proposeTransfer(
spendingKey.account,
toAddress,
amount,
"Funds from Demo App"
),
spendingKey
)
}
}
mainActivity()?.hideKeyboard()

View File

@ -22,6 +22,7 @@ import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.PersistableWallet
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
import cash.z.ecc.android.sdk.model.WalletAddresses
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
@ -47,6 +48,7 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@ -202,7 +204,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
viewModelScope.launch {
val spendingKey = spendingKey.filterNotNull().first()
runCatching { synchronizer.send(spendingKey, zecSend) }
.onSuccess { mutableSendState.value = SendState.Sent(it) }
.onSuccess { mutableSendState.value = SendState.Sent(it.toList()) }
.onFailure { mutableSendState.value = SendState.Error(it) }
}
} else {
@ -215,6 +217,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
*
* Observe the result via [sendState].
*/
@Suppress("MagicNumber")
fun shieldFunds() {
if (sendState.value is SendState.Sending) {
return
@ -226,8 +229,15 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
if (null != synchronizer) {
viewModelScope.launch {
val spendingKey = spendingKey.filterNotNull().first()
kotlin.runCatching { synchronizer.shieldFunds(spendingKey) }
.onSuccess { mutableSendState.value = SendState.Sent(it) }
kotlin.runCatching {
synchronizer.proposeShielding(spendingKey.account, Zatoshi(100000))?.let {
synchronizer.createProposedTransactions(
it,
spendingKey
)
}
}
.onSuccess { it?.let { mutableSendState.value = SendState.Sent(it.toList()) } }
.onFailure { mutableSendState.value = SendState.Error(it) }
}
} else {
@ -302,7 +312,7 @@ sealed class SendState {
override fun toString(): String = "Sending"
}
class Sent(val localTxId: Long) : SendState() {
class Sent(val txIds: List<TransactionSubmitResult>) : SendState() {
override fun toString(): String = "Sent"
}

View File

@ -26,7 +26,7 @@ ZCASH_ASCII_GPG_KEY=
# Configures whether release is an unstable snapshot, therefore published to the snapshot repository.
IS_SNAPSHOT=true
LIBRARY_VERSION=2.0.6
LIBRARY_VERSION=2.0.7
# Kotlin compiler warnings can be considered errors, failing the build.
ZCASH_IS_TREAT_WARNINGS_AS_ERRORS=true

View File

@ -9,9 +9,12 @@ data class ZecSend(val destination: WalletAddress, val amount: Zatoshi, val memo
suspend fun Synchronizer.send(
spendingKey: UnifiedSpendingKey,
send: ZecSend
) = sendToAddress(
spendingKey,
send.amount,
send.destination.address,
send.memo.value
) = createProposedTransactions(
proposeTransfer(
spendingKey.account,
send.destination.address,
send.amount,
send.memo.value
),
spendingKey
)

View File

@ -112,11 +112,14 @@ class TestnetIntegrationTest : ScopedTest() {
Account.DEFAULT
)
log("sending to address")
synchronizer.sendToAddress(
spendingKey,
ZcashSdk.MINERS_FEE,
toAddress,
"first mainnet tx from the SDK"
synchronizer.createProposedTransactions(
synchronizer.proposeTransfer(
spendingKey.account,
toAddress,
ZcashSdk.MINERS_FEE,
"first mainnet tx from the SDK"
),
spendingKey
)
return true
}

View File

@ -106,7 +106,15 @@ class TestWallet(
memo: String = "",
amount: Zatoshi = Zatoshi(500L)
): TestWallet {
synchronizer.sendToAddress(spendingKey, amount, address, memo)
synchronizer.createProposedTransactions(
synchronizer.proposeTransfer(
spendingKey.account,
address,
amount,
memo
),
spendingKey
)
return this
}
@ -124,7 +132,12 @@ class TestWallet(
Twig.debug { "FOUND utxo balance of total: $walletBalance" }
if (walletBalance.value > 0L) {
synchronizer.shieldFunds(spendingKey)
synchronizer.proposeShielding(spendingKey.account, Zatoshi(100000))?.let {
synchronizer.createProposedTransactions(
it,
spendingKey
)
}
}
}

View File

@ -88,15 +88,17 @@ internal class FakeRustBackend(
override suspend fun proposeShielding(
account: Int,
memo: ByteArray?
): ProposalUnsafe {
shieldingThreshold: Long,
memo: ByteArray?,
transparentReceiver: String?
): ProposalUnsafe? {
TODO("Not yet implemented")
}
override suspend fun createProposedTransaction(
override suspend fun createProposedTransactions(
proposal: ProposalUnsafe,
unifiedSpendingKey: ByteArray
): ByteArray {
): List<ByteArray> {
TODO("Not yet implemented")
}

View File

@ -42,8 +42,10 @@ import cash.z.ecc.android.sdk.internal.transaction.TransactionEncoderImpl
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
@ -66,6 +68,7 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
@ -405,6 +408,7 @@ class SdkSynchronizer private constructor(
lastScanTime = now
SYNCED
}
is Stopped -> STOPPED
is Disconnected -> DISCONNECTED
is Syncing, Initialized -> SYNCING
@ -555,6 +559,58 @@ class SdkSynchronizer private constructor(
account
)
@Throws(TransactionEncoderException::class)
override suspend fun proposeTransfer(
account: Account,
recipient: String,
amount: Zatoshi,
memo: String
): Proposal = txManager.proposeTransfer(account, recipient, amount, memo)
@Throws(TransactionEncoderException::class)
override suspend fun proposeShielding(
account: Account,
shieldingThreshold: Zatoshi,
memo: String,
transparentReceiver: String?
): Proposal? = txManager.proposeShielding(account, shieldingThreshold, memo, transparentReceiver)
@Throws(TransactionEncoderException::class)
override suspend fun createProposedTransactions(
proposal: Proposal,
usk: UnifiedSpendingKey
): Flow<TransactionSubmitResult> {
// Internally, this logic submits and checks every incoming transaction, and once [Failure] or
// [NotAttempted] submission result occurs, it returns [NotAttempted] for the rest of them
var anySubmissionFailed = false
return txManager.createProposedTransactions(proposal, usk)
.asFlow()
.map { transaction ->
if (anySubmissionFailed) {
TransactionSubmitResult.NotAttempted(transaction.txId)
} else {
val submission = txManager.submit(transaction)
when (submission) {
is TransactionSubmitResult.Success -> {
// Expected state
}
is TransactionSubmitResult.Failure,
is TransactionSubmitResult.NotAttempted -> {
anySubmissionFailed = true
}
}
submission
}
}
}
@Deprecated(
message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.",
replaceWith =
ReplaceWith(
"createProposedTransactions(proposeTransfer(usk.account, toAddress, amount, memo), usk)"
)
)
@Throws(TransactionEncoderException::class, TransactionSubmitException::class)
override suspend fun sendToAddress(
usk: UnifiedSpendingKey,
@ -571,13 +627,23 @@ class SdkSynchronizer private constructor(
usk.account
)
if (txManager.submit(encodedTx)) {
return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!!
} else {
throw TransactionSubmitException()
when (txManager.submit(encodedTx)) {
is TransactionSubmitResult.Success -> {
return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!!
}
else -> {
throw TransactionSubmitException()
}
}
}
@Deprecated(
message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.",
replaceWith =
ReplaceWith(
"proposeShielding(usk.account, shieldingThreshold, memo)?.let { createProposedTransactions(it, usk) }"
)
)
@Throws(TransactionEncoderException::class, TransactionSubmitException::class)
override suspend fun shieldFunds(
usk: UnifiedSpendingKey,
@ -596,10 +662,13 @@ class SdkSynchronizer private constructor(
usk.account
)
if (txManager.submit(encodedTx)) {
return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!!
} else {
throw TransactionSubmitException()
when (txManager.submit(encodedTx)) {
is TransactionSubmitResult.Success -> {
return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!!
}
else -> {
throw TransactionSubmitException()
}
}
}

View File

@ -15,8 +15,10 @@ import cash.z.ecc.android.sdk.internal.model.ext.toBlockHeight
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
@ -167,6 +169,64 @@ interface Synchronizer {
*/
suspend fun getTransparentAddress(account: Account): String
/**
* Creates a proposal for transferring funds to the given recipient.
*
* @param account the account from which to transfer funds.
* @param recipient the recipient's address.
* @param amount the amount of zatoshi to send.
* @param memo the optional memo to include as part of the proposal's transactions.
*
* @return the proposal or an exception
*/
suspend fun proposeTransfer(
account: Account,
recipient: String,
amount: Zatoshi,
memo: String = ""
): Proposal
/**
* Creates a proposal for shielding any transparent funds received by the given account.
*
* @param account the account for which to shield funds.
* @param shieldingThreshold the minimum transparent balance required before a
* proposal will be created.
* @param memo the optional memo to include as part of the proposal's transactions.
* @param transparentReceiver a specific transparent receiver within the account that
* should be the source of transparent funds. Default is
* null which will select whichever of the account's
* transparent receivers has funds to shield.
*
* @return the proposal, or null if the transparent balance that would be shielded is
* zero or below `shieldingThreshold`.
*
* @throws Exception if `transparentReceiver` is null and there are transparent funds
* in more than one of the account's transparent receivers.
*/
suspend fun proposeShielding(
account: Account,
shieldingThreshold: Zatoshi,
memo: String = ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX,
transparentReceiver: String? = null
): Proposal?
/**
* Creates the transactions in the given proposal.
*
* @param proposal the proposal for which to create transactions.
* @param usk the unified spending key associated with the account for which the
* proposal was created.
*
* @return a flow of result objects for the transactions that were created as part of
* the proposal, indicating whether they were submitted to the network or if
* an error occurred.
*/
suspend fun createProposedTransactions(
proposal: Proposal,
usk: UnifiedSpendingKey
): Flow<TransactionSubmitResult>
/**
* Sends zatoshi.
*
@ -180,6 +240,13 @@ interface Synchronizer {
* useful for updating the UI without needing to poll. Of course, polling is always an option
* for any wallet that wants to ignore this return value.
*/
@Deprecated(
message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.",
replaceWith =
ReplaceWith(
"createProposedTransactions(proposeTransfer(usk.account, toAddress, amount, memo), usk)"
)
)
suspend fun sendToAddress(
usk: UnifiedSpendingKey,
amount: Zatoshi,
@ -187,6 +254,13 @@ interface Synchronizer {
memo: String = ""
): Long
@Deprecated(
message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.",
replaceWith =
ReplaceWith(
"proposeShielding(usk.account, shieldingThreshold, memo)?.let { createProposedTransactions(it, usk) }"
)
)
suspend fun shieldFunds(
usk: UnifiedSpendingKey,
memo: String = ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX

View File

@ -26,21 +26,23 @@ internal interface TypesafeBackend {
@Suppress("LongParameterList")
suspend fun proposeTransfer(
usk: UnifiedSpendingKey,
account: Account,
to: String,
value: Long,
memo: ByteArray? = byteArrayOf()
): Proposal
suspend fun proposeShielding(
usk: UnifiedSpendingKey,
memo: ByteArray? = byteArrayOf()
): Proposal
account: Account,
shieldingThreshold: Long,
memo: ByteArray? = byteArrayOf(),
transparentReceiver: String? = null
): Proposal?
suspend fun createProposedTransaction(
suspend fun createProposedTransactions(
proposal: Proposal,
usk: UnifiedSpendingKey
): FirstClassByteArray
): List<FirstClassByteArray>
suspend fun getCurrentAddress(account: Account): String

View File

@ -37,14 +37,14 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke
@Suppress("LongParameterList")
override suspend fun proposeTransfer(
usk: UnifiedSpendingKey,
account: Account,
to: String,
value: Long,
memo: ByteArray?
): Proposal =
Proposal.fromUnsafe(
backend.proposeTransfer(
usk.account.value,
account.value,
to,
value,
memo
@ -52,26 +52,30 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke
)
override suspend fun proposeShielding(
usk: UnifiedSpendingKey,
memo: ByteArray?
): Proposal =
Proposal.fromUnsafe(
backend.proposeShielding(
usk.account.value,
memo
account: Account,
shieldingThreshold: Long,
memo: ByteArray?,
transparentReceiver: String?
): Proposal? =
backend.proposeShielding(
account.value,
shieldingThreshold,
memo,
transparentReceiver
)?.let {
Proposal.fromUnsafe(
it
)
)
}
override suspend fun createProposedTransaction(
override suspend fun createProposedTransactions(
proposal: Proposal,
usk: UnifiedSpendingKey
): FirstClassByteArray =
FirstClassByteArray(
backend.createProposedTransaction(
proposal.toUnsafe(),
usk.copyBytes()
)
)
): List<FirstClassByteArray> =
backend.createProposedTransactions(
proposal.toUnsafe(),
usk.copyBytes()
).map { FirstClassByteArray(it) }
override suspend fun getCurrentAddress(account: Account): String {
return backend.getCurrentAddress(account.value)

View File

@ -2,7 +2,9 @@ package cash.z.ecc.android.sdk.internal.transaction
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.Zatoshi
@ -33,6 +35,62 @@ internal interface OutboundTransactionManager {
account: Account
): EncodedTransaction
/**
* Creates a proposal for transferring funds to the given recipient.
*
* @param account the account from which to transfer funds.
* @param recipient the recipient's address.
* @param amount the amount of zatoshi to send.
* @param memo the optional memo to include as part of the proposal's transactions.
*
* @return the proposal or an exception
*/
suspend fun proposeTransfer(
account: Account,
recipient: String,
amount: Zatoshi,
memo: String
): Proposal
/**
* Creates a proposal for shielding any transparent funds received by the given account.
*
* @param account the account for which to shield funds.
* @param shieldingThreshold the minimum transparent balance required before a
* proposal will be created.
* @param memo the optional memo to include as part of the proposal's transactions.
* @param transparentReceiver a specific transparent receiver within the account that
* should be the source of transparent funds. Default is
* null which will select whichever of the account's
* transparent receivers has funds to shield.
*
* @return the proposal, or null if the transparent balance that would be shielded is
* zero or below `shieldingThreshold`.
*
* @throws Exception if `transparentReceiver` is null and there are transparent funds
* in more than one of the account's transparent receivers.
*/
suspend fun proposeShielding(
account: Account,
shieldingThreshold: Zatoshi,
memo: String,
transparentReceiver: String?
): Proposal?
/**
* Creates the transactions in the given proposal.
*
* @param proposal the proposal for which to create transactions.
* @param usk the unified spending key associated with the account for which the
* proposal was created.
*
* @return the successfully encoded transactions or an exception
*/
suspend fun createProposedTransactions(
proposal: Proposal,
usk: UnifiedSpendingKey
): List<EncodedTransaction>
/**
* Submits the transaction represented by [encodedTransaction] to lightwalletd to broadcast to the
* network and, hopefully, include in the next block.
@ -41,7 +99,7 @@ internal interface OutboundTransactionManager {
* to lightwalletd.
* @return true if the transaction was successfully submitted to lightwalletd.
*/
suspend fun submit(encodedTransaction: EncodedTransaction): Boolean
suspend fun submit(encodedTransaction: EncodedTransaction): TransactionSubmitResult
/**
* Return true when the given address is a valid t-addr.

View File

@ -1,9 +1,12 @@
package cash.z.ecc.android.sdk.internal.transaction
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.ext.toHexReversed
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.Zatoshi
import co.electriccoin.lightwallet.client.LightWalletClient
@ -40,20 +43,60 @@ internal class OutboundTransactionManagerImpl(
}
}
override suspend fun submit(encodedTransaction: EncodedTransaction): Boolean {
override suspend fun proposeTransfer(
account: Account,
recipient: String,
amount: Zatoshi,
memo: String
): Proposal = encoder.proposeTransfer(account, recipient, amount, memo.toByteArray())
override suspend fun proposeShielding(
account: Account,
shieldingThreshold: Zatoshi,
memo: String,
transparentReceiver: String?
): Proposal? = encoder.proposeShielding(account, shieldingThreshold, memo.toByteArray(), transparentReceiver)
override suspend fun createProposedTransactions(
proposal: Proposal,
usk: UnifiedSpendingKey
): List<EncodedTransaction> = encoder.createProposedTransactions(proposal, usk)
override suspend fun submit(encodedTransaction: EncodedTransaction): TransactionSubmitResult {
return when (val response = service.submitTransaction(encodedTransaction.raw.byteArray)) {
is Response.Success -> {
Twig.debug { "SUCCESS: submit transaction completed with response: ${response.result}" }
true
if (response.result.code == 0) {
Twig.info {
"SUCCESS: submit transaction completed for:" +
" ${encodedTransaction.txId.byteArray.toHexReversed()}"
}
TransactionSubmitResult.Success(encodedTransaction.txId)
} else {
Twig.error {
"FAILURE! submit transaction ${encodedTransaction.txId.byteArray.toHexReversed()} " +
"completed with response: ${response.result.code}: ${response.result.message}"
}
TransactionSubmitResult.Failure(
encodedTransaction.txId,
false,
response.result.code,
response.result.message
)
}
}
is Response.Failure -> {
Twig.debug {
"FAILURE! submit transaction completed with response: ${response.code}: ${
Twig.error {
"FAILURE! submit transaction failed with gRPC response: ${response.code}: ${
response.description
}"
}
false
TransactionSubmitResult.Failure(
encodedTransaction.txId,
true,
response.code,
response.description
)
}
}
}

View File

@ -1,7 +1,9 @@
package cash.z.ecc.android.sdk.internal.transaction
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.Zatoshi
@ -38,6 +40,61 @@ internal interface TransactionEncoder {
memo: ByteArray? = byteArrayOf()
): EncodedTransaction
/**
* Creates a proposal for transferring funds to the given recipient.
*
* @param account the account from which to transfer funds.
* @param recipient the recipient's address.
* @param amount the amount of zatoshi to send.
* @param memo the optional memo to include as part of the proposal's transactions.
*
* @return the proposal or an exception
*/
suspend fun proposeTransfer(
account: Account,
recipient: String,
amount: Zatoshi,
memo: ByteArray? = byteArrayOf()
): Proposal
/**
* Creates a proposal for shielding any transparent funds sent to the given account.
*
* @param account the account for which to shield funds.
* @param shieldingThreshold the minimum transparent balance required before a
* proposal will be created.
* @param memo the optional memo to include as part of the proposal's transactions.
* @param transparentReceiver a specific transparent receiver within the account that
* should be the source of transparent funds. Default is
* null which will select whichever of the account's
* transparent receivers has funds to shield.
*
* @return the proposal, or null if the transparent balance that would be shielded is
* zero or below `shieldingThreshold`.
*
* @throws Exception if `transparentReceiver` is null and there are transparent funds
* in more than one of the account's transparent receivers.
*/
suspend fun proposeShielding(
account: Account,
shieldingThreshold: Zatoshi,
memo: ByteArray? = byteArrayOf(),
transparentReceiver: String? = null
): Proposal?
/**
* Creates the transactions in the given proposal.
*
* @param proposal the proposal to create.
* @param usk the unified spending key associated with the notes that will be spent.
*
* @return the successfully encoded transactions or an exception
*/
suspend fun createProposedTransactions(
proposal: Proposal,
usk: UnifiedSpendingKey
): List<EncodedTransaction>
/**
* Utility function to help with validation. This is not called during [createTransaction]
* because this class asserts that all validation is done externally by the UI, for now.

View File

@ -1,5 +1,6 @@
package cash.z.ecc.android.sdk.internal.transaction
import cash.z.ecc.android.sdk.exception.SdkException
import cash.z.ecc.android.sdk.exception.TransactionEncoderException
import cash.z.ecc.android.sdk.ext.masked
import cash.z.ecc.android.sdk.internal.SaplingParamTool
@ -7,8 +8,10 @@ import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.TypesafeBackend
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.FirstClassByteArray
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.Zatoshi
@ -22,6 +25,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
* @property repository the repository that stores information about the transactions being created
* such as the raw bytes and raw txId.
*/
@Suppress("TooManyFunctions")
internal class TransactionEncoderImpl(
private val backend: TypesafeBackend,
private val saplingParamTool: SaplingParamTool,
@ -64,6 +68,77 @@ internal class TransactionEncoderImpl(
?: throw TransactionEncoderException.TransactionNotFoundException(transactionId)
}
override suspend fun proposeTransfer(
account: Account,
recipient: String,
amount: Zatoshi,
memo: ByteArray?
): Proposal {
Twig.debug {
"creating proposal to spend $amount zatoshi to" +
" ${recipient.masked()} with memo: ${memo?.decodeToString()}"
}
return runCatching {
backend.proposeTransfer(
account,
recipient,
amount.value,
memo
)
}.onFailure {
Twig.error(it) { "Caught exception while creating proposal." }
}.onSuccess { result ->
Twig.debug { "result of proposeTransfer: $result" }
}.getOrThrow()
}
override suspend fun proposeShielding(
account: Account,
shieldingThreshold: Zatoshi,
memo: ByteArray?,
transparentReceiver: String?
): Proposal? {
return runCatching {
backend.proposeShielding(account, shieldingThreshold.value, memo, transparentReceiver)
}.onFailure {
// TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee)
// then consider custom error that says no UTXOs existed to shield
// TODO [#680]: https://github.com/zcash/zcash-android-wallet-sdk/issues/680
Twig.error(it) { "proposeShielding failed" }
}.onSuccess { result ->
Twig.debug { "result of proposeShielding: $result" }
}.getOrThrow()
}
override suspend fun createProposedTransactions(
proposal: Proposal,
usk: UnifiedSpendingKey
): List<EncodedTransaction> {
Twig.debug {
"creating transactions for proposal"
}
val transactionIds =
runCatching {
saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory)
Twig.debug { "params exist! attempting to send..." }
backend.createProposedTransactions(proposal, usk)
}.onFailure {
Twig.error(it) { "Caught exception while creating transaction." }
}.onSuccess { result ->
Twig.debug { "result of createProposedTransactions: $result" }
}.getOrThrow()
val txs =
transactionIds.map { transactionId ->
repository.findEncodedTransactionByTxId(transactionId)
?: throw TransactionEncoderException.TransactionNotFoundException(transactionId)
}
return txs
}
/**
* Utility function to help with validation. This is not called during [createTransaction]
* because this class asserts that all validation is done externally by the UI, for now.
@ -133,48 +208,53 @@ internal class TransactionEncoderImpl(
" ${toAddress.masked()} with memo: ${memo?.decodeToString()}"
}
@Suppress("TooGenericExceptionCaught")
return try {
return runCatching {
saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory)
Twig.debug { "params exist! attempting to send..." }
// TODO [#1359]: Expose the proposal in a way that enables querying its fee.
// TODO [#1359]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/1359
val proposal =
backend.proposeTransfer(
usk,
usk.account,
toAddress,
amount.value,
memo
)
backend.createProposedTransaction(proposal, usk)
} catch (t: Throwable) {
Twig.debug(t) { "Caught exception while creating transaction." }
throw t
}.also { result ->
val transactionIds = backend.createProposedTransactions(proposal, usk)
assert(transactionIds.size == 1)
transactionIds[0]
}.onFailure {
Twig.error(it) { "Caught exception while creating transaction." }
}.onSuccess { result ->
Twig.debug { "result of sendToAddress: $result" }
}
}.getOrThrow()
}
private suspend fun createShieldingSpend(
usk: UnifiedSpendingKey,
memo: ByteArray? = byteArrayOf()
): FirstClassByteArray {
@Suppress("TooGenericExceptionCaught")
return try {
return runCatching {
saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory)
Twig.debug { "params exist! attempting to shield..." }
// TODO [#1359]: Expose the proposal in a way that enables querying its fee.
// TODO [#1359]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/1359
val proposal = backend.proposeShielding(usk, memo)
backend.createProposedTransaction(proposal, usk)
} catch (t: Throwable) {
val proposal =
backend.proposeShielding(usk.account, SHIELDING_THRESHOLD, memo)
?: throw SdkException(
"Insufficient balance (have 0, need $SHIELDING_THRESHOLD including fee)",
null
)
val transactionIds = backend.createProposedTransactions(proposal, usk)
assert(transactionIds.size == 1)
transactionIds[0]
}.onFailure {
// TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee)
// then consider custom error that says no UTXOs existed to shield
// TODO [#680]: https://github.com/zcash/zcash-android-wallet-sdk/issues/680
Twig.debug(t) { "Shield failed" }
throw t
}.also { result ->
Twig.error(it) { "Shield failed" }
}.onSuccess { result ->
Twig.debug { "result of shieldToAddress: $result" }
}
}.getOrThrow()
}
companion object {
private const val SHIELDING_THRESHOLD = 100000L
}
}

View File

@ -20,7 +20,7 @@ class Proposal(
// Check for type errors eagerly, to ensure that the caller won't
// encounter these errors later.
typed.feeRequired()
typed.totalFeeRequired()
return typed
}
@ -34,9 +34,21 @@ class Proposal(
}
/**
* Returns the fee required by this proposal.
* Returns the number of transactions that this proposal will create.
*
* This is equal to the number of `TransactionSubmitResult`s that will be returned
* from `Synchronizer.createProposedTransactions`.
*
* Proposals always create at least one transaction.
*/
fun feeRequired(): Zatoshi {
return Zatoshi(inner.feeRequired())
fun transactionCount(): Int {
return inner.transactionCount()
}
/**
* Returns the total fee required by this proposal for its transactions.
*/
fun totalFeeRequired(): Zatoshi {
return Zatoshi(inner.totalFeeRequired())
}
}

View File

@ -0,0 +1,32 @@
package cash.z.ecc.android.sdk.model
/**
* A result object for a transaction that was created as part of a proposal, indicating
* whether it was submitted to the network or if an error occurred.
*/
sealed class TransactionSubmitResult {
/**
* The transaction was successfully submitted to the mempool.
*/
data class Success(val txId: FirstClassByteArray) : TransactionSubmitResult()
/**
* An error occurred while submitting the transaction.
*
* If `grpcError` is true, the transaction failed to reach the `lightwalletd` server.
* Otherwise, the transaction reached the `lightwalletd` server but failed to enter
* the mempool.
*/
data class Failure(
val txId: FirstClassByteArray,
val grpcError: Boolean,
val code: Int,
val description: String?
) : TransactionSubmitResult()
/**
* The transaction was created and is in the local wallet, but was not submitted to
* the network.
*/
data class NotAttempted(val txId: FirstClassByteArray) : TransactionSubmitResult()
}