From 1908d70f7faa6063de97095c22f1c1470831a767 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Thu, 4 Jan 2024 18:58:03 -0300 Subject: [PATCH] [#1341] Add Support for GetSubTreeRoots in Advanced Re Org tests closes #1341 Updates Proto files and compile new swift versions Adds setSubTree darkside service adds subtrees into buildFakeChain() methods --- .../Service/GRPC/ProtoBuf/proto/service.proto | 9 +- .../Service/GRPC/ProtoBuf/service.grpc.swift | 822 +++++++++++++++++- .../Service/GRPC/ProtoBuf/service.pb.swift | 4 +- Tests/TestUtils/DarkSideWalletService.swift | 4 + Tests/TestUtils/FakeChainBuilder.swift | 86 +- Tests/TestUtils/proto/darkside.grpc.swift | 103 +++ Tests/TestUtils/proto/darkside.pb.swift | 63 +- Tests/TestUtils/proto/darkside.proto | 10 + 8 files changed, 1064 insertions(+), 37 deletions(-) diff --git a/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/proto/service.proto b/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/proto/service.proto index c15a3ec9..a94e639f 100644 --- a/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/proto/service.proto +++ b/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/proto/service.proto @@ -31,7 +31,7 @@ message TxFilter { 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 { @@ -170,7 +170,8 @@ service CompactTxStreamer { // 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 + // Return the transactions corresponding to the given t-address within the given block range + // NB - this method is misnamed, it returns transactions, not transaction IDs. rpc GetTaddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {} rpc GetTaddressBalance(AddressList) returns (Balance) {} rpc GetTaddressBalanceStream(stream Address) returns (Balance) {} @@ -197,8 +198,8 @@ 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. + // Returns a stream of information about roots of subtrees of the note commitment tree + // for the specified shielded protocol (Sapling or Orchard). rpc GetSubtreeRoots(GetSubtreeRootsArg) returns (stream SubtreeRoot) {} rpc GetAddressUtxos(GetAddressUtxosArg) returns (GetAddressUtxosReplyList) {} diff --git a/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.grpc.swift b/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.grpc.swift index 1e18c03e..4630a31e 100644 --- a/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.grpc.swift +++ b/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.grpc.swift @@ -255,7 +255,8 @@ extension CompactTxStreamerClientProtocol { ) } - /// Return the txids corresponding to the given t-address within the given block range + /// Return the transactions corresponding to the given t-address within the given block range + /// NB - this method is misnamed, it returns transactions, not transaction IDs. /// /// - Parameters: /// - request: Request to send to GetTaddressTxids. @@ -402,8 +403,8 @@ extension CompactTxStreamerClientProtocol { ) } - /// Returns a stream of information about roots of subtrees of the Sapling and Orchard - /// note commitment trees. + /// Returns a stream of information about roots of subtrees of the note commitment tree + /// for the specified shielded protocol (Sapling or Orchard). /// /// - Parameters: /// - request: Request to send to GetSubtreeRoots. @@ -1358,3 +1359,818 @@ internal enum CompactTxStreamerClientMetadata { } } +/// To build a server, implement a class that conforms to this protocol. +internal protocol CompactTxStreamerProvider: CallHandlerProvider { + var interceptors: CompactTxStreamerServerInterceptorFactoryProtocol? { get } + + /// Return the height of the tip of the best chain + func getLatestBlock(request: ChainSpec, context: StatusOnlyCallContext) -> EventLoopFuture + + /// Return the compact block corresponding to the given block identifier + func getBlock(request: BlockID, context: StatusOnlyCallContext) -> EventLoopFuture + + /// Same as GetBlock except actions contain only nullifiers + func getBlockNullifiers(request: BlockID, context: StatusOnlyCallContext) -> EventLoopFuture + + /// Return a list of consecutive compact blocks + func getBlockRange(request: BlockRange, context: StreamingResponseCallContext) -> EventLoopFuture + + /// Same as GetBlockRange except actions contain only nullifiers + func getBlockRangeNullifiers(request: BlockRange, context: StreamingResponseCallContext) -> EventLoopFuture + + /// Return the requested full (not compact) transaction (as from zcashd) + func getTransaction(request: TxFilter, context: StatusOnlyCallContext) -> EventLoopFuture + + /// Submit the given transaction to the Zcash network + func sendTransaction(request: RawTransaction, context: StatusOnlyCallContext) -> EventLoopFuture + + /// Return the transactions corresponding to the given t-address within the given block range + /// NB - this method is misnamed, it returns transactions, not transaction IDs. + func getTaddressTxids(request: TransparentAddressBlockFilter, context: StreamingResponseCallContext) -> EventLoopFuture + + func getTaddressBalance(request: AddressList, context: StatusOnlyCallContext) -> EventLoopFuture + + func getTaddressBalanceStream(context: UnaryResponseCallContext) -> EventLoopFuture<(StreamEvent
) -> Void> + + /// Return the compact transactions currently in the mempool; the results + /// can be a few seconds out of date. If the Exclude list is empty, return + /// all transactions; otherwise return all *except* those in the Exclude list + /// (if any); this allows the client to avoid receiving transactions that it + /// already has (from an earlier call to this rpc). The transaction IDs in the + /// Exclude list can be shortened to any number of bytes to make the request + /// more bandwidth-efficient; if two or more transactions in the mempool + /// match a shortened txid, they are all sent (none is excluded). Transactions + /// in the exclude list that don't exist in the mempool are ignored. + func getMempoolTx(request: Exclude, context: StreamingResponseCallContext) -> EventLoopFuture + + /// Return a stream of current Mempool transactions. This will keep the output stream open while + /// there are mempool transactions. It will close the returned stream when a new block is mined. + func getMempoolStream(request: Empty, context: StreamingResponseCallContext) -> EventLoopFuture + + /// 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 + /// values also (even though they can be obtained using GetBlock). + /// The block can be specified by either height or hash. + func getTreeState(request: BlockID, context: StatusOnlyCallContext) -> EventLoopFuture + + func getLatestTreeState(request: Empty, context: StatusOnlyCallContext) -> EventLoopFuture + + /// Returns a stream of information about roots of subtrees of the note commitment tree + /// for the specified shielded protocol (Sapling or Orchard). + func getSubtreeRoots(request: GetSubtreeRootsArg, context: StreamingResponseCallContext) -> EventLoopFuture + + func getAddressUtxos(request: GetAddressUtxosArg, context: StatusOnlyCallContext) -> EventLoopFuture + + func getAddressUtxosStream(request: GetAddressUtxosArg, context: StreamingResponseCallContext) -> EventLoopFuture + + /// Return information about this lightwalletd instance and the blockchain + func getLightdInfo(request: Empty, context: StatusOnlyCallContext) -> EventLoopFuture + + /// Testing-only, requires lightwalletd --ping-very-insecure (do not enable in production) + func ping(request: Duration, context: StatusOnlyCallContext) -> EventLoopFuture +} + +extension CompactTxStreamerProvider { + internal var serviceName: Substring { + return CompactTxStreamerServerMetadata.serviceDescriptor.fullName[...] + } + + /// Determines, calls and returns the appropriate request handler, depending on the request's method. + /// Returns nil for methods not handled by this service. + internal func handle( + method name: Substring, + context: CallHandlerContext + ) -> GRPCServerHandlerProtocol? { + switch name { + case "GetLatestBlock": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetLatestBlockInterceptors() ?? [], + userFunction: self.getLatestBlock(request:context:) + ) + + case "GetBlock": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetBlockInterceptors() ?? [], + userFunction: self.getBlock(request:context:) + ) + + case "GetBlockNullifiers": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetBlockNullifiersInterceptors() ?? [], + userFunction: self.getBlockNullifiers(request:context:) + ) + + case "GetBlockRange": + return ServerStreamingServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetBlockRangeInterceptors() ?? [], + userFunction: self.getBlockRange(request:context:) + ) + + case "GetBlockRangeNullifiers": + return ServerStreamingServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetBlockRangeNullifiersInterceptors() ?? [], + userFunction: self.getBlockRangeNullifiers(request:context:) + ) + + case "GetTransaction": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetTransactionInterceptors() ?? [], + userFunction: self.getTransaction(request:context:) + ) + + case "SendTransaction": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeSendTransactionInterceptors() ?? [], + userFunction: self.sendTransaction(request:context:) + ) + + case "GetTaddressTxids": + return ServerStreamingServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetTaddressTxidsInterceptors() ?? [], + userFunction: self.getTaddressTxids(request:context:) + ) + + case "GetTaddressBalance": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetTaddressBalanceInterceptors() ?? [], + userFunction: self.getTaddressBalance(request:context:) + ) + + case "GetTaddressBalanceStream": + return ClientStreamingServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer
(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetTaddressBalanceStreamInterceptors() ?? [], + observerFactory: self.getTaddressBalanceStream(context:) + ) + + case "GetMempoolTx": + return ServerStreamingServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetMempoolTxInterceptors() ?? [], + userFunction: self.getMempoolTx(request:context:) + ) + + case "GetMempoolStream": + return ServerStreamingServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetMempoolStreamInterceptors() ?? [], + userFunction: self.getMempoolStream(request:context:) + ) + + case "GetTreeState": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetTreeStateInterceptors() ?? [], + userFunction: self.getTreeState(request:context:) + ) + + case "GetLatestTreeState": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetLatestTreeStateInterceptors() ?? [], + userFunction: self.getLatestTreeState(request:context:) + ) + + case "GetSubtreeRoots": + return ServerStreamingServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetSubtreeRootsInterceptors() ?? [], + userFunction: self.getSubtreeRoots(request:context:) + ) + + case "GetAddressUtxos": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetAddressUtxosInterceptors() ?? [], + userFunction: self.getAddressUtxos(request:context:) + ) + + case "GetAddressUtxosStream": + return ServerStreamingServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetAddressUtxosStreamInterceptors() ?? [], + userFunction: self.getAddressUtxosStream(request:context:) + ) + + case "GetLightdInfo": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetLightdInfoInterceptors() ?? [], + userFunction: self.getLightdInfo(request:context:) + ) + + case "Ping": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makePingInterceptors() ?? [], + userFunction: self.ping(request:context:) + ) + + default: + return nil + } + } +} + +/// To implement a server, implement an object which conforms to this protocol. +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +internal protocol CompactTxStreamerAsyncProvider: CallHandlerProvider, Sendable { + static var serviceDescriptor: GRPCServiceDescriptor { get } + var interceptors: CompactTxStreamerServerInterceptorFactoryProtocol? { get } + + /// Return the height of the tip of the best chain + func getLatestBlock( + request: ChainSpec, + context: GRPCAsyncServerCallContext + ) async throws -> BlockID + + /// Return the compact block corresponding to the given block identifier + func getBlock( + request: BlockID, + context: GRPCAsyncServerCallContext + ) async throws -> CompactBlock + + /// Same as GetBlock except actions contain only nullifiers + func getBlockNullifiers( + request: BlockID, + context: GRPCAsyncServerCallContext + ) async throws -> CompactBlock + + /// Return a list of consecutive compact blocks + func getBlockRange( + request: BlockRange, + responseStream: GRPCAsyncResponseStreamWriter, + context: GRPCAsyncServerCallContext + ) async throws + + /// Same as GetBlockRange except actions contain only nullifiers + func getBlockRangeNullifiers( + request: BlockRange, + responseStream: GRPCAsyncResponseStreamWriter, + context: GRPCAsyncServerCallContext + ) async throws + + /// Return the requested full (not compact) transaction (as from zcashd) + func getTransaction( + request: TxFilter, + context: GRPCAsyncServerCallContext + ) async throws -> RawTransaction + + /// Submit the given transaction to the Zcash network + func sendTransaction( + request: RawTransaction, + context: GRPCAsyncServerCallContext + ) async throws -> SendResponse + + /// Return the transactions corresponding to the given t-address within the given block range + /// NB - this method is misnamed, it returns transactions, not transaction IDs. + func getTaddressTxids( + request: TransparentAddressBlockFilter, + responseStream: GRPCAsyncResponseStreamWriter, + context: GRPCAsyncServerCallContext + ) async throws + + func getTaddressBalance( + request: AddressList, + context: GRPCAsyncServerCallContext + ) async throws -> Balance + + func getTaddressBalanceStream( + requestStream: GRPCAsyncRequestStream
, + context: GRPCAsyncServerCallContext + ) async throws -> Balance + + /// Return the compact transactions currently in the mempool; the results + /// can be a few seconds out of date. If the Exclude list is empty, return + /// all transactions; otherwise return all *except* those in the Exclude list + /// (if any); this allows the client to avoid receiving transactions that it + /// already has (from an earlier call to this rpc). The transaction IDs in the + /// Exclude list can be shortened to any number of bytes to make the request + /// more bandwidth-efficient; if two or more transactions in the mempool + /// match a shortened txid, they are all sent (none is excluded). Transactions + /// in the exclude list that don't exist in the mempool are ignored. + func getMempoolTx( + request: Exclude, + responseStream: GRPCAsyncResponseStreamWriter, + context: GRPCAsyncServerCallContext + ) async throws + + /// Return a stream of current Mempool transactions. This will keep the output stream open while + /// there are mempool transactions. It will close the returned stream when a new block is mined. + func getMempoolStream( + request: Empty, + responseStream: GRPCAsyncResponseStreamWriter, + context: GRPCAsyncServerCallContext + ) async throws + + /// 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 + /// values also (even though they can be obtained using GetBlock). + /// The block can be specified by either height or hash. + func getTreeState( + request: BlockID, + context: GRPCAsyncServerCallContext + ) async throws -> TreeState + + func getLatestTreeState( + request: Empty, + context: GRPCAsyncServerCallContext + ) async throws -> TreeState + + /// Returns a stream of information about roots of subtrees of the note commitment tree + /// for the specified shielded protocol (Sapling or Orchard). + func getSubtreeRoots( + request: GetSubtreeRootsArg, + responseStream: GRPCAsyncResponseStreamWriter, + context: GRPCAsyncServerCallContext + ) async throws + + func getAddressUtxos( + request: GetAddressUtxosArg, + context: GRPCAsyncServerCallContext + ) async throws -> GetAddressUtxosReplyList + + func getAddressUtxosStream( + request: GetAddressUtxosArg, + responseStream: GRPCAsyncResponseStreamWriter, + context: GRPCAsyncServerCallContext + ) async throws + + /// Return information about this lightwalletd instance and the blockchain + func getLightdInfo( + request: Empty, + context: GRPCAsyncServerCallContext + ) async throws -> LightdInfo + + /// Testing-only, requires lightwalletd --ping-very-insecure (do not enable in production) + func ping( + request: Duration, + context: GRPCAsyncServerCallContext + ) async throws -> PingResponse +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension CompactTxStreamerAsyncProvider { + internal static var serviceDescriptor: GRPCServiceDescriptor { + return CompactTxStreamerServerMetadata.serviceDescriptor + } + + internal var serviceName: Substring { + return CompactTxStreamerServerMetadata.serviceDescriptor.fullName[...] + } + + internal var interceptors: CompactTxStreamerServerInterceptorFactoryProtocol? { + return nil + } + + internal func handle( + method name: Substring, + context: CallHandlerContext + ) -> GRPCServerHandlerProtocol? { + switch name { + case "GetLatestBlock": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetLatestBlockInterceptors() ?? [], + wrapping: { try await self.getLatestBlock(request: $0, context: $1) } + ) + + case "GetBlock": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetBlockInterceptors() ?? [], + wrapping: { try await self.getBlock(request: $0, context: $1) } + ) + + case "GetBlockNullifiers": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetBlockNullifiersInterceptors() ?? [], + wrapping: { try await self.getBlockNullifiers(request: $0, context: $1) } + ) + + case "GetBlockRange": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetBlockRangeInterceptors() ?? [], + wrapping: { try await self.getBlockRange(request: $0, responseStream: $1, context: $2) } + ) + + case "GetBlockRangeNullifiers": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetBlockRangeNullifiersInterceptors() ?? [], + wrapping: { try await self.getBlockRangeNullifiers(request: $0, responseStream: $1, context: $2) } + ) + + case "GetTransaction": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetTransactionInterceptors() ?? [], + wrapping: { try await self.getTransaction(request: $0, context: $1) } + ) + + case "SendTransaction": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeSendTransactionInterceptors() ?? [], + wrapping: { try await self.sendTransaction(request: $0, context: $1) } + ) + + case "GetTaddressTxids": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetTaddressTxidsInterceptors() ?? [], + wrapping: { try await self.getTaddressTxids(request: $0, responseStream: $1, context: $2) } + ) + + case "GetTaddressBalance": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetTaddressBalanceInterceptors() ?? [], + wrapping: { try await self.getTaddressBalance(request: $0, context: $1) } + ) + + case "GetTaddressBalanceStream": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer
(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetTaddressBalanceStreamInterceptors() ?? [], + wrapping: { try await self.getTaddressBalanceStream(requestStream: $0, context: $1) } + ) + + case "GetMempoolTx": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetMempoolTxInterceptors() ?? [], + wrapping: { try await self.getMempoolTx(request: $0, responseStream: $1, context: $2) } + ) + + case "GetMempoolStream": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetMempoolStreamInterceptors() ?? [], + wrapping: { try await self.getMempoolStream(request: $0, responseStream: $1, context: $2) } + ) + + case "GetTreeState": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetTreeStateInterceptors() ?? [], + wrapping: { try await self.getTreeState(request: $0, context: $1) } + ) + + case "GetLatestTreeState": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetLatestTreeStateInterceptors() ?? [], + wrapping: { try await self.getLatestTreeState(request: $0, context: $1) } + ) + + case "GetSubtreeRoots": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetSubtreeRootsInterceptors() ?? [], + wrapping: { try await self.getSubtreeRoots(request: $0, responseStream: $1, context: $2) } + ) + + case "GetAddressUtxos": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetAddressUtxosInterceptors() ?? [], + wrapping: { try await self.getAddressUtxos(request: $0, context: $1) } + ) + + case "GetAddressUtxosStream": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetAddressUtxosStreamInterceptors() ?? [], + wrapping: { try await self.getAddressUtxosStream(request: $0, responseStream: $1, context: $2) } + ) + + case "GetLightdInfo": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetLightdInfoInterceptors() ?? [], + wrapping: { try await self.getLightdInfo(request: $0, context: $1) } + ) + + case "Ping": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makePingInterceptors() ?? [], + wrapping: { try await self.ping(request: $0, context: $1) } + ) + + default: + return nil + } + } +} + +internal protocol CompactTxStreamerServerInterceptorFactoryProtocol: Sendable { + + /// - Returns: Interceptors to use when handling 'getLatestBlock'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetLatestBlockInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getBlock'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetBlockInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getBlockNullifiers'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetBlockNullifiersInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getBlockRange'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetBlockRangeInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getBlockRangeNullifiers'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetBlockRangeNullifiersInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getTransaction'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetTransactionInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'sendTransaction'. + /// Defaults to calling `self.makeInterceptors()`. + func makeSendTransactionInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getTaddressTxids'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetTaddressTxidsInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getTaddressBalance'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetTaddressBalanceInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getTaddressBalanceStream'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetTaddressBalanceStreamInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getMempoolTx'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetMempoolTxInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getMempoolStream'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetMempoolStreamInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getTreeState'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetTreeStateInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getLatestTreeState'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetLatestTreeStateInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getSubtreeRoots'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetSubtreeRootsInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getAddressUtxos'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetAddressUtxosInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getAddressUtxosStream'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetAddressUtxosStreamInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getLightdInfo'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetLightdInfoInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'ping'. + /// Defaults to calling `self.makeInterceptors()`. + func makePingInterceptors() -> [ServerInterceptor] +} + +internal enum CompactTxStreamerServerMetadata { + internal static let serviceDescriptor = GRPCServiceDescriptor( + name: "CompactTxStreamer", + fullName: "cash.z.wallet.sdk.rpc.CompactTxStreamer", + methods: [ + CompactTxStreamerServerMetadata.Methods.getLatestBlock, + CompactTxStreamerServerMetadata.Methods.getBlock, + CompactTxStreamerServerMetadata.Methods.getBlockNullifiers, + CompactTxStreamerServerMetadata.Methods.getBlockRange, + CompactTxStreamerServerMetadata.Methods.getBlockRangeNullifiers, + CompactTxStreamerServerMetadata.Methods.getTransaction, + CompactTxStreamerServerMetadata.Methods.sendTransaction, + CompactTxStreamerServerMetadata.Methods.getTaddressTxids, + CompactTxStreamerServerMetadata.Methods.getTaddressBalance, + CompactTxStreamerServerMetadata.Methods.getTaddressBalanceStream, + CompactTxStreamerServerMetadata.Methods.getMempoolTx, + CompactTxStreamerServerMetadata.Methods.getMempoolStream, + CompactTxStreamerServerMetadata.Methods.getTreeState, + CompactTxStreamerServerMetadata.Methods.getLatestTreeState, + CompactTxStreamerServerMetadata.Methods.getSubtreeRoots, + CompactTxStreamerServerMetadata.Methods.getAddressUtxos, + CompactTxStreamerServerMetadata.Methods.getAddressUtxosStream, + CompactTxStreamerServerMetadata.Methods.getLightdInfo, + CompactTxStreamerServerMetadata.Methods.ping, + ] + ) + + internal enum Methods { + internal static let getLatestBlock = GRPCMethodDescriptor( + name: "GetLatestBlock", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLatestBlock", + type: GRPCCallType.unary + ) + + internal static let getBlock = GRPCMethodDescriptor( + name: "GetBlock", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlock", + type: GRPCCallType.unary + ) + + internal static let getBlockNullifiers = GRPCMethodDescriptor( + name: "GetBlockNullifiers", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlockNullifiers", + type: GRPCCallType.unary + ) + + internal static let getBlockRange = GRPCMethodDescriptor( + name: "GetBlockRange", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlockRange", + type: GRPCCallType.serverStreaming + ) + + internal static let getBlockRangeNullifiers = GRPCMethodDescriptor( + name: "GetBlockRangeNullifiers", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlockRangeNullifiers", + type: GRPCCallType.serverStreaming + ) + + internal static let getTransaction = GRPCMethodDescriptor( + name: "GetTransaction", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTransaction", + type: GRPCCallType.unary + ) + + internal static let sendTransaction = GRPCMethodDescriptor( + name: "SendTransaction", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/SendTransaction", + type: GRPCCallType.unary + ) + + internal static let getTaddressTxids = GRPCMethodDescriptor( + name: "GetTaddressTxids", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressTxids", + type: GRPCCallType.serverStreaming + ) + + internal static let getTaddressBalance = GRPCMethodDescriptor( + name: "GetTaddressBalance", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressBalance", + type: GRPCCallType.unary + ) + + internal static let getTaddressBalanceStream = GRPCMethodDescriptor( + name: "GetTaddressBalanceStream", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressBalanceStream", + type: GRPCCallType.clientStreaming + ) + + internal static let getMempoolTx = GRPCMethodDescriptor( + name: "GetMempoolTx", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetMempoolTx", + type: GRPCCallType.serverStreaming + ) + + internal static let getMempoolStream = GRPCMethodDescriptor( + name: "GetMempoolStream", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetMempoolStream", + type: GRPCCallType.serverStreaming + ) + + internal static let getTreeState = GRPCMethodDescriptor( + name: "GetTreeState", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTreeState", + type: GRPCCallType.unary + ) + + internal static let getLatestTreeState = GRPCMethodDescriptor( + name: "GetLatestTreeState", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLatestTreeState", + type: GRPCCallType.unary + ) + + internal static let getSubtreeRoots = GRPCMethodDescriptor( + name: "GetSubtreeRoots", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetSubtreeRoots", + type: GRPCCallType.serverStreaming + ) + + internal static let getAddressUtxos = GRPCMethodDescriptor( + name: "GetAddressUtxos", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressUtxos", + type: GRPCCallType.unary + ) + + internal static let getAddressUtxosStream = GRPCMethodDescriptor( + name: "GetAddressUtxosStream", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressUtxosStream", + type: GRPCCallType.serverStreaming + ) + + internal static let getLightdInfo = GRPCMethodDescriptor( + name: "GetLightdInfo", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo", + type: GRPCCallType.unary + ) + + internal static let ping = GRPCMethodDescriptor( + name: "Ping", + path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/Ping", + type: GRPCCallType.unary + ) + } +} diff --git a/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.pb.swift b/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.pb.swift index d54ba917..67f796bd 100644 --- a/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.pb.swift +++ b/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.pb.swift @@ -56,7 +56,7 @@ enum ShieldedProtocol: SwiftProtobuf.Enum { extension ShieldedProtocol: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [ShieldedProtocol] = [ + static let allCases: [ShieldedProtocol] = [ .sapling, .orchard, ] @@ -144,7 +144,7 @@ struct TxFilter { fileprivate var _block: BlockID? = nil } -/// 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. struct RawTransaction { diff --git a/Tests/TestUtils/DarkSideWalletService.swift b/Tests/TestUtils/DarkSideWalletService.swift index 60e3f90e..fb4d791b 100644 --- a/Tests/TestUtils/DarkSideWalletService.swift +++ b/Tests/TestUtils/DarkSideWalletService.swift @@ -190,6 +190,10 @@ class DarksideWalletService: LightWalletService { func getSubtreeRoots(_ request: ZcashLightClientKit.GetSubtreeRootsArg) -> AsyncThrowingStream { service.getSubtreeRoots(request) } + + func setSubtreeRoots(_ request: DarksideSubtreeRoots) { + _ = darksideService.setSubtreeRoots(request) + } } enum DarksideWalletDConstants: NetworkConstants { diff --git a/Tests/TestUtils/FakeChainBuilder.swift b/Tests/TestUtils/FakeChainBuilder.swift index 6c062f1f..d8a863ca 100644 --- a/Tests/TestUtils/FakeChainBuilder.swift +++ b/Tests/TestUtils/FakeChainBuilder.swift @@ -15,14 +15,14 @@ enum FakeChainBuilderError: Error { enum FakeChainBuilder { static let someOtherTxUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/t-shielded-spend.txt" static let txMainnetBlockUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/663150.txt" - + static let testnetCanopyUpdateUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/testnet-canopy/1028400-1028600.txt" - + static let testnetCanopyStartBlock = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/testnet-canopy/1013250.txt" static let testnetPreCanopyTx = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/testnet-canopy/pre-activation-txs/61088726aaa7c25b0568dd7bf19955f4a57f7173034e720c924107ff05cd3649.txt" - + static let testnetPostCanopyTx = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/testnet-canopy/post-activation-txs/ecaa6c03709d70aa25446a81690b18ddb11daac96a03fe4b5cfd0d89a49fb963.txt" - + static func buildSingleNoteChain(darksideWallet: DarksideWalletService, branchID: String, chainName: String) throws { try darksideWallet.reset( saplingActivation: 663150, @@ -31,14 +31,14 @@ enum FakeChainBuilder { branchID: branchID, chainName: chainName ) - + try darksideWallet.useDataset(from: txMainnetBlockUrl) - + try darksideWallet.stageBlocksCreate(from: 663151, count: 100) - + try darksideWallet.stageTransaction(from: txUrls[663174]!, at: 663174) } - + static func buildChain(darksideWallet: DarksideWalletService, branchID: String, chainName: String) throws { try darksideWallet.reset( saplingActivation: 663150, @@ -47,16 +47,18 @@ enum FakeChainBuilder { branchID: branchID, chainName: chainName ) - + try darksideWallet.useDataset(from: txMainnetBlockUrl) - + try darksideWallet.stageBlocksCreate(from: 663151, count: 100) - + try darksideWallet.stageTransaction(from: txUrls[663174]!, at: 663174) - + try darksideWallet.stageTransaction(from: txUrls[663188]!, at: 663188) + + try setSubTreeRoots(service: darksideWallet) } - + static func buildChainWithTxsFarFromEachOther(darksideWallet: DarksideWalletService, branchID: String, chainName: String, length: Int) throws { try darksideWallet.reset( saplingActivation: 663150, @@ -65,16 +67,16 @@ enum FakeChainBuilder { branchID: branchID, chainName: chainName ) - + try darksideWallet.useDataset(from: txMainnetBlockUrl) - + try darksideWallet.stageBlocksCreate(from: 663151, count: length) - + try darksideWallet.stageTransaction(from: txUrls[663188]!, at: 663188) - + try darksideWallet.stageTransaction(from: txUrls[663974]!, at: 663974) } - + static func buildChain(darksideWallet: DarksideWalletService, branchID: String, chainName: String, length: Int) throws { try darksideWallet.reset( saplingActivation: 663150, @@ -83,20 +85,20 @@ enum FakeChainBuilder { branchID: branchID, chainName: chainName ) - + try darksideWallet.useDataset(from: txMainnetBlockUrl) - + try darksideWallet.stageBlocksCreate(from: 663151, count: length) try darksideWallet.stageTransaction(from: txUrls[663174]!, at: 663174) try darksideWallet.stageTransaction(from: txUrls[663188]!, at: 663188) try darksideWallet.stageTransaction(from: txUrls[663202]!, at: 663202) try darksideWallet.stageTransaction(from: txUrls[663218]!, at: 663218) try darksideWallet.stageTransaction(from: txUrls[663229]!, at: 663229) - + try darksideWallet.stageTransaction(from: txUrls[663953]!, at: 663953) try darksideWallet.stageTransaction(from: txUrls[663974]!, at: 663974) } - + static func buildChain( darksideWallet: DarksideWalletService, birthday: BlockHeight, @@ -119,7 +121,7 @@ enum FakeChainBuilder { try darksideWallet.stageBlocksCreate(from: birthday + 1, count: length) try darksideWallet.stageTransaction(from: testnetPreCanopyTx, at: networkActivationHeight - ZcashSDK.expiryOffset) } - + static func buildChainPostActivationFunds(darksideWallet: DarksideWalletService, birthday: BlockHeight, startSaplingTreeSize: UInt32, startOrchardTreeSize: UInt32, networkActivationHeight: BlockHeight, length: Int) throws { try darksideWallet.reset( saplingActivation: birthday, @@ -133,7 +135,7 @@ enum FakeChainBuilder { try darksideWallet.stageBlocksCreate(from: birthday + 1, count: length) try darksideWallet.stageTransaction(from: testnetPostCanopyTx, at: networkActivationHeight + 1) } - + static func buildChainMixedFunds( darksideWallet: DarksideWalletService, birthday: BlockHeight, @@ -154,14 +156,14 @@ enum FakeChainBuilder { chainName: chainName, length: length ) - + try darksideWallet.stageTransaction(from: testnetPostCanopyTx, at: networkActivationHeight + ZcashSDK.expiryOffset) } - + static func buildTxUrl(for id: String) -> String { "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/\(id).txt" } - + static let txUrls = [ 663174: buildTxUrl(for: "8f064d23c66dc36e32445e5f3b50e0f32ac3ddb78cff21fb521eb6c19c07c99a"), 663188: buildTxUrl(for: "15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590"), @@ -244,4 +246,34 @@ enum FakeChainBuilder { 822410: buildTxUrl(for: "f3f8684be8d77367d099a38f30e3652410cdebe35c006d0599d86d8ec640867f"), 828933: buildTxUrl(for: "1fd394257d1c10c8a70fb760cf73f6d0e96e61edcf1ffca6da12d733a59221a4") ] + + static func setSubTreeRoots(service: DarksideWalletService) throws { + let subtreeRoots = try DarksideSubtreeRoots.with { subtreeRoots in + subtreeRoots.shieldedProtocol = .sapling + subtreeRoots.startIndex = 0 + subtreeRoots.subtreeRoots = [ + try .init( + jsonString: + """ + { + "rootHash": "dUu1k+pC0jGn3fNnZA8Ju/WdwA8sHSADzDQODAFrWxM=", + "completingBlockHash": "AAAAAACPmsBjbYHceVyw9Ulab9B06BWhnhmfDaSRUf4=", + "completingBlockHeight": "558822" + } + """ + ), + try .init( + jsonString: + """ + { + "rootHash": "A2VMPqy7m5PhIs9td7YG6uKWEPTzikd5hTaBl/1o4C0=", + "completingBlockHash": "AAAAAAIFzQaAI1LPzYkhgVvQqdc+ta/D7AnzXHGARqk=", + "completingBlockHeight": "670209" + } + """ + ) + ] + } + service.setSubtreeRoots(subtreeRoots) + } } diff --git a/Tests/TestUtils/proto/darkside.grpc.swift b/Tests/TestUtils/proto/darkside.grpc.swift index 694d1e23..c7688b9d 100644 --- a/Tests/TestUtils/proto/darkside.grpc.swift +++ b/Tests/TestUtils/proto/darkside.grpc.swift @@ -89,6 +89,11 @@ internal protocol DarksideStreamerClientProtocol: GRPCClient { _ request: Empty, callOptions: CallOptions? ) -> UnaryCall + + func setSubtreeRoots( + _ request: DarksideSubtreeRoots, + callOptions: CallOptions? + ) -> UnaryCall } extension DarksideStreamerClientProtocol { @@ -391,6 +396,25 @@ extension DarksideStreamerClientProtocol { interceptors: self.interceptors?.makeClearAllTreeStatesInterceptors() ?? [] ) } + + /// Sets the subtree roots cache (for GetSubtreeRoots), + /// replacing any existing entries + /// + /// - Parameters: + /// - request: Request to send to SetSubtreeRoots. + /// - callOptions: Call options. + /// - Returns: A `UnaryCall` with futures for the metadata, status and response. + internal func setSubtreeRoots( + _ request: DarksideSubtreeRoots, + callOptions: CallOptions? = nil + ) -> UnaryCall { + return self.makeUnaryCall( + path: DarksideStreamerClientMetadata.Methods.setSubtreeRoots.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeSetSubtreeRootsInterceptors() ?? [] + ) + } } @available(*, deprecated) @@ -526,6 +550,11 @@ internal protocol DarksideStreamerAsyncClientProtocol: GRPCClient { _ request: Empty, callOptions: CallOptions? ) -> GRPCAsyncUnaryCall + + func makeSetSubtreeRootsCall( + _ request: DarksideSubtreeRoots, + callOptions: CallOptions? + ) -> GRPCAsyncUnaryCall } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) @@ -701,6 +730,18 @@ extension DarksideStreamerAsyncClientProtocol { interceptors: self.interceptors?.makeClearAllTreeStatesInterceptors() ?? [] ) } + + internal func makeSetSubtreeRootsCall( + _ request: DarksideSubtreeRoots, + callOptions: CallOptions? = nil + ) -> GRPCAsyncUnaryCall { + return self.makeAsyncUnaryCall( + path: DarksideStreamerClientMetadata.Methods.setSubtreeRoots.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeSetSubtreeRootsInterceptors() ?? [] + ) + } } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) @@ -896,6 +937,18 @@ extension DarksideStreamerAsyncClientProtocol { interceptors: self.interceptors?.makeClearAllTreeStatesInterceptors() ?? [] ) } + + internal func setSubtreeRoots( + _ request: DarksideSubtreeRoots, + callOptions: CallOptions? = nil + ) async throws -> Empty { + return try await self.performAsyncUnaryCall( + path: DarksideStreamerClientMetadata.Methods.setSubtreeRoots.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeSetSubtreeRootsInterceptors() ?? [] + ) + } } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) @@ -958,6 +1011,9 @@ internal protocol DarksideStreamerClientInterceptorFactoryProtocol: Sendable { /// - Returns: Interceptors to use when invoking 'clearAllTreeStates'. func makeClearAllTreeStatesInterceptors() -> [ClientInterceptor] + + /// - Returns: Interceptors to use when invoking 'setSubtreeRoots'. + func makeSetSubtreeRootsInterceptors() -> [ClientInterceptor] } internal enum DarksideStreamerClientMetadata { @@ -979,6 +1035,7 @@ internal enum DarksideStreamerClientMetadata { DarksideStreamerClientMetadata.Methods.addTreeState, DarksideStreamerClientMetadata.Methods.removeTreeState, DarksideStreamerClientMetadata.Methods.clearAllTreeStates, + DarksideStreamerClientMetadata.Methods.setSubtreeRoots, ] ) @@ -1066,6 +1123,12 @@ internal enum DarksideStreamerClientMetadata { path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/ClearAllTreeStates", type: GRPCCallType.unary ) + + internal static let setSubtreeRoots = GRPCMethodDescriptor( + name: "SetSubtreeRoots", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/SetSubtreeRoots", + type: GRPCCallType.unary + ) } } @@ -1160,6 +1223,10 @@ internal protocol DarksideStreamerProvider: CallHandlerProvider { /// Clear the list of GetTreeStates entries (can't fail) func clearAllTreeStates(request: Empty, context: StatusOnlyCallContext) -> EventLoopFuture + + /// Sets the subtree roots cache (for GetSubtreeRoots), + /// replacing any existing entries + func setSubtreeRoots(request: DarksideSubtreeRoots, context: StatusOnlyCallContext) -> EventLoopFuture } extension DarksideStreamerProvider { @@ -1300,6 +1367,15 @@ extension DarksideStreamerProvider { userFunction: self.clearAllTreeStates(request:context:) ) + case "SetSubtreeRoots": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeSetSubtreeRootsInterceptors() ?? [], + userFunction: self.setSubtreeRoots(request:context:) + ) + default: return nil } @@ -1442,6 +1518,13 @@ internal protocol DarksideStreamerAsyncProvider: CallHandlerProvider, Sendable { request: Empty, context: GRPCAsyncServerCallContext ) async throws -> Empty + + /// Sets the subtree roots cache (for GetSubtreeRoots), + /// replacing any existing entries + func setSubtreeRoots( + request: DarksideSubtreeRoots, + context: GRPCAsyncServerCallContext + ) async throws -> Empty } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) @@ -1589,6 +1672,15 @@ extension DarksideStreamerAsyncProvider { wrapping: { try await self.clearAllTreeStates(request: $0, context: $1) } ) + case "SetSubtreeRoots": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeSetSubtreeRootsInterceptors() ?? [], + wrapping: { try await self.setSubtreeRoots(request: $0, context: $1) } + ) + default: return nil } @@ -1652,6 +1744,10 @@ internal protocol DarksideStreamerServerInterceptorFactoryProtocol: Sendable { /// - Returns: Interceptors to use when handling 'clearAllTreeStates'. /// Defaults to calling `self.makeInterceptors()`. func makeClearAllTreeStatesInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'setSubtreeRoots'. + /// Defaults to calling `self.makeInterceptors()`. + func makeSetSubtreeRootsInterceptors() -> [ServerInterceptor] } internal enum DarksideStreamerServerMetadata { @@ -1673,6 +1769,7 @@ internal enum DarksideStreamerServerMetadata { DarksideStreamerServerMetadata.Methods.addTreeState, DarksideStreamerServerMetadata.Methods.removeTreeState, DarksideStreamerServerMetadata.Methods.clearAllTreeStates, + DarksideStreamerServerMetadata.Methods.setSubtreeRoots, ] ) @@ -1760,5 +1857,11 @@ internal enum DarksideStreamerServerMetadata { path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/ClearAllTreeStates", type: GRPCCallType.unary ) + + internal static let setSubtreeRoots = GRPCMethodDescriptor( + name: "SetSubtreeRoots", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/SetSubtreeRoots", + type: GRPCCallType.unary + ) } } diff --git a/Tests/TestUtils/proto/darkside.pb.swift b/Tests/TestUtils/proto/darkside.pb.swift index f8d1291e..034a64c5 100644 --- a/Tests/TestUtils/proto/darkside.pb.swift +++ b/Tests/TestUtils/proto/darkside.pb.swift @@ -13,7 +13,7 @@ import Foundation import SwiftProtobuf - +@testable import ZcashLightClientKit // If the compiler emits an error on this type, it is because this file // was generated by a version of the `protoc` Swift plug-in that is // incompatible with the version of SwiftProtobuf to which you are linking. @@ -116,6 +116,22 @@ struct DarksideEmptyBlocks { init() {} } +struct DarksideSubtreeRoots { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var shieldedProtocol: ShieldedProtocol = .sapling + + var startIndex: UInt32 = 0 + + var subtreeRoots: [SubtreeRoot] = [] + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + #if swift(>=5.5) && canImport(_Concurrency) extension DarksideMetaState: @unchecked Sendable {} extension DarksideBlock: @unchecked Sendable {} @@ -123,6 +139,7 @@ extension DarksideBlocksURL: @unchecked Sendable {} extension DarksideTransactionsURL: @unchecked Sendable {} extension DarksideHeight: @unchecked Sendable {} extension DarksideEmptyBlocks: @unchecked Sendable {} +extension DarksideSubtreeRoots: @unchecked Sendable {} #endif // swift(>=5.5) && canImport(_Concurrency) // MARK: - Code below here is support for the SwiftProtobuf runtime. @@ -362,3 +379,47 @@ extension DarksideEmptyBlocks: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl return true } } + +extension DarksideSubtreeRoots: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".DarksideSubtreeRoots" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "shieldedProtocol"), + 2: .same(proto: "startIndex"), + 3: .same(proto: "subtreeRoots"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.shieldedProtocol) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.startIndex) }() + case 3: try { try decoder.decodeRepeatedMessageField(value: &self.subtreeRoots) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.shieldedProtocol != .sapling { + try visitor.visitSingularEnumField(value: self.shieldedProtocol, fieldNumber: 1) + } + if self.startIndex != 0 { + try visitor.visitSingularUInt32Field(value: self.startIndex, fieldNumber: 2) + } + if !self.subtreeRoots.isEmpty { + try visitor.visitRepeatedMessageField(value: self.subtreeRoots, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: DarksideSubtreeRoots, rhs: DarksideSubtreeRoots) -> Bool { + if lhs.shieldedProtocol != rhs.shieldedProtocol {return false} + if lhs.startIndex != rhs.startIndex {return false} + if lhs.subtreeRoots != rhs.subtreeRoots {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Tests/TestUtils/proto/darkside.proto b/Tests/TestUtils/proto/darkside.proto index 5468e769..249af4a4 100644 --- a/Tests/TestUtils/proto/darkside.proto +++ b/Tests/TestUtils/proto/darkside.proto @@ -45,6 +45,12 @@ message DarksideEmptyBlocks { int32 count = 3; } +message DarksideSubtreeRoots { + ShieldedProtocol shieldedProtocol = 1; + uint32 startIndex = 2; + repeated SubtreeRoot subtreeRoots = 3; +} + // Darksidewalletd maintains two staging areas, blocks and transactions. The // Stage*() gRPCs add items to the staging area; ApplyStaged() "applies" everything // in the staging area to the working (operational) state that the mock zcashd @@ -132,4 +138,8 @@ service DarksideStreamer { // Clear the list of GetTreeStates entries (can't fail) rpc ClearAllTreeStates(Empty) returns (Empty) {} + + // Sets the subtree roots cache (for GetSubtreeRoots), + // replacing any existing entries + rpc SetSubtreeRoots(DarksideSubtreeRoots) returns (Empty) {} }