Migrate to `zcash_client_sqlite 0.9.0`

The backend now supports proposals that create multiple transactions.
It still does not generate such proposals itself, and we assert this
inside the now-deprecated APIs.
This commit is contained in:
Jack Grigg 2024-03-01 18:51:54 +00:00
parent abffb3f9ee
commit 99e91fa8b7
13 changed files with 157 additions and 104 deletions

47
backend-lib/Cargo.lock generated
View File

@ -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",

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

@ -35,10 +35,10 @@ interface Backend {
transparentReceiver: String? = null
): ProposalUnsafe?
suspend fun createProposedTransaction(
suspend fun createProposedTransactions(
proposal: ProposalUnsafe,
unifiedSpendingKey: ByteArray
): ByteArray
): List<ByteArray>
suspend fun decryptAndStoreTransaction(tx: ByteArray)

View File

@ -328,19 +328,19 @@ class RustBackend private constructor(
}
}
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(
@ -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<ByteArray>
@JvmStatic
private external fun branchIdForHeight(

View File

@ -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 }
}
}

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())
}
@ -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)
}
};

View File

@ -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<JByteArray<'a>, failure::Error> {
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

@ -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<ByteArray> {
TODO("Not yet implemented")
}

View File

@ -39,10 +39,10 @@ internal interface TypesafeBackend {
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

@ -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<FirstClassByteArray> =
backend.createProposedTransactions(
proposal.toUnsafe(),
usk.copyBytes()
).map { FirstClassByteArray(it) }
override suspend fun getCurrentAddress(account: Account): String {
return backend.getCurrentAddress(account.value)

View File

@ -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

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,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())
}
}