* [#1113] Adopt updated gRPC proto files * [#1114] Add GetSubtreeRoots method
This commit is contained in:
parent
f1b5e3aade
commit
cb85f69311
|
@ -11,6 +11,8 @@ import co.electriccoin.lightwallet.client.model.LightWalletEndpointInfoUnsafe
|
|||
import co.electriccoin.lightwallet.client.model.RawTransactionUnsafe
|
||||
import co.electriccoin.lightwallet.client.model.Response
|
||||
import co.electriccoin.lightwallet.client.model.SendResponseUnsafe
|
||||
import co.electriccoin.lightwallet.client.model.ShieldedProtocolEnum
|
||||
import co.electriccoin.lightwallet.client.model.SubtreeRootUnsafe
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
|
@ -70,6 +72,23 @@ interface LightWalletClient {
|
|||
blockHeightRange: ClosedRange<BlockHeightUnsafe>
|
||||
): Flow<Response<RawTransactionUnsafe>>
|
||||
|
||||
/**
|
||||
* Returns a stream of information about roots of subtrees of the Sapling and Orchard note commitment trees.
|
||||
*
|
||||
* @return a flow of information about roots of subtrees of the Sapling and Orchard note commitment trees.
|
||||
*
|
||||
* @param startIndex Index identifying where to start returning subtree roots
|
||||
* @param shieldedProtocol Shielded protocol to return subtree roots for. See `ShieldedProtocolEnum` enum class.
|
||||
* @param maxEntries Maximum number of entries to return, or 0 for all entries
|
||||
*
|
||||
* @throws IllegalArgumentException when empty argument provided
|
||||
*/
|
||||
fun getSubtreeRoots(
|
||||
startIndex: Int,
|
||||
shieldedProtocol: ShieldedProtocolEnum,
|
||||
maxEntries: Int
|
||||
): Flow<Response<SubtreeRootUnsafe>>
|
||||
|
||||
/**
|
||||
* Reconnect to the same or a different server. This is useful when the connection is
|
||||
* unrecoverable. That might be time to switch to a mirror or just reconnect.
|
||||
|
|
|
@ -2,5 +2,7 @@ package co.electriccoin.lightwallet.client.internal
|
|||
|
||||
internal object Constants {
|
||||
const val LOG_TAG = "LightWalletClient" // NON-NLS
|
||||
const val ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE = "Illegal argument provided - can't be empty:" // NON-NLS
|
||||
const val ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_COMMON = "Illegal argument provided:" // NON-NLS
|
||||
const val ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_EMPTY = "$ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_COMMON " +
|
||||
"can't be empty:" // NON-NLS
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ import co.electriccoin.lightwallet.client.model.LightWalletEndpointInfoUnsafe
|
|||
import co.electriccoin.lightwallet.client.model.RawTransactionUnsafe
|
||||
import co.electriccoin.lightwallet.client.model.Response
|
||||
import co.electriccoin.lightwallet.client.model.SendResponseUnsafe
|
||||
import co.electriccoin.lightwallet.client.model.ShieldedProtocolEnum
|
||||
import co.electriccoin.lightwallet.client.model.SubtreeRootUnsafe
|
||||
import com.google.protobuf.ByteString
|
||||
import io.grpc.CallOptions
|
||||
import io.grpc.Channel
|
||||
|
@ -48,7 +50,7 @@ internal class LightWalletClientImpl private constructor(
|
|||
|
||||
override fun getBlockRange(heightRange: ClosedRange<BlockHeightUnsafe>): Flow<Response<CompactBlockUnsafe>> {
|
||||
require(!heightRange.isEmpty()) {
|
||||
"${Constants.ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE} range: $heightRange." // NON-NLS
|
||||
"${Constants.ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_EMPTY} range: $heightRange." // NON-NLS
|
||||
}
|
||||
|
||||
return try {
|
||||
|
@ -101,8 +103,8 @@ internal class LightWalletClientImpl private constructor(
|
|||
|
||||
override suspend fun submitTransaction(spendTransaction: ByteArray): Response<SendResponseUnsafe> {
|
||||
require(spendTransaction.isNotEmpty()) {
|
||||
"${Constants.ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE} Failed to submit transaction because it was empty, so " +
|
||||
"this request was ignored on the client-side." // NON-NLS
|
||||
"${Constants.ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_EMPTY} Failed to submit transaction because it was empty," +
|
||||
" so this request was ignored on the client-side." // NON-NLS
|
||||
}
|
||||
|
||||
val request = Service.RawTransaction.newBuilder()
|
||||
|
@ -122,8 +124,8 @@ internal class LightWalletClientImpl private constructor(
|
|||
|
||||
override suspend fun fetchTransaction(txId: ByteArray): Response<RawTransactionUnsafe> {
|
||||
require(txId.isNotEmpty()) {
|
||||
"${Constants.ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE} Failed to start fetching the transaction with null " +
|
||||
"transaction ID, so this request was ignored on the client-side." // NON-NLS
|
||||
"${Constants.ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_EMPTY} Failed to start fetching the transaction with" +
|
||||
" null transaction ID, so this request was ignored on the client-side." // NON-NLS
|
||||
}
|
||||
|
||||
val request = Service.TxFilter.newBuilder().setHash(ByteString.copyFrom(txId)).build()
|
||||
|
@ -144,7 +146,7 @@ internal class LightWalletClientImpl private constructor(
|
|||
startHeight: BlockHeightUnsafe
|
||||
): Flow<Response<GetAddressUtxosReplyUnsafe>> {
|
||||
require(tAddresses.isNotEmpty() && tAddresses.all { it.isNotBlank() }) {
|
||||
"${Constants.ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE} array of addresses contains invalid item." // NON-NLS
|
||||
"${Constants.ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_EMPTY} array of addresses contains invalid item." // NON-NLS
|
||||
}
|
||||
|
||||
val getUtxosBuilder = Service.GetAddressUtxosArg.newBuilder()
|
||||
|
@ -176,7 +178,8 @@ internal class LightWalletClientImpl private constructor(
|
|||
blockHeightRange: ClosedRange<BlockHeightUnsafe>
|
||||
): Flow<Response<RawTransactionUnsafe>> {
|
||||
require(!blockHeightRange.isEmpty() && tAddress.isNotBlank()) {
|
||||
"${Constants.ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE} range: $blockHeightRange, address: $tAddress." // NON-NLS
|
||||
"${Constants.ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_EMPTY} range: $blockHeightRange, address: " +
|
||||
"$tAddress." // NON-NLS
|
||||
}
|
||||
|
||||
val request = Service.TransparentAddressBlockFilter.newBuilder()
|
||||
|
@ -200,6 +203,37 @@ internal class LightWalletClientImpl private constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getSubtreeRoots(
|
||||
startIndex: Int,
|
||||
shieldedProtocol: ShieldedProtocolEnum,
|
||||
maxEntries: Int
|
||||
): Flow<Response<SubtreeRootUnsafe>> {
|
||||
require(startIndex >= 0 && maxEntries >= 0) {
|
||||
"${Constants.ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_COMMON} startIndex: $startIndex, maxEntries: $maxEntries."
|
||||
}
|
||||
|
||||
val getSubtreeRootsArgBuilder = Service.GetSubtreeRootsArg.newBuilder()
|
||||
getSubtreeRootsArgBuilder.startIndex = startIndex
|
||||
getSubtreeRootsArgBuilder.shieldedProtocol = shieldedProtocol.toProtocol()
|
||||
getSubtreeRootsArgBuilder.maxEntries = maxEntries
|
||||
|
||||
val request = getSubtreeRootsArgBuilder.build()
|
||||
|
||||
return try {
|
||||
requireChannel().createStub(streamingRequestTimeout)
|
||||
.getSubtreeRoots(request)
|
||||
.map {
|
||||
val response: Response<SubtreeRootUnsafe> = Response.Success(SubtreeRootUnsafe.new(it))
|
||||
response
|
||||
}.catch {
|
||||
val failure: Response.Failure<SubtreeRootUnsafe> = GrpcStatusResolver.resolveFailureFromStatus(it)
|
||||
emit(failure)
|
||||
}
|
||||
} catch (e: StatusException) {
|
||||
flowOf(GrpcStatusResolver.resolveFailureFromStatus(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun shutdown() {
|
||||
channel.shutdown()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package co.electriccoin.lightwallet.client.model
|
||||
|
||||
import cash.z.wallet.sdk.internal.rpc.Service.ShieldedProtocol
|
||||
|
||||
enum class ShieldedProtocolEnum {
|
||||
SAPLING,
|
||||
ORCHARD;
|
||||
|
||||
fun toProtocol() = when (this) {
|
||||
SAPLING -> ShieldedProtocol.sapling
|
||||
ORCHARD -> ShieldedProtocol.orchard
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package co.electriccoin.lightwallet.client.model
|
||||
|
||||
import cash.z.wallet.sdk.internal.rpc.Service.SubtreeRoot
|
||||
|
||||
/**
|
||||
* SubtreeRoot contains information about roots of subtrees of the Sapling and Orchard note commitment trees, which
|
||||
* has come from the Light Wallet server.
|
||||
*
|
||||
* It is marked as "unsafe" because it is not guaranteed to be valid.
|
||||
*
|
||||
* @param rootHash The 32-byte Merkle root of the subtree
|
||||
* @param completingBlockHash The hash of the block that completed this subtree.
|
||||
* @param completingBlockHeight The height of the block that completed this subtree in the main chain.
|
||||
*/
|
||||
class SubtreeRootUnsafe(
|
||||
val rootHash: ByteArray,
|
||||
val completingBlockHash: ByteArray,
|
||||
val completingBlockHeight: BlockHeightUnsafe
|
||||
) {
|
||||
companion object {
|
||||
fun new(subtreeRoot: SubtreeRoot) = SubtreeRootUnsafe(
|
||||
rootHash = subtreeRoot.rootHash.toByteArray(),
|
||||
completingBlockHash = subtreeRoot.completingBlockHash.toByteArray(),
|
||||
completingBlockHeight = BlockHeightUnsafe(subtreeRoot.completingBlockHeight),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2019-2020 The Zcash developers
|
||||
// Copyright (c) 2019-2021 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
|
@ -7,61 +7,75 @@ package cash.z.wallet.sdk.rpc;
|
|||
option java_package = "cash.z.wallet.sdk.internal.rpc";
|
||||
option go_package = "lightwalletd/walletrpc";
|
||||
option swift_prefix = "";
|
||||
|
||||
// Remember that proto3 fields are all optional. A field that is not present will be set to its zero value.
|
||||
// bytes fields of hashes are in canonical little-endian format.
|
||||
|
||||
// ChainMetadata represents information about the state of the chain as of a given block.
|
||||
message ChainMetadata {
|
||||
uint32 saplingCommitmentTreeSize = 1; // the size of the Sapling note commitment tree as of the end of this block
|
||||
uint32 orchardCommitmentTreeSize = 2; // the size of the Orchard note commitment tree as of the end of this block
|
||||
}
|
||||
|
||||
// CompactBlock is a packaging of ONLY the data from a block that's needed to:
|
||||
// 1. Detect a payment to your shielded Sapling address
|
||||
// 2. Detect a spend of your shielded Sapling notes
|
||||
// 3. Update your witnesses to generate new Sapling spend proofs.
|
||||
message CompactBlock {
|
||||
uint32 protoVersion = 1; // the version of this wire format, for storage
|
||||
uint64 height = 2; // the height of this block
|
||||
bytes hash = 3; // the ID (hash) of this block, same as in block explorers
|
||||
bytes prevHash = 4; // the ID (hash) of this block's predecessor
|
||||
uint32 time = 5; // Unix epoch time when the block was mined
|
||||
bytes header = 6; // (hash, prevHash, and time) OR (full header)
|
||||
repeated CompactTx vtx = 7; // zero or more compact transactions from this block
|
||||
uint32 protoVersion = 1; // the version of this wire format, for storage
|
||||
uint64 height = 2; // the height of this block
|
||||
bytes hash = 3; // the ID (hash) of this block, same as in block explorers
|
||||
bytes prevHash = 4; // the ID (hash) of this block's predecessor
|
||||
uint32 time = 5; // Unix epoch time when the block was mined
|
||||
bytes header = 6; // (hash, prevHash, and time) OR (full header)
|
||||
repeated CompactTx vtx = 7; // zero or more compact transactions from this block
|
||||
ChainMetadata chainMetadata = 8; // information about the state of the chain as of this block
|
||||
}
|
||||
|
||||
// CompactTx contains the minimum information for a wallet to know if this transaction
|
||||
// is relevant to it (either pays to it or spends from it) via shielded elements
|
||||
// only. This message will not encode a transparent-to-transparent transaction.
|
||||
message CompactTx {
|
||||
uint64 index = 1; // the index within the full block
|
||||
bytes hash = 2; // the ID (hash) of this transaction, same as in block explorers
|
||||
// Index and hash will allow the receiver to call out to chain
|
||||
// explorers or other data structures to retrieve more information
|
||||
// about this transaction.
|
||||
uint64 index = 1; // the index within the full block
|
||||
bytes hash = 2; // the ID (hash) of this transaction, same as in block explorers
|
||||
|
||||
// The transaction fee: present if server can provide. In the case of a
|
||||
// stateless server and a transaction with transparent inputs, this will be
|
||||
// unset because the calculation requires reference to prior transactions.
|
||||
// in a pure-Sapling context, the fee will be calculable as:
|
||||
// valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut))
|
||||
uint32 fee = 3;
|
||||
// The transaction fee: present if server can provide. In the case of a
|
||||
// stateless server and a transaction with transparent inputs, this will be
|
||||
// unset because the calculation requires reference to prior transactions.
|
||||
// If there are no transparent inputs, the fee will be calculable as:
|
||||
// valueBalanceSapling + valueBalanceOrchard + sum(vPubNew) - sum(vPubOld) - sum(tOut)
|
||||
uint32 fee = 3;
|
||||
|
||||
repeated CompactSaplingSpend spends = 4; // inputs
|
||||
repeated CompactSaplingOutput outputs = 5; // outputs
|
||||
repeated CompactOrchardAction actions = 6;
|
||||
repeated CompactSaplingSpend spends = 4;
|
||||
repeated CompactSaplingOutput outputs = 5;
|
||||
repeated CompactOrchardAction actions = 6;
|
||||
}
|
||||
|
||||
// CompactSaplingSpend is a Sapling Spend Description as described in 7.3 of the Zcash
|
||||
// protocol specification.
|
||||
message CompactSaplingSpend {
|
||||
bytes nf = 1; // nullifier (see the Zcash protocol specification)
|
||||
bytes nf = 1; // nullifier (see the Zcash protocol specification)
|
||||
}
|
||||
|
||||
// output is a Sapling Output Description as described in section 7.4 of the
|
||||
// Zcash protocol spec. Total size is 948.
|
||||
// output encodes the `cmu` field, `ephemeralKey` field, and a 52-byte prefix of the
|
||||
// `encCiphertext` field of a Sapling Output Description. These fields are described in
|
||||
// section 7.4 of the Zcash protocol spec:
|
||||
// https://zips.z.cash/protocol/protocol.pdf#outputencodingandconsensus
|
||||
// Total size is 116 bytes.
|
||||
message CompactSaplingOutput {
|
||||
bytes cmu = 1; // note commitment u-coordinate
|
||||
bytes epk = 2; // ephemeral public key
|
||||
bytes ciphertext = 3; // first 52 bytes of ciphertext
|
||||
bytes cmu = 1; // note commitment u-coordinate
|
||||
bytes ephemeralKey = 2; // ephemeral public key
|
||||
bytes ciphertext = 3; // first 52 bytes of ciphertext
|
||||
}
|
||||
|
||||
// https://github.com/zcash/zips/blob/main/zip-0225.rst#orchard-action-description-orchardaction
|
||||
// (but not all fields are needed)
|
||||
message CompactOrchardAction {
|
||||
bytes nullifier = 1; // [32] The nullifier of the input note
|
||||
bytes cmx = 2; // [32] The x-coordinate of the note commitment for the output note
|
||||
bytes ephemeralKey = 3; // [32] An encoding of an ephemeral Pallas public key
|
||||
bytes ciphertext = 4; // [52] The note plaintext component of the encCiphertext field
|
||||
}
|
||||
bytes nullifier = 1; // [32] The nullifier of the input note
|
||||
bytes cmx = 2; // [32] The x-coordinate of the note commitment for the output note
|
||||
bytes ephemeralKey = 3; // [32] An encoding of an ephemeral Pallas public key
|
||||
bytes ciphertext = 4; // [52] The first 52 bytes of the encCiphertext field
|
||||
}
|
||||
|
|
|
@ -131,4 +131,4 @@ service DarksideStreamer {
|
|||
|
||||
// Clear the list of GetTreeStates entries (can't fail)
|
||||
rpc ClearAllTreeStates(Empty) returns (Empty) {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ import "compact_formats.proto";
|
|||
// A BlockID message contains identifiers to select a block: a height or a
|
||||
// hash. Specification by hash is not implemented, but may be in the future.
|
||||
message BlockID {
|
||||
uint64 height = 1;
|
||||
bytes hash = 2;
|
||||
uint64 height = 1;
|
||||
bytes hash = 2;
|
||||
}
|
||||
|
||||
// BlockRange specifies a series of blocks from start to end inclusive.
|
||||
|
@ -27,12 +27,12 @@ message BlockRange {
|
|||
// transaction: either a block and an index, or a direct transaction hash.
|
||||
// Currently, only specification by hash is supported.
|
||||
message TxFilter {
|
||||
BlockID block = 1; // block identifier, height or hash
|
||||
uint64 index = 2; // index within the block
|
||||
bytes hash = 3; // transaction ID (hash, txid)
|
||||
BlockID block = 1; // block identifier, height or hash
|
||||
uint64 index = 2; // index within the block
|
||||
bytes hash = 3; // transaction ID (hash, txid)
|
||||
}
|
||||
|
||||
// RawTransaction contains the complete transaction data. It also optionally includes
|
||||
// RawTransaction contains the complete transaction data. It also optionally includes
|
||||
// the block height in which the transaction was included, or, when returned
|
||||
// by GetMempoolStream(), the latest block height.
|
||||
message RawTransaction {
|
||||
|
@ -119,6 +119,22 @@ message TreeState {
|
|||
string orchardTree = 6; // orchard commitment tree state
|
||||
}
|
||||
|
||||
enum ShieldedProtocol {
|
||||
sapling = 0;
|
||||
orchard = 1;
|
||||
}
|
||||
|
||||
message GetSubtreeRootsArg {
|
||||
uint32 startIndex = 1; // Index identifying where to start returning subtree roots
|
||||
ShieldedProtocol shieldedProtocol = 2; // Shielded protocol to return subtree roots for
|
||||
uint32 maxEntries = 3; // Maximum number of entries to return, or 0 for all entries.
|
||||
}
|
||||
message SubtreeRoot {
|
||||
bytes rootHash = 2; // The 32-byte Merkle root of the subtree.
|
||||
bytes completingBlockHash = 3; // The hash of the block that completed this subtree.
|
||||
uint64 completingBlockHeight = 4; // The height of the block that completed this subtree in the main chain.
|
||||
}
|
||||
|
||||
// Results are sorted by height, which makes it easy to issue another
|
||||
// request that picks up from where the previous left off.
|
||||
message GetAddressUtxosArg {
|
||||
|
@ -143,8 +159,12 @@ service CompactTxStreamer {
|
|||
rpc GetLatestBlock(ChainSpec) returns (BlockID) {}
|
||||
// Return the compact block corresponding to the given block identifier
|
||||
rpc GetBlock(BlockID) returns (CompactBlock) {}
|
||||
// Same as GetBlock except actions contain only nullifiers
|
||||
rpc GetBlockNullifiers(BlockID) returns (CompactBlock) {}
|
||||
// Return a list of consecutive compact blocks
|
||||
rpc GetBlockRange(BlockRange) returns (stream CompactBlock) {}
|
||||
// Same as GetBlockRange except actions contain only nullifiers
|
||||
rpc GetBlockRangeNullifiers(BlockRange) returns (stream CompactBlock) {}
|
||||
|
||||
// Return the requested full (not compact) transaction (as from zcashd)
|
||||
rpc GetTransaction(TxFilter) returns (RawTransaction) {}
|
||||
|
@ -178,6 +198,10 @@ service CompactTxStreamer {
|
|||
rpc GetTreeState(BlockID) returns (TreeState) {}
|
||||
rpc GetLatestTreeState(Empty) returns (TreeState) {}
|
||||
|
||||
// Returns a stream of information about roots of subtrees of the Sapling and Orchard
|
||||
// note commitment trees.
|
||||
rpc GetSubtreeRoots(GetSubtreeRootsArg) returns (stream SubtreeRoot) {}
|
||||
|
||||
rpc GetAddressUtxos(GetAddressUtxosArg) returns (GetAddressUtxosReplyList) {}
|
||||
rpc GetAddressUtxosStream(GetAddressUtxosArg) returns (stream GetAddressUtxosReply) {}
|
||||
|
||||
|
@ -185,4 +209,4 @@ service CompactTxStreamer {
|
|||
rpc GetLightdInfo(Empty) returns (LightdInfo) {}
|
||||
// Testing-only, requires lightwalletd --ping-very-insecure (do not enable in production)
|
||||
rpc Ping(Duration) returns (PingResponse) {}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue