diff --git a/backend-lib/Cargo.lock b/backend-lib/Cargo.lock index 0d35da5a..fbb678cd 100644 --- a/backend-lib/Cargo.lock +++ b/backend-lib/Cargo.lock @@ -469,7 +469,8 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[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 +495,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", ] @@ -1035,9 +1037,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[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", @@ -1455,9 +1457,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", @@ -2169,7 +2171,6 @@ dependencies = [ "jni", "libc", "log-panics", - "orchard", "paranoid-android", "prost", "rayon", @@ -2190,7 +2191,8 @@ dependencies = [ [[package]] name = "zcash_address" version = "0.3.1" -source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce173f1d9ed4f806e310bc3a873301531e7a6dc209928584d6404e3f8228ef4" dependencies = [ "bech32", "bs58", @@ -2200,8 +2202,9 @@ dependencies = [ [[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 +2242,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 +2254,6 @@ dependencies = [ "incrementalmerkletree", "jubjub", "maybe-rayon", - "orchard", "prost", "rusqlite", "sapling-crypto", @@ -2271,7 +2274,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 +2283,9 @@ dependencies = [ [[package]] name = "zcash_keys" -version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48390513c05a4090533e2a1aca486f0fb6b684493b8aefedda6ee01742b3f8f6" dependencies = [ "bech32", "bls12_381", @@ -2317,8 +2322,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 +2360,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", diff --git a/backend-lib/Cargo.toml b/backend-lib/Cargo.toml index 8885ab59..ca135271 100644 --- a/backend-lib/Cargo.toml +++ b/backend-lib/Cargo.toml @@ -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" } diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt index 6ba1375b..0fef4da1 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt @@ -35,10 +35,10 @@ interface Backend { transparentReceiver: String? = null ): ProposalUnsafe? - suspend fun createProposedTransaction( + suspend fun createProposedTransactions( proposal: ProposalUnsafe, unifiedSpendingKey: ByteArray - ): ByteArray + ): List suspend fun decryptAndStoreTransaction(tx: ByteArray) diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt index e0cfffde..82f5f0bc 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt @@ -328,19 +328,19 @@ class RustBackend private constructor( } } - override suspend fun createProposedTransaction( + override suspend fun createProposedTransactions( proposal: ProposalUnsafe, unifiedSpendingKey: ByteArray - ): ByteArray = + ): List = withContext(SdkDispatchers.DATABASE_IO) { - createProposedTransaction( + createProposedTransactions( dataDbFile.absolutePath, proposal.toByteArray(), unifiedSpendingKey, spendParamsPath = saplingSpendFile.absolutePath, outputParamsPath = saplingOutputFile.absolutePath, networkId = networkId - ) + ).asList() } override suspend fun putUtxo( @@ -599,14 +599,14 @@ class RustBackend private constructor( @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 @JvmStatic private external fun branchIdForHeight( diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ProposalUnsafe.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ProposalUnsafe.kt index f9af1fa6..5ce80d3f 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ProposalUnsafe.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ProposalUnsafe.kt @@ -38,9 +38,9 @@ class ProposalUnsafe( } /** - * Returns the fee required by this proposal. + * Returns the total fee required by this proposal for its transactions. */ - fun feeRequired(): Long { - return inner.balance.feeRequired + fun totalFeeRequired(): Long { + return inner.stepsList.fold(0) { acc, step -> acc + step.balance.feeRequired } } } diff --git a/backend-lib/src/main/proto/proposal.proto b/backend-lib/src/main/proto/proposal.proto index 84a8aed4..4084efb2 100644 --- a/backend-lib/src/main/proto/proposal.proto +++ b/backend-lib/src/main/proto/proposal.proto @@ -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; } diff --git a/backend-lib/src/main/rust/lib.rs b/backend-lib/src/main/rust/lib.rs index 2890400c..02861c03 100644 --- a/backend-lib/src/main/rust/lib.rs +++ b/backend-lib/src/main/rust/lib.rs @@ -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, ) -> jni::errors::Result> { let account_balances = utils::rust_vec_to_java( env, @@ -1397,7 +1397,7 @@ fn zip317_helper( 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()) } @@ -1574,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>, @@ -1597,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)?; @@ -1612,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, @@ -1621,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()) } @@ -1699,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) } }; diff --git a/backend-lib/src/main/rust/utils.rs b/backend-lib/src/main/rust/utils.rs index ef1ed9aa..e1867926 100644 --- a/backend-lib/src/main/rust/utils.rs +++ b/backend-lib/src/main/rust/utils.rs @@ -42,10 +42,7 @@ pub(crate) fn java_nullable_string_to_rust(env: &mut JNIEnv, jstring: &JString) (!jstring.is_null()).then(|| java_string_to_rust(env, jstring)) } -pub(crate) fn rust_bytes_to_java<'a>( - env: &JNIEnv<'a>, - data: &[u8], -) -> Result, failure::Error> { +pub(crate) fn rust_bytes_to_java<'a>(env: &JNIEnv<'a>, data: &[u8]) -> JNIResult> { // 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()) }; diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt index 53872421..e5f80350 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt @@ -95,10 +95,10 @@ internal class FakeRustBackend( TODO("Not yet implemented") } - override suspend fun createProposedTransaction( + override suspend fun createProposedTransactions( proposal: ProposalUnsafe, unifiedSpendingKey: ByteArray - ): ByteArray { + ): List { TODO("Not yet implemented") } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt index 69a7c0b6..42861be8 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt @@ -39,10 +39,10 @@ internal interface TypesafeBackend { transparentReceiver: String? = null ): Proposal? - suspend fun createProposedTransaction( + suspend fun createProposedTransactions( proposal: Proposal, usk: UnifiedSpendingKey - ): FirstClassByteArray + ): List suspend fun getCurrentAddress(account: Account): String diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt index 5d66a329..1eb811f3 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt @@ -68,16 +68,14 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke ) } - override suspend fun createProposedTransaction( + override suspend fun createProposedTransactions( proposal: Proposal, usk: UnifiedSpendingKey - ): FirstClassByteArray = - FirstClassByteArray( - backend.createProposedTransaction( - proposal.toUnsafe(), - usk.copyBytes() - ) - ) + ): List = + backend.createProposedTransactions( + proposal.toUnsafe(), + usk.copyBytes() + ).map { FirstClassByteArray(it) } override suspend fun getCurrentAddress(account: Account): String { return backend.getCurrentAddress(account.value) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt index 671c089a..807b442b 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt @@ -124,11 +124,11 @@ internal class TransactionEncoderImpl( } @Suppress("TooGenericExceptionCaught") - val transactionId = + val transactionIds = try { saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory) Twig.debug { "params exist! attempting to send..." } - backend.createProposedTransaction(proposal, usk) + backend.createProposedTransactions(proposal, usk) } catch (t: Throwable) { Twig.debug(t) { "Caught exception while creating transaction." } throw t @@ -136,11 +136,13 @@ internal class TransactionEncoderImpl( Twig.debug { "result of createProposedTransactions: $result" } } - val tx = - repository.findEncodedTransactionByTxId(transactionId) - ?: throw TransactionEncoderException.TransactionNotFoundException(transactionId) + val txs = + transactionIds.map { transactionId -> + repository.findEncodedTransactionByTxId(transactionId) + ?: throw TransactionEncoderException.TransactionNotFoundException(transactionId) + } - return listOf(tx) + return txs } /** @@ -223,7 +225,9 @@ internal class TransactionEncoderImpl( amount.value, memo ) - backend.createProposedTransaction(proposal, usk) + val transactionIds = backend.createProposedTransactions(proposal, usk) + assert(transactionIds.size == 1) + transactionIds[0] } catch (t: Throwable) { Twig.debug(t) { "Caught exception while creating transaction." } throw t @@ -244,7 +248,9 @@ internal class TransactionEncoderImpl( val proposal = backend.proposeShielding(usk.account, 100000, memo) ?: throw SdkException("Insufficient balance (have 0, need 100000 including fee)", null) - backend.createProposedTransaction(proposal, usk) + val transactionIds = backend.createProposedTransactions(proposal, usk) + assert(transactionIds.size == 1) + transactionIds[0] } catch (t: Throwable) { // 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 diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt index fc068f9b..d28361b1 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt @@ -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,9 @@ class Proposal( } /** - * Returns the fee required by this proposal. + * Returns the total fee required by this proposal for its transactions. */ - fun feeRequired(): Zatoshi { - return Zatoshi(inner.feeRequired()) + fun totalFeeRequired(): Zatoshi { + return Zatoshi(inner.totalFeeRequired()) } }