New: Non-Rust changes to support auto-shielding.

This commit is contained in:
Kevin Gorham 2021-02-17 16:07:57 -05:00
parent 5f90675b87
commit 1d2c7b1a4f
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
16 changed files with 436 additions and 40 deletions

View File

@ -40,6 +40,7 @@ import cash.z.ecc.android.sdk.ext.twigTask
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.service.LightWalletService
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.transaction.OutboundTransactionManager
import cash.z.ecc.android.sdk.transaction.PagedTransactionRepository
import cash.z.ecc.android.sdk.transaction.PersistentTransactionManager
@ -500,7 +501,13 @@ class SdkSynchronizer internal constructor(
override suspend fun cancelSpend(pendingId: Long) = txManager.cancel(pendingId)
override suspend fun getAddress(accountId: Int): String = processor.getAddress(accountId)
override suspend fun getAddress(accountId: Int): String = getShieldedAddress(accountId)
override suspend fun getShieldedAddress(accountId: Int): String = processor.getShieldedAddress(accountId)
override suspend fun getTransparentAddress(seed: ByteArray, accountId: Int, index: Int): String {
return DerivationTool.deriveTransparentAddress(seed, accountId, index)
}
override fun sendToAddress(
spendingKey: String,
@ -529,6 +536,45 @@ class SdkSynchronizer internal constructor(
txManager.monitorById(it.id)
}.distinctUntilChanged()
override fun shieldFunds(
spendingKey: String,
transparentSecretKey: String,
memo: String
): Flow<PendingTransaction> = flow {
twig("Initializing shielding transaction")
val tAddr = DerivationTool.deriveTransparentAddress(transparentSecretKey)
val tBalance = processor.getUtxoCacheBalance(tAddr)
val zAddr = getAddress(0)
// Emit the placeholder transaction, then switch to monitoring the database
txManager.initSpend(tBalance.availableZatoshi, zAddr, memo, 0).let { placeHolderTx ->
emit(placeHolderTx)
txManager.encode(spendingKey, transparentSecretKey, placeHolderTx).let { encodedTx ->
// only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX.
if (encodedTx.isCancelled()) {
twig("[cleanup] this shielding tx has been cancelled so we will cleanup instead of submitting")
if (cleanupCancelledTx(encodedTx)) refreshBalance()
encodedTx
} else {
txManager.submit(encodedTx)
}
}
}
}.flatMapLatest {
twig("Monitoring shielding transaction (id: ${it.id}) for updates...")
txManager.monitorById(it.id)
}.distinctUntilChanged()
override suspend fun refreshUtxos(address: String, sinceHeight: Int): Int {
// TODO: we need to think about how we restrict this to only our taddr
return processor.downloadUtxos(address, sinceHeight)
}
override suspend fun getTransparentBalance(tAddr: String): WalletBalance {
return processor.getUtxoCacheBalance(tAddr)
}
override suspend fun isValidShieldedAddr(address: String) =
txManager.isValidShieldedAddress(address)
@ -649,4 +695,4 @@ fun Synchronizer(
txManager,
processor
)
}
}

View File

@ -120,14 +120,38 @@ interface Synchronizer {
//
/**
* Gets the address for the given account.
* Gets the shielded address for the given account. This is syntactic sugar for
* [getShieldedAddress] because we use z-addrs by default.
*
* @param accountId the optional accountId whose address is of interest. By default, the first
* account is used.
*
* @return the address for the given account.
* @return the shielded address for the given account.
*/
suspend fun getAddress(accountId: Int = 0): String
suspend fun getAddress(accountId: Int = 0) = getShieldedAddress(accountId)
/**
* Gets the shielded address for the given account.
*
* @param accountId the optional accountId whose address is of interest. By default, the first
* account is used.
*
* @return the shielded address for the given account.
*/
suspend fun getShieldedAddress(accountId: Int = 0): String
/**
* Gets the transparent address for the given account and index.
*
* @param accountId the optional accountId whose address is of interest. By default, the first
* account is used.
* @param index the optional index whose address is of interest. By default, the first index is
* used.
*
* @return the address for the given account and index.
*/
suspend fun getTransparentAddress(seed: ByteArray, accountId: Int = 0, index: Int = 0): String
/**
* Sends zatoshi.
@ -151,6 +175,12 @@ interface Synchronizer {
fromAccountIndex: Int = 0
): Flow<PendingTransaction>
fun shieldFunds(
spendingKey: String,
transparentSecretKey: String,
memo: String = ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX
): Flow<PendingTransaction>
/**
* Returns true when the given address is a valid z-addr. Invalid addresses will throw an
* exception. Valid z-addresses have these characteristics: //TODO copy info from related ZIP
@ -230,6 +260,13 @@ interface Synchronizer {
errorHandler: (Throwable) -> Unit = { throw it }
)
suspend fun refreshUtxos(tAddr: String, sinceHeight: Int): Int
/**
* Returns the balance that the wallet knows about. This should be called after [refreshUtxos].
*/
suspend fun getTransparentBalance(tAddr: String): WalletBalance
//
// Error Handling
//

View File

@ -34,6 +34,7 @@ import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.jni.RustBackendWelding
import cash.z.ecc.android.sdk.transaction.PagedTransactionRepository
import cash.z.ecc.android.sdk.transaction.TransactionRepository
import cash.z.wallet.sdk.rpc.Service
import io.grpc.StatusRuntimeException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
@ -313,6 +314,33 @@ class CompactBlockProcessor(
if (!repository.isInitialized()) throw CompactBlockProcessorException.Uninitialized
}
internal suspend fun downloadUtxos(tAddress: String, startHeight: Int): Int = withContext(IO) {
var skipped = 0
twig("Downloading utxos starting at height $startHeight")
downloader.lightWalletService.fetchUtxos(tAddress, startHeight).let { result ->
result.forEach { utxo: Service.GetAddressUtxosReply ->
twig("Found UTXO at height ${utxo.height.toInt()}")
try {
rustBackend.putUtxo(
tAddress,
utxo.txid.toByteArray(),
utxo.index,
utxo.script.toByteArray(),
utxo.valueZat,
utxo.height.toInt()
)
} catch (t: Throwable) {
// TODO: more accurately track the utxos that were skipped (in theory, this could fail for other reasons)
skipped++
twig("Warning: Ignoring transaction at height ${utxo.height} @ index ${utxo.index} because it already exists")
}
}
// return the number of UTXOs that were downloaded
result.size - skipped
}
}
/**
* Request all blocks in the given range and persist them locally for processing, later.
*
@ -546,8 +574,8 @@ class CompactBlockProcessor(
*
* @return the address of this wallet.
*/
suspend fun getAddress(accountId: Int) = withContext(IO) {
rustBackend.getAddress(accountId)
suspend fun getShieldedAddress(accountId: Int) = withContext(IO) {
rustBackend.getShieldedAddress(accountId)
}
/**
@ -572,6 +600,11 @@ class CompactBlockProcessor(
}
}
suspend fun getUtxoCacheBalance(address: String): WalletBalance = withContext(IO) {
rustBackend.getDownloadedUtxoBalance(address)
}
/**
* Transmits the given state for this processor.
*/

View File

@ -0,0 +1,59 @@
package cash.z.ecc.android.sdk.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "utxos")
data class UtxoEntity(
val address: String ="",
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
val txid: ByteArray? = byteArrayOf(),
@ColumnInfo(name = "tx_index")
val transactionIndex: Int? = -1,
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
val script: ByteArray? = byteArrayOf(),
val value: Long = 0L,
val height: Int? = -1,
) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0L
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is UtxoEntity) return false
if (id != other.id) return false
if (address != other.address) return false
if (txid != null) {
if (other.txid == null) return false
if (!txid.contentEquals(other.txid)) return false
} else if (other.txid != null) return false
if (transactionIndex != other.transactionIndex) return false
if (script != null) {
if (other.script == null) return false
if (!script.contentEquals(other.script)) return false
} else if (other.script != null) return false
if (value != other.value) return false
if (height != other.height) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + address.hashCode()
result = 31 * result + (txid?.contentHashCode() ?: 0)
result = 31 * result + (transactionIndex ?: 0)
result = 31 * result + (script?.contentHashCode() ?: 0)
result = 31 * result + value.hashCode()
result = 31 * result + (height ?: 0)
return result
}
}

View File

@ -110,4 +110,8 @@ open class ZcashSdkCommon {
* this will do for now, since we're using a cloudfront URL that already redirects.
*/
val CLOUD_PARAM_DIR_URL = "https://z.cash/downloads/"
/**
* The default memo to use when shielding transparent funds.
*/
open val DEFAULT_SHIELD_FUNDS_MEMO_PREFIX = "shielding:"
}

View File

@ -1,10 +1,10 @@
package cash.z.ecc.android.sdk.jni
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.exception.BirthdayException
import cash.z.ecc.android.sdk.ext.ZcashSdk.OUTPUT_PARAM_FILE_NAME
import cash.z.ecc.android.sdk.ext.ZcashSdk.SPEND_PARAM_FILE_NAME
import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.rpc.LocalRpcTypes
import java.io.File
/**
@ -64,7 +64,11 @@ class RustBackend private constructor() : RustBackendWelding {
return initBlocksTable(pathDataDb, height, hash, time, saplingTree)
}
override fun getAddress(account: Int) = getAddress(pathDataDb, account)
override fun getShieldedAddress(account: Int) = getShieldedAddress(pathDataDb, account)
override fun getTransparentAddress(account: Int, index: Int): String {
throw NotImplementedError("TODO: implement this at the zcash_client_sqlite level. But for now, use DerivationTool, instead to derive addresses from seeds")
}
override fun getBalance(account: Int) = getBalance(pathDataDb, account)
@ -108,30 +112,62 @@ class RustBackend private constructor() : RustBackendWelding {
"$pathParamsDir/$OUTPUT_PARAM_FILE_NAME"
)
override fun shieldToAddress(
extsk: String,
tsk: String,
memo: ByteArray?
): Long {
twig("TMP: shieldToAddress with db path: $pathDataDb, ${memo?.size}")
return shieldToAddress(
pathDataDb,
0,
extsk,
tsk,
memo ?: ByteArray(0),
"${pathParamsDir}/$SPEND_PARAM_FILE_NAME",
"${pathParamsDir}/$OUTPUT_PARAM_FILE_NAME"
)
}
override fun putUtxo(
tAddress: String,
txId: ByteArray,
index: Int,
script: ByteArray,
value: Long,
height: Int
): Boolean = putUtxo(pathDataDb, tAddress, txId, index, script, value, height)
override fun getDownloadedUtxoBalance(address: String): CompactBlockProcessor.WalletBalance {
val verified = getVerifiedTransparentBalance(pathDataDb, address)
val total = getTotalTransparentBalance(pathDataDb, address)
return CompactBlockProcessor.WalletBalance(total, verified)
}
override fun isValidShieldedAddr(addr: String) = isValidShieldedAddress(addr)
override fun isValidTransparentAddr(addr: String) = isValidTransparentAddress(addr)
override fun getBranchIdForHeight(height: Int): Long = branchIdForHeight(height)
/**
* This is a proof-of-concept for doing Local RPC, where we are effectively using the JNI
* boundary as a grpc server. It is slightly inefficient in terms of both space and time but
* given that it is all done locally, on the heap, it seems to be a worthwhile tradeoff because
* it reduces the complexity and expands the capacity for the two layers to communicate.
*
* We're able to keep the "unsafe" byteArray functions private and wrap them in typeSafe
* equivalents and, eventually, surface any parse errors (for now, errors are only logged).
*/
override fun parseTransactionDataList(tdl: LocalRpcTypes.TransactionDataList): LocalRpcTypes.TransparentTransactionList {
return try {
// serialize the list, send it over to rust and get back a serialized set of results that we parse out and return
return LocalRpcTypes.TransparentTransactionList.parseFrom(parseTransactionDataList(tdl.toByteArray()))
} catch (t: Throwable) {
twig("ERROR: failed to parse transaction data list due to: $t caused by: ${t.cause}")
LocalRpcTypes.TransparentTransactionList.newBuilder().build()
}
}
// /**
// * This is a proof-of-concept for doing Local RPC, where we are effectively using the JNI
// * boundary as a grpc server. It is slightly inefficient in terms of both space and time but
// * given that it is all done locally, on the heap, it seems to be a worthwhile tradeoff because
// * it reduces the complexity and expands the capacity for the two layers to communicate.
// *
// * We're able to keep the "unsafe" byteArray functions private and wrap them in typeSafe
// * equivalents and, eventually, surface any parse errors (for now, errors are only logged).
// */
// override fun parseTransactionDataList(tdl: LocalRpcTypes.TransactionDataList): LocalRpcTypes.TransparentTransactionList {
// return try {
// // serialize the list, send it over to rust and get back a serialized set of results that we parse out and return
// return LocalRpcTypes.TransparentTransactionList.parseFrom(parseTransactionDataList(tdl.toByteArray()))
// } catch (t: Throwable) {
// twig("ERROR: failed to parse transaction data list due to: $t caused by: ${t.cause}")
// LocalRpcTypes.TransparentTransactionList.newBuilder().build()
// }
// }
/**
* Exposes all of the librustzcash functions along with helpers for loading the static library.
@ -208,7 +244,9 @@ class RustBackend private constructor() : RustBackendWelding {
saplingTree: String
): Boolean
@JvmStatic private external fun getAddress(dbDataPath: String, account: Int): String
@JvmStatic private external fun getShieldedAddress(dbDataPath: String, account: Int): String
// TODO: implement this in the zcash_client_sqlite layer. For now, use DerivationTool, instead.
// @JvmStatic private external fun getTransparentAddress(dbDataPath: String, account: Int): String
@JvmStatic private external fun isValidShieldedAddress(addr: String): Boolean
@ -244,10 +282,38 @@ class RustBackend private constructor() : RustBackendWelding {
outputParamsPath: String
): Long
@JvmStatic private external fun shieldToAddress(
dbDataPath: String,
account: Int,
extsk: String,
tsk: String,
memo: ByteArray,
spendParamsPath: String,
outputParamsPath: String
): Long
@JvmStatic private external fun initLogs()
@JvmStatic private external fun branchIdForHeight(height: Int): Long
@JvmStatic private external fun parseTransactionDataList(serializedList: ByteArray): ByteArray
@JvmStatic private external fun putUtxo(
dbDataPath: String,
tAddress: String,
txId: ByteArray,
index: Int,
script: ByteArray,
value: Long,
height: Int
): Boolean
@JvmStatic private external fun getVerifiedTransparentBalance(
pathDataDb: String,
taddr: String
): Long
@JvmStatic private external fun getTotalTransparentBalance(
pathDataDb: String,
taddr: String
): Long
}
}

View File

@ -1,6 +1,6 @@
package cash.z.ecc.android.sdk.jni
import cash.z.ecc.android.sdk.rpc.LocalRpcTypes
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
/**
* Contract defining the exposed capabilities of the Rust backend.
@ -19,6 +19,12 @@ interface RustBackendWelding {
memo: ByteArray? = byteArrayOf()
): Long
fun shieldToAddress(
extsk: String,
tsk: String,
memo: ByteArray? = byteArrayOf()
): Long
fun decryptAndStoreTransaction(tx: ByteArray)
fun initAccountsTable(seed: ByteArray, numberOfAccounts: Int): Array<String>
@ -33,7 +39,9 @@ interface RustBackendWelding {
fun isValidTransparentAddr(addr: String): Boolean
fun getAddress(account: Int = 0): String
fun getShieldedAddress(account: Int = 0): String
fun getTransparentAddress(account: Int = 0, index: Int = 0): String
fun getBalance(account: Int = 0): Long
@ -45,7 +53,7 @@ interface RustBackendWelding {
fun getVerifiedBalance(account: Int = 0): Long
fun parseTransactionDataList(tdl: LocalRpcTypes.TransactionDataList): LocalRpcTypes.TransparentTransactionList
// fun parseTransactionDataList(tdl: LocalRpcTypes.TransactionDataList): LocalRpcTypes.TransparentTransactionList
fun rewindToHeight(height: Int): Boolean
@ -53,6 +61,17 @@ interface RustBackendWelding {
fun validateCombinedChain(): Int
fun putUtxo(
tAddress: String,
txId: ByteArray,
index: Int,
script: ByteArray,
value: Long,
height: Int
): Boolean
fun getDownloadedUtxoBalance(address: String): CompactBlockProcessor.WalletBalance
// Implemented by `DerivationTool`
interface Derivation {
fun deriveShieldedAddress(viewingKey: String): String
@ -61,7 +80,11 @@ interface RustBackendWelding {
fun deriveSpendingKeys(seed: ByteArray, numberOfAccounts: Int = 1): Array<String>
fun deriveTransparentAddress(seed: ByteArray): String
fun deriveTransparentAddress(seed: ByteArray, account: Int = 0, index: Int = 0): String
fun deriveTransparentAddress(transparentSecretKey: String): String
fun deriveTransparentSecretKey(seed: ByteArray, account: Int = 0, index: Int = 0): String
fun deriveViewingKey(spendingKey: String): String

View File

@ -106,6 +106,18 @@ class LightWalletGrpcService private constructor(
)
}
override fun fetchUtxos(
tAddress: String,
startHeight: Int
): List<Service.GetAddressUtxosReply> {
channel.resetConnectBackoff()
val result = channel.createStub().getAddressUtxos(
Service.GetAddressUtxosArg.newBuilder().setAddress(tAddress)
.setStartHeight(startHeight.toLong()).build()
)
return result.addressUtxosList
}
override fun getTAddressTransactions(
tAddress: String,
blockHeightRange: IntRange

View File

@ -16,6 +16,16 @@ interface LightWalletService {
*/
fun fetchTransaction(txId: ByteArray): Service.RawTransaction?
/**
* Fetch all UTXOs for the given address, going back to the start height.
*
* @param tAddress the transparent address to use.
* @param startHeight the starting height to use.
*
* @return the UTXOs for the given address from the startHeight.
*/
fun fetchUtxos(tAddress: String, startHeight: Int): List<Service.GetAddressUtxosReply>
/**
* Return the given range of blocks.
*

View File

@ -75,8 +75,16 @@ class DerivationTool {
// WIP probably shouldn't be used just yet. Why?
// - because we need the private key associated with this seed and this function doesn't return it.
// - the underlying implementation needs to be split out into a few lower-level calls
override fun deriveTransparentAddress(seed: ByteArray): String = withRustBackendLoaded {
deriveTransparentAddressFromSeed(seed)
override fun deriveTransparentAddress(seed: ByteArray, account: Int, index: Int): String = withRustBackendLoaded {
deriveTransparentAddressFromSeed(seed, account, index)
}
override fun deriveTransparentAddress(transparentSecretKey: String): String = withRustBackendLoaded {
deriveTransparentAddressFromSecretKey(transparentSecretKey)
}
override fun deriveTransparentSecretKey(seed: ByteArray, account: Int, index: Int): String = withRustBackendLoaded {
deriveTransparentSecretKeyFromSeed(seed, account, index)
}
fun validateViewingKey(viewingKey: String) {
@ -122,6 +130,13 @@ class DerivationTool {
private external fun deriveShieldedAddressFromViewingKey(key: String): String
@JvmStatic
private external fun deriveTransparentAddressFromSeed(seed: ByteArray): String
private external fun deriveTransparentAddressFromSeed(seed: ByteArray, account: Int, index: Int): String
@JvmStatic
private external fun deriveTransparentAddressFromSecretKey(tsk: String): String
@JvmStatic
private external fun deriveTransparentSecretKeyFromSeed(seed: ByteArray, account: Int, index: Int): String
}
}

View File

@ -70,10 +70,16 @@ class SaplingParamTool {
twig("directory did not exist attempting to make it")
file.parentFile.mkdirs()
}
Okio.buffer(Okio.sink(file)).use {
twig("writing to $file")
it.writeAll(response.body().source())
}
} else {
failureMessage += "Error while fetching $paramFileName : $response\n"
twig(failureMessage)
}
twig("fetch succeeded, done writing $paramFileName")
}
if (failureMessage.isNotEmpty()) throw TransactionEncoderException.FetchParamsException(
failureMessage

View File

@ -139,6 +139,40 @@ class PersistentTransactionManager(
tx
}
override suspend fun encode(
spendingKey: String,
transparentSecretKey: String,
pendingTx: PendingTransaction
): PendingTransaction {
twig("managing the creation of a shielding transaction")
var tx = pendingTx as PendingTransactionEntity
try {
twig("beginning to encode shielding transaction with : $encoder")
val encodedTx = encoder.createShieldingTransaction(
spendingKey,
transparentSecretKey,
tx.memo
)
twig("successfully encoded shielding transaction!")
safeUpdate("updating shielding transaction encoding") {
updateEncoding(tx.id, encodedTx.raw, encodedTx.txId, encodedTx.expiryHeight)
}
} catch (t: Throwable) {
val message = "failed to encode shielding transaction due to : ${t.message} caused by: ${t.cause}"
twig(message)
safeUpdate("updating shielding transaction error info") {
updateError(tx.id, message, ERROR_ENCODING)
}
} finally {
safeUpdate("incrementing shielding transaction encodeAttempts (from: ${tx.encodeAttempts})") {
updateEncodeAttempts(tx.id, max(1, tx.encodeAttempts + 1))
tx = findById(tx.id)!!
}
}
return tx
}
override suspend fun submit(pendingTx: PendingTransaction): PendingTransaction = withContext(Dispatchers.IO) {
// reload the tx to check for cancellation
var tx = pendingTransactionDao { findById(pendingTx.id) }

View File

@ -24,6 +24,12 @@ interface TransactionEncoder {
fromAccountIndex: Int = 0
): EncodedTransaction
suspend fun createShieldingTransaction(
spendingKey: String,
transparentSecretKey: String,
memo: ByteArray? = byteArrayOf()
): 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

@ -39,6 +39,12 @@ interface OutboundTransactionManager {
*/
suspend fun encode(spendingKey: String, pendingTx: PendingTransaction): PendingTransaction
suspend fun encode(
spendingKey: String,
transparentSecretKey: String,
pendingTx: PendingTransaction
): PendingTransaction
/**
* Submits the transaction represented by [pendingTx] to lightwalletd to broadcast to the
* network and, hopefully, include in the next block.

View File

@ -51,6 +51,17 @@ class WalletTransactionEncoder(
?: throw TransactionEncoderException.TransactionNotFoundException(transactionId)
}
override suspend fun createShieldingTransaction(
spendingKey: String,
transparentSecretKey: String,
memo: ByteArray?
): EncodedTransaction = withContext(IO) {
twig("TMP: createShieldingTransaction with $spendingKey and $transparentSecretKey and ${memo?.size}")
val transactionId = createShieldingSpend(spendingKey, transparentSecretKey, memo)
repository.findEncodedTransactionById(transactionId)
?: throw TransactionEncoderException.TransactionNotFoundException(transactionId)
}
/**
* 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.
@ -126,4 +137,29 @@ class WalletTransactionEncoder(
twig("result of sendToAddress: $result")
}
}
private suspend fun createShieldingSpend(
spendingKey: String,
transparentSecretKey: String,
memo: ByteArray? = byteArrayOf()
): Long = withContext(IO) {
twigTask("creating transaction to shield all UTXOs") {
try {
SaplingParamTool.ensureParams((rustBackend as RustBackend).pathParamsDir)
twig("params exist! attempting to shield...")
rustBackend.shieldToAddress(
spendingKey,
transparentSecretKey,
memo
)
} catch (t: Throwable) {
// TODO: if this error matches: Insufficient balance (have 0, need 1000 including fee)
// then consider custom error that says no UTXOs existed to shield
twig("Shield failed due to: ${t.message}")
throw t
}
}.also { result ->
twig("result of shieldToAddress: $result")
}
}
}

View File

@ -34,7 +34,7 @@ message TxFilter {
// RawTransaction contains the complete transaction data. It also optionally includes
// the block height in which the transaction was included.
message RawTransaction {
bytes data = 1; // exact data returned by zcash 'getrawtransaction'
bytes data = 1; // exact data returned by Zcash 'getrawtransaction'
uint64 height = 2; // height that the transaction was mined (or -1)
}
@ -66,6 +66,9 @@ message LightdInfo {
string branch = 9;
string buildDate = 10;
string buildUser = 11;
uint64 estimatedHeight = 12; // less than tip height if zcashd is syncing
string zcashdBuild = 13; // example: "v4.1.1-877212414"
string zcashdSubversion = 14; // example: "/MagicBean:4.1.1/"
}
// TransparentAddressBlockFilter restricts the results to the given address
@ -104,7 +107,7 @@ message Exclude {
repeated bytes txid = 1;
}
// The TreeState is derived from the zcash z_gettreestate rpc.
// The TreeState is derived from the Zcash z_gettreestate rpc.
message TreeState {
string network = 1; // "main" or "test"
uint64 height = 2;
@ -139,7 +142,7 @@ service CompactTxStreamer {
// Return the requested full (not compact) transaction (as from zcashd)
rpc GetTransaction(TxFilter) returns (RawTransaction) {}
// Submit the given transaction to the zcash network
// Submit the given transaction to the Zcash network
rpc SendTransaction(RawTransaction) returns (SendResponse) {}
// Return the txids corresponding to the given t-address within the given block range
@ -159,7 +162,7 @@ service CompactTxStreamer {
rpc GetMempoolTx(Exclude) returns (stream CompactTx) {}
// GetTreeState returns the note commitment tree state corresponding to the given block.
// See section 3.7 of the zcash protocol specification. It returns several other useful
// See section 3.7 of the Zcash protocol specification. It returns several other useful
// values also (even though they can be obtained using GetBlock).
// The block can be specified by either height or hash.
rpc GetTreeState(BlockID) returns (TreeState) {}