diff --git a/Tests/AliasDarksideTests/SDKSynchronizerAliasDarksideTests.swift b/Tests/AliasDarksideTests/SDKSynchronizerAliasDarksideTests.swift index 3bfa8b96..783d9ddb 100644 --- a/Tests/AliasDarksideTests/SDKSynchronizerAliasDarksideTests.swift +++ b/Tests/AliasDarksideTests/SDKSynchronizerAliasDarksideTests.swift @@ -59,7 +59,7 @@ class SDKSynchronizerAliasDarksideTests: ZcashTestCase { endpoint: endpoint ) - try await coordinator.reset(saplingActivation: birthday, branchID: branchID, chainName: chainName) + try await coordinator.reset(saplingActivation: birthday, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: branchID, chainName: chainName) coordinators.append(coordinator) } diff --git a/Tests/DarksideTests/AdvancedReOrgTests.swift b/Tests/DarksideTests/AdvancedReOrgTests.swift index c9ffc51c..1b570b64 100644 --- a/Tests/DarksideTests/AdvancedReOrgTests.swift +++ b/Tests/DarksideTests/AdvancedReOrgTests.swift @@ -33,7 +33,7 @@ class AdvancedReOrgTests: ZcashTestCase { walletBirthday: birthday + 50, network: network ) - try await coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName) + try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: self.branchID, chainName: self.chainName) } override func tearDown() async throws { @@ -453,7 +453,13 @@ class AdvancedReOrgTests: ZcashTestCase { await hookToReOrgNotification() self.expectedReorgHeight = 663196 self.expectedRewindHeight = 663175 - try await coordinator.reset(saplingActivation: birthday, branchID: "2bb40e60", chainName: "main") + try await coordinator.reset( + saplingActivation: birthday, + startSaplingTreeSize: 128607, + startOrchardTreeSize: 0, + branchID: "2bb40e60", + chainName: "main" + ) try coordinator.resetBlocks(dataset: .predefined(dataset: .txIndexChangeBefore)) try coordinator.applyStaged(blockheight: 663195) sleep(1) @@ -1031,7 +1037,7 @@ class AdvancedReOrgTests: ZcashTestCase { /// 8. sync to latest height /// 9. verify that the balance is equal to the one before the reorg func testReOrgChangesInboundMinedHeight() async throws { - try await coordinator.reset(saplingActivation: 663150, branchID: branchID, chainName: chainName) + try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: branchID, chainName: chainName) sleep(2) try coordinator.resetBlocks(dataset: .predefined(dataset: .txHeightReOrgBefore)) sleep(2) @@ -1096,7 +1102,7 @@ class AdvancedReOrgTests: ZcashTestCase { // FIXME [#644]: Test works with lightwalletd v0.4.13 but is broken when using newer lightwalletd. More info is in #644. func testReOrgRemovesIncomingTxForever() async throws { await hookToReOrgNotification() - try await coordinator.reset(saplingActivation: 663150, branchID: branchID, chainName: chainName) + try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: branchID, chainName: chainName) try coordinator.resetBlocks(dataset: .predefined(dataset: .txReOrgRemovesInboundTxBefore)) diff --git a/Tests/DarksideTests/BalanceTests.swift b/Tests/DarksideTests/BalanceTests.swift index 90fae922..0b9da37d 100644 --- a/Tests/DarksideTests/BalanceTests.swift +++ b/Tests/DarksideTests/BalanceTests.swift @@ -30,7 +30,7 @@ class BalanceTests: ZcashTestCase { walletBirthday: birthday, network: network ) - try await coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main") + try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: "e9ff75a6", chainName: "main") } override func tearDown() async throws { diff --git a/Tests/DarksideTests/DarksideSanityCheckTests.swift b/Tests/DarksideTests/DarksideSanityCheckTests.swift index 73071aad..b6087cc3 100644 --- a/Tests/DarksideTests/DarksideSanityCheckTests.swift +++ b/Tests/DarksideTests/DarksideSanityCheckTests.swift @@ -33,7 +33,7 @@ class DarksideSanityCheckTests: ZcashTestCase { network: network ) - try await coordinator.reset(saplingActivation: self.birthday, branchID: self.branchID, chainName: self.chainName) + try await coordinator.reset(saplingActivation: self.birthday, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: self.branchID, chainName: self.chainName) try self.coordinator.resetBlocks(dataset: .default) } diff --git a/Tests/DarksideTests/PendingTransactionUpdatesTest.swift b/Tests/DarksideTests/PendingTransactionUpdatesTest.swift index 20fbc97e..431b8222 100644 --- a/Tests/DarksideTests/PendingTransactionUpdatesTest.swift +++ b/Tests/DarksideTests/PendingTransactionUpdatesTest.swift @@ -30,7 +30,7 @@ class PendingTransactionUpdatesTest: ZcashTestCase { walletBirthday: birthday, network: network ) - try await coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main") + try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: "e9ff75a6", chainName: "main") } override func tearDown() async throws { diff --git a/Tests/DarksideTests/ReOrgTests.swift b/Tests/DarksideTests/ReOrgTests.swift index 23e76aed..c3798a1f 100644 --- a/Tests/DarksideTests/ReOrgTests.swift +++ b/Tests/DarksideTests/ReOrgTests.swift @@ -50,7 +50,7 @@ class ReOrgTests: ZcashTestCase { network: self.network ) - try await coordinator.reset(saplingActivation: self.birthday, branchID: self.branchID, chainName: self.chainName) + try await coordinator.reset(saplingActivation: self.birthday, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: self.branchID, chainName: self.chainName) try self.coordinator.resetBlocks(dataset: .default) @@ -128,7 +128,7 @@ class ReOrgTests: ZcashTestCase { targetHeight: BlockHeight ) async throws { do { - try await coordinator.reset(saplingActivation: birthday, branchID: branchID, chainName: chainName) + try await coordinator.reset(saplingActivation: birthday, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: branchID, chainName: chainName) try coordinator.resetBlocks(dataset: .predefined(dataset: .beforeReOrg)) try coordinator.applyStaged(blockheight: firstLatestHeight) sleep(1) diff --git a/Tests/DarksideTests/RewindRescanTests.swift b/Tests/DarksideTests/RewindRescanTests.swift index be2c944f..4c195a0c 100644 --- a/Tests/DarksideTests/RewindRescanTests.swift +++ b/Tests/DarksideTests/RewindRescanTests.swift @@ -35,7 +35,7 @@ class RewindRescanTests: ZcashTestCase { walletBirthday: birthday, network: network ) - try await coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main") + try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: "e9ff75a6", chainName: "main") } override func tearDown() async throws { diff --git a/Tests/DarksideTests/ShieldFundsTests.swift b/Tests/DarksideTests/ShieldFundsTests.swift index bac246b4..dfcaed59 100644 --- a/Tests/DarksideTests/ShieldFundsTests.swift +++ b/Tests/DarksideTests/ShieldFundsTests.swift @@ -30,7 +30,7 @@ class ShieldFundsTests: ZcashTestCase { walletBirthday: birthday, network: network ) - try await coordinator.reset(saplingActivation: birthday, branchID: self.branchID, chainName: self.chainName) + try await coordinator.reset(saplingActivation: birthday, startSaplingTreeSize: 1120954, startOrchardTreeSize: 0, branchID: self.branchID, chainName: self.chainName) try coordinator.service.clearAddedUTXOs() } diff --git a/Tests/DarksideTests/SynchronizerDarksideTests.swift b/Tests/DarksideTests/SynchronizerDarksideTests.swift index 10eca140..fb45a60d 100644 --- a/Tests/DarksideTests/SynchronizerDarksideTests.swift +++ b/Tests/DarksideTests/SynchronizerDarksideTests.swift @@ -40,7 +40,7 @@ class SynchronizerDarksideTests: ZcashTestCase { network: network ) - try await coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main") + try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: "e9ff75a6", chainName: "main") } override func tearDown() async throws { diff --git a/Tests/DarksideTests/SynchronizerTests.swift b/Tests/DarksideTests/SynchronizerTests.swift index d8fc2c14..dc0bae19 100644 --- a/Tests/DarksideTests/SynchronizerTests.swift +++ b/Tests/DarksideTests/SynchronizerTests.swift @@ -33,7 +33,7 @@ final class SynchronizerTests: ZcashTestCase { walletBirthday: birthday + 50, network: network ) - try await coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName) + try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: self.branchID, chainName: self.chainName) let eventClosure: CompactBlockProcessor.EventClosure = { [weak self] event in switch event { diff --git a/Tests/DarksideTests/Z2TReceiveTests.swift b/Tests/DarksideTests/Z2TReceiveTests.swift index 16bf4268..05b5de3e 100644 --- a/Tests/DarksideTests/Z2TReceiveTests.swift +++ b/Tests/DarksideTests/Z2TReceiveTests.swift @@ -33,7 +33,7 @@ class Z2TReceiveTests: ZcashTestCase { walletBirthday: birthday, network: network ) - try await coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName) + try await coordinator.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: self.branchID, chainName: self.chainName) } override func tearDown() async throws { diff --git a/Tests/TestUtils/DarkSideWalletService.swift b/Tests/TestUtils/DarkSideWalletService.swift index 8b655939..a212ba54 100644 --- a/Tests/TestUtils/DarkSideWalletService.swift +++ b/Tests/TestUtils/DarkSideWalletService.swift @@ -122,11 +122,19 @@ class DarksideWalletService: LightWalletService { } } - func reset(saplingActivation: BlockHeight, branchID: String = "d3adb33f", chainName: String = "test") throws { + func reset( + saplingActivation: BlockHeight, + startSaplingTreeSize: UInt32, + startOrchardTreeSize: UInt32, + branchID: String = "d3adb33f", + chainName: String = "test" + ) throws { var metaState = DarksideMetaState() metaState.saplingActivation = Int32(saplingActivation) metaState.branchID = branchID metaState.chainName = chainName + metaState.startSaplingCommitmentTreeSize = startSaplingTreeSize + metaState.startOrchardCommitmentTreeSize = startOrchardTreeSize // TODO: [#718] complete meta state correctly, https://github.com/zcash/ZcashLightClientKit/issues/718 _ = try darksideService.reset(metaState).response.wait() } diff --git a/Tests/TestUtils/FakeChainBuilder.swift b/Tests/TestUtils/FakeChainBuilder.swift index 2377d80a..03d03792 100644 --- a/Tests/TestUtils/FakeChainBuilder.swift +++ b/Tests/TestUtils/FakeChainBuilder.swift @@ -24,7 +24,7 @@ enum FakeChainBuilder { 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, branchID: branchID, chainName: chainName) + try darksideWallet.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: branchID, chainName: chainName) try darksideWallet.useDataset(from: txMainnetBlockUrl) try darksideWallet.stageBlocksCreate(from: 663151, count: 100) @@ -33,7 +33,7 @@ enum FakeChainBuilder { } static func buildChain(darksideWallet: DarksideWalletService, branchID: String, chainName: String) throws { - try darksideWallet.reset(saplingActivation: 663150, branchID: branchID, chainName: chainName) + try darksideWallet.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: branchID, chainName: chainName) try darksideWallet.useDataset(from: txMainnetBlockUrl) try darksideWallet.stageBlocksCreate(from: 663151, count: 100) @@ -44,7 +44,7 @@ enum FakeChainBuilder { } static func buildChainWithTxsFarFromEachOther(darksideWallet: DarksideWalletService, branchID: String, chainName: String, length: Int) throws { - try darksideWallet.reset(saplingActivation: 663150, branchID: branchID, chainName: chainName) + try darksideWallet.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: branchID, chainName: chainName) try darksideWallet.useDataset(from: txMainnetBlockUrl) try darksideWallet.stageBlocksCreate(from: 663151, count: length) @@ -55,7 +55,7 @@ enum FakeChainBuilder { } static func buildChain(darksideWallet: DarksideWalletService, branchID: String, chainName: String, length: Int) throws { - try darksideWallet.reset(saplingActivation: 663150, branchID: branchID, chainName: chainName) + try darksideWallet.reset(saplingActivation: 663150, startSaplingTreeSize: 128607, startOrchardTreeSize: 0, branchID: branchID, chainName: chainName) try darksideWallet.useDataset(from: txMainnetBlockUrl) try darksideWallet.stageBlocksCreate(from: 663151, count: length) @@ -72,20 +72,22 @@ enum FakeChainBuilder { static func buildChain( darksideWallet: DarksideWalletService, birthday: BlockHeight, + startSaplingTreeSize: UInt32, + startOrchardTreeSize: UInt32, networkActivationHeight: BlockHeight, branchID: String, chainName: String, length: Int ) throws { - try darksideWallet.reset(saplingActivation: birthday, branchID: branchID, chainName: chainName) + try darksideWallet.reset(saplingActivation: birthday, startSaplingTreeSize: startSaplingTreeSize, startOrchardTreeSize: startOrchardTreeSize, branchID: branchID, chainName: chainName) try darksideWallet.useDataset(testnetCanopyStartBlock) try darksideWallet.stageBlocksCreate(from: birthday + 1, count: length) try darksideWallet.stageTransaction(from: testnetPreCanopyTx, at: networkActivationHeight - ZcashSDK.expiryOffset) } - static func buildChainPostActivationFunds(darksideWallet: DarksideWalletService, birthday: BlockHeight, networkActivationHeight: BlockHeight, length: Int) throws { - try darksideWallet.reset(saplingActivation: birthday, branchID: "e9ff75a6", chainName: "testnet") + static func buildChainPostActivationFunds(darksideWallet: DarksideWalletService, birthday: BlockHeight, startSaplingTreeSize: UInt32, startOrchardTreeSize: UInt32, networkActivationHeight: BlockHeight, length: Int) throws { + try darksideWallet.reset(saplingActivation: birthday, startSaplingTreeSize: startSaplingTreeSize, startOrchardTreeSize: startOrchardTreeSize, branchID: "e9ff75a6", chainName: "testnet") try darksideWallet.useDataset(testnetCanopyStartBlock) try darksideWallet.stageBlocksCreate(from: birthday + 1, count: length) @@ -95,6 +97,8 @@ enum FakeChainBuilder { static func buildChainMixedFunds( darksideWallet: DarksideWalletService, birthday: BlockHeight, + startSaplingTreeSize: UInt32, + startOrchardTreeSize: UInt32, networkActivationHeight: BlockHeight, branchID: String, chainName: String, @@ -103,6 +107,8 @@ enum FakeChainBuilder { try buildChain( darksideWallet: darksideWallet, birthday: birthday, + startSaplingTreeSize: startSaplingTreeSize, + startOrchardTreeSize: startOrchardTreeSize, networkActivationHeight: networkActivationHeight, branchID: branchID, chainName: chainName, diff --git a/Tests/TestUtils/TestCoordinator.swift b/Tests/TestUtils/TestCoordinator.swift index 7f2c9941..9e1174f2 100644 --- a/Tests/TestUtils/TestCoordinator.swift +++ b/Tests/TestUtils/TestCoordinator.swift @@ -208,7 +208,7 @@ extension TestCoordinator { try await service.latestBlockHeight() } - func reset(saplingActivation: BlockHeight, branchID: String, chainName: String) async throws { + func reset(saplingActivation: BlockHeight, startSaplingTreeSize: UInt32, startOrchardTreeSize: UInt32, branchID: String, chainName: String) async throws { await self.synchronizer.blockProcessor.stop() let config = await self.synchronizer.blockProcessor.config @@ -229,7 +229,7 @@ extension TestCoordinator { await self.synchronizer.blockProcessor.update(config: newConfig) - try service.reset(saplingActivation: saplingActivation, branchID: branchID, chainName: chainName) + try service.reset(saplingActivation: saplingActivation, startSaplingTreeSize: startSaplingTreeSize, startOrchardTreeSize: startOrchardTreeSize, branchID: branchID, chainName: chainName) } func getIncomingTransactions() throws -> [RawTransaction]? { diff --git a/Tests/TestUtils/proto/darkside.grpc.swift b/Tests/TestUtils/proto/darkside.grpc.swift index 91d17962..694d1e23 100644 --- a/Tests/TestUtils/proto/darkside.grpc.swift +++ b/Tests/TestUtils/proto/darkside.grpc.swift @@ -1069,3 +1069,696 @@ internal enum DarksideStreamerClientMetadata { } } +/// 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 +/// serves; transactions are placed into their corresponding blocks (by height). +/// +/// To build a server, implement a class that conforms to this protocol. +internal protocol DarksideStreamerProvider: CallHandlerProvider { + var interceptors: DarksideStreamerServerInterceptorFactoryProtocol? { get } + + /// Reset reverts all darksidewalletd state (active block range, latest height, + /// staged blocks and transactions) and lightwalletd state (cache) to empty, + /// the same as the initial state. This occurs synchronously and instantaneously; + /// no reorg happens in lightwalletd. This is good to do before each independent + /// test so that no state leaks from one test to another. + /// Also sets (some of) the values returned by GetLightdInfo(). The Sapling + /// activation height specified here must be where the block range starts. + func reset(request: DarksideMetaState, context: StatusOnlyCallContext) -> EventLoopFuture + + /// StageBlocksStream accepts a list of blocks and saves them into the blocks + /// staging area until ApplyStaged() is called; there is no immediate effect on + /// the mock zcashd. Blocks are hex-encoded. Order is important, see ApplyStaged. + func stageBlocksStream(context: UnaryResponseCallContext) -> EventLoopFuture<(StreamEvent) -> Void> + + /// StageBlocks is the same as StageBlocksStream() except the blocks are fetched + /// from the given URL. Blocks are one per line, hex-encoded (not JSON). + func stageBlocks(request: DarksideBlocksURL, context: StatusOnlyCallContext) -> EventLoopFuture + + /// StageBlocksCreate is like the previous two, except it creates 'count' + /// empty blocks at consecutive heights starting at height 'height'. The + /// 'nonce' is part of the header, so it contributes to the block hash; this + /// lets you create identical blocks (same transactions and height), but with + /// different hashes. + func stageBlocksCreate(request: DarksideEmptyBlocks, context: StatusOnlyCallContext) -> EventLoopFuture + + /// StageTransactionsStream stores the given transaction-height pairs in the + /// staging area until ApplyStaged() is called. Note that these transactions + /// are not returned by the production GetTransaction() gRPC until they + /// appear in a "mined" block (contained in the active blockchain presented + /// by the mock zcashd). + func stageTransactionsStream(context: UnaryResponseCallContext) -> EventLoopFuture<(StreamEvent) -> Void> + + /// StageTransactions is the same except the transactions are fetched from + /// the given url. They are all staged into the block at the given height. + /// Staging transactions to different heights requires multiple calls. + func stageTransactions(request: DarksideTransactionsURL, context: StatusOnlyCallContext) -> EventLoopFuture + + /// ApplyStaged iterates the list of blocks that were staged by the + /// StageBlocks*() gRPCs, in the order they were staged, and "merges" each + /// into the active, working blocks list that the mock zcashd is presenting + /// to lightwalletd. Even as each block is applied, the active list can't + /// have gaps; if the active block range is 1000-1006, and the staged block + /// range is 1003-1004, the resulting range is 1000-1004, with 1000-1002 + /// unchanged, blocks 1003-1004 from the new range, and 1005-1006 dropped. + /// + /// After merging all blocks, ApplyStaged() appends staged transactions (in + /// the order received) into each one's corresponding (by height) block + /// The staging area is then cleared. + /// + /// The argument specifies the latest block height that mock zcashd reports + /// (i.e. what's returned by GetLatestBlock). Note that ApplyStaged() can + /// also be used to simply advance the latest block height presented by mock + /// zcashd. That is, there doesn't need to be anything in the staging area. + func applyStaged(request: DarksideHeight, context: StatusOnlyCallContext) -> EventLoopFuture + + /// Calls to the production gRPC SendTransaction() store the transaction in + /// a separate area (not the staging area); this method returns all transactions + /// in this separate area, which is then cleared. The height returned + /// with each transaction is -1 (invalid) since these transactions haven't + /// been mined yet. The intention is that the transactions returned here can + /// then, for example, be given to StageTransactions() to get them "mined" + /// into a specified block on the next ApplyStaged(). + func getIncomingTransactions(request: Empty, context: StreamingResponseCallContext) -> EventLoopFuture + + /// Clear the incoming transaction pool. + func clearIncomingTransactions(request: Empty, context: StatusOnlyCallContext) -> EventLoopFuture + + /// Add a GetAddressUtxosReply entry to be returned by GetAddressUtxos(). + /// There is no staging or applying for these, very simple. + func addAddressUtxo(request: GetAddressUtxosReply, context: StatusOnlyCallContext) -> EventLoopFuture + + /// Clear the list of GetAddressUtxos entries (can't fail) + func clearAddressUtxo(request: Empty, context: StatusOnlyCallContext) -> EventLoopFuture + + /// Adds a GetTreeState to the tree state cache + func addTreeState(request: TreeState, context: StatusOnlyCallContext) -> EventLoopFuture + + /// Removes a GetTreeState for the given height from cache if present (can't fail) + func removeTreeState(request: BlockID, context: StatusOnlyCallContext) -> EventLoopFuture + + /// Clear the list of GetTreeStates entries (can't fail) + func clearAllTreeStates(request: Empty, context: StatusOnlyCallContext) -> EventLoopFuture +} + +extension DarksideStreamerProvider { + internal var serviceName: Substring { + return DarksideStreamerServerMetadata.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 "Reset": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeResetInterceptors() ?? [], + userFunction: self.reset(request:context:) + ) + + case "StageBlocksStream": + return ClientStreamingServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeStageBlocksStreamInterceptors() ?? [], + observerFactory: self.stageBlocksStream(context:) + ) + + case "StageBlocks": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeStageBlocksInterceptors() ?? [], + userFunction: self.stageBlocks(request:context:) + ) + + case "StageBlocksCreate": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeStageBlocksCreateInterceptors() ?? [], + userFunction: self.stageBlocksCreate(request:context:) + ) + + case "StageTransactionsStream": + return ClientStreamingServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeStageTransactionsStreamInterceptors() ?? [], + observerFactory: self.stageTransactionsStream(context:) + ) + + case "StageTransactions": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeStageTransactionsInterceptors() ?? [], + userFunction: self.stageTransactions(request:context:) + ) + + case "ApplyStaged": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeApplyStagedInterceptors() ?? [], + userFunction: self.applyStaged(request:context:) + ) + + case "GetIncomingTransactions": + return ServerStreamingServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetIncomingTransactionsInterceptors() ?? [], + userFunction: self.getIncomingTransactions(request:context:) + ) + + case "ClearIncomingTransactions": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeClearIncomingTransactionsInterceptors() ?? [], + userFunction: self.clearIncomingTransactions(request:context:) + ) + + case "AddAddressUtxo": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeAddAddressUtxoInterceptors() ?? [], + userFunction: self.addAddressUtxo(request:context:) + ) + + case "ClearAddressUtxo": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeClearAddressUtxoInterceptors() ?? [], + userFunction: self.clearAddressUtxo(request:context:) + ) + + case "AddTreeState": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeAddTreeStateInterceptors() ?? [], + userFunction: self.addTreeState(request:context:) + ) + + case "RemoveTreeState": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeRemoveTreeStateInterceptors() ?? [], + userFunction: self.removeTreeState(request:context:) + ) + + case "ClearAllTreeStates": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeClearAllTreeStatesInterceptors() ?? [], + userFunction: self.clearAllTreeStates(request:context:) + ) + + default: + return nil + } + } +} + +/// 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 +/// serves; transactions are placed into their corresponding blocks (by height). +/// +/// To implement a server, implement an object which conforms to this protocol. +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +internal protocol DarksideStreamerAsyncProvider: CallHandlerProvider, Sendable { + static var serviceDescriptor: GRPCServiceDescriptor { get } + var interceptors: DarksideStreamerServerInterceptorFactoryProtocol? { get } + + /// Reset reverts all darksidewalletd state (active block range, latest height, + /// staged blocks and transactions) and lightwalletd state (cache) to empty, + /// the same as the initial state. This occurs synchronously and instantaneously; + /// no reorg happens in lightwalletd. This is good to do before each independent + /// test so that no state leaks from one test to another. + /// Also sets (some of) the values returned by GetLightdInfo(). The Sapling + /// activation height specified here must be where the block range starts. + func reset( + request: DarksideMetaState, + context: GRPCAsyncServerCallContext + ) async throws -> Empty + + /// StageBlocksStream accepts a list of blocks and saves them into the blocks + /// staging area until ApplyStaged() is called; there is no immediate effect on + /// the mock zcashd. Blocks are hex-encoded. Order is important, see ApplyStaged. + func stageBlocksStream( + requestStream: GRPCAsyncRequestStream, + context: GRPCAsyncServerCallContext + ) async throws -> Empty + + /// StageBlocks is the same as StageBlocksStream() except the blocks are fetched + /// from the given URL. Blocks are one per line, hex-encoded (not JSON). + func stageBlocks( + request: DarksideBlocksURL, + context: GRPCAsyncServerCallContext + ) async throws -> Empty + + /// StageBlocksCreate is like the previous two, except it creates 'count' + /// empty blocks at consecutive heights starting at height 'height'. The + /// 'nonce' is part of the header, so it contributes to the block hash; this + /// lets you create identical blocks (same transactions and height), but with + /// different hashes. + func stageBlocksCreate( + request: DarksideEmptyBlocks, + context: GRPCAsyncServerCallContext + ) async throws -> Empty + + /// StageTransactionsStream stores the given transaction-height pairs in the + /// staging area until ApplyStaged() is called. Note that these transactions + /// are not returned by the production GetTransaction() gRPC until they + /// appear in a "mined" block (contained in the active blockchain presented + /// by the mock zcashd). + func stageTransactionsStream( + requestStream: GRPCAsyncRequestStream, + context: GRPCAsyncServerCallContext + ) async throws -> Empty + + /// StageTransactions is the same except the transactions are fetched from + /// the given url. They are all staged into the block at the given height. + /// Staging transactions to different heights requires multiple calls. + func stageTransactions( + request: DarksideTransactionsURL, + context: GRPCAsyncServerCallContext + ) async throws -> Empty + + /// ApplyStaged iterates the list of blocks that were staged by the + /// StageBlocks*() gRPCs, in the order they were staged, and "merges" each + /// into the active, working blocks list that the mock zcashd is presenting + /// to lightwalletd. Even as each block is applied, the active list can't + /// have gaps; if the active block range is 1000-1006, and the staged block + /// range is 1003-1004, the resulting range is 1000-1004, with 1000-1002 + /// unchanged, blocks 1003-1004 from the new range, and 1005-1006 dropped. + /// + /// After merging all blocks, ApplyStaged() appends staged transactions (in + /// the order received) into each one's corresponding (by height) block + /// The staging area is then cleared. + /// + /// The argument specifies the latest block height that mock zcashd reports + /// (i.e. what's returned by GetLatestBlock). Note that ApplyStaged() can + /// also be used to simply advance the latest block height presented by mock + /// zcashd. That is, there doesn't need to be anything in the staging area. + func applyStaged( + request: DarksideHeight, + context: GRPCAsyncServerCallContext + ) async throws -> Empty + + /// Calls to the production gRPC SendTransaction() store the transaction in + /// a separate area (not the staging area); this method returns all transactions + /// in this separate area, which is then cleared. The height returned + /// with each transaction is -1 (invalid) since these transactions haven't + /// been mined yet. The intention is that the transactions returned here can + /// then, for example, be given to StageTransactions() to get them "mined" + /// into a specified block on the next ApplyStaged(). + func getIncomingTransactions( + request: Empty, + responseStream: GRPCAsyncResponseStreamWriter, + context: GRPCAsyncServerCallContext + ) async throws + + /// Clear the incoming transaction pool. + func clearIncomingTransactions( + request: Empty, + context: GRPCAsyncServerCallContext + ) async throws -> Empty + + /// Add a GetAddressUtxosReply entry to be returned by GetAddressUtxos(). + /// There is no staging or applying for these, very simple. + func addAddressUtxo( + request: GetAddressUtxosReply, + context: GRPCAsyncServerCallContext + ) async throws -> Empty + + /// Clear the list of GetAddressUtxos entries (can't fail) + func clearAddressUtxo( + request: Empty, + context: GRPCAsyncServerCallContext + ) async throws -> Empty + + /// Adds a GetTreeState to the tree state cache + func addTreeState( + request: TreeState, + context: GRPCAsyncServerCallContext + ) async throws -> Empty + + /// Removes a GetTreeState for the given height from cache if present (can't fail) + func removeTreeState( + request: BlockID, + context: GRPCAsyncServerCallContext + ) async throws -> Empty + + /// Clear the list of GetTreeStates entries (can't fail) + func clearAllTreeStates( + request: Empty, + context: GRPCAsyncServerCallContext + ) async throws -> Empty +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension DarksideStreamerAsyncProvider { + internal static var serviceDescriptor: GRPCServiceDescriptor { + return DarksideStreamerServerMetadata.serviceDescriptor + } + + internal var serviceName: Substring { + return DarksideStreamerServerMetadata.serviceDescriptor.fullName[...] + } + + internal var interceptors: DarksideStreamerServerInterceptorFactoryProtocol? { + return nil + } + + internal func handle( + method name: Substring, + context: CallHandlerContext + ) -> GRPCServerHandlerProtocol? { + switch name { + case "Reset": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeResetInterceptors() ?? [], + wrapping: { try await self.reset(request: $0, context: $1) } + ) + + case "StageBlocksStream": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeStageBlocksStreamInterceptors() ?? [], + wrapping: { try await self.stageBlocksStream(requestStream: $0, context: $1) } + ) + + case "StageBlocks": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeStageBlocksInterceptors() ?? [], + wrapping: { try await self.stageBlocks(request: $0, context: $1) } + ) + + case "StageBlocksCreate": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeStageBlocksCreateInterceptors() ?? [], + wrapping: { try await self.stageBlocksCreate(request: $0, context: $1) } + ) + + case "StageTransactionsStream": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeStageTransactionsStreamInterceptors() ?? [], + wrapping: { try await self.stageTransactionsStream(requestStream: $0, context: $1) } + ) + + case "StageTransactions": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeStageTransactionsInterceptors() ?? [], + wrapping: { try await self.stageTransactions(request: $0, context: $1) } + ) + + case "ApplyStaged": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeApplyStagedInterceptors() ?? [], + wrapping: { try await self.applyStaged(request: $0, context: $1) } + ) + + case "GetIncomingTransactions": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeGetIncomingTransactionsInterceptors() ?? [], + wrapping: { try await self.getIncomingTransactions(request: $0, responseStream: $1, context: $2) } + ) + + case "ClearIncomingTransactions": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeClearIncomingTransactionsInterceptors() ?? [], + wrapping: { try await self.clearIncomingTransactions(request: $0, context: $1) } + ) + + case "AddAddressUtxo": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeAddAddressUtxoInterceptors() ?? [], + wrapping: { try await self.addAddressUtxo(request: $0, context: $1) } + ) + + case "ClearAddressUtxo": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeClearAddressUtxoInterceptors() ?? [], + wrapping: { try await self.clearAddressUtxo(request: $0, context: $1) } + ) + + case "AddTreeState": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeAddTreeStateInterceptors() ?? [], + wrapping: { try await self.addTreeState(request: $0, context: $1) } + ) + + case "RemoveTreeState": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeRemoveTreeStateInterceptors() ?? [], + wrapping: { try await self.removeTreeState(request: $0, context: $1) } + ) + + case "ClearAllTreeStates": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeClearAllTreeStatesInterceptors() ?? [], + wrapping: { try await self.clearAllTreeStates(request: $0, context: $1) } + ) + + default: + return nil + } + } +} + +internal protocol DarksideStreamerServerInterceptorFactoryProtocol: Sendable { + + /// - Returns: Interceptors to use when handling 'reset'. + /// Defaults to calling `self.makeInterceptors()`. + func makeResetInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'stageBlocksStream'. + /// Defaults to calling `self.makeInterceptors()`. + func makeStageBlocksStreamInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'stageBlocks'. + /// Defaults to calling `self.makeInterceptors()`. + func makeStageBlocksInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'stageBlocksCreate'. + /// Defaults to calling `self.makeInterceptors()`. + func makeStageBlocksCreateInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'stageTransactionsStream'. + /// Defaults to calling `self.makeInterceptors()`. + func makeStageTransactionsStreamInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'stageTransactions'. + /// Defaults to calling `self.makeInterceptors()`. + func makeStageTransactionsInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'applyStaged'. + /// Defaults to calling `self.makeInterceptors()`. + func makeApplyStagedInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'getIncomingTransactions'. + /// Defaults to calling `self.makeInterceptors()`. + func makeGetIncomingTransactionsInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'clearIncomingTransactions'. + /// Defaults to calling `self.makeInterceptors()`. + func makeClearIncomingTransactionsInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'addAddressUtxo'. + /// Defaults to calling `self.makeInterceptors()`. + func makeAddAddressUtxoInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'clearAddressUtxo'. + /// Defaults to calling `self.makeInterceptors()`. + func makeClearAddressUtxoInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'addTreeState'. + /// Defaults to calling `self.makeInterceptors()`. + func makeAddTreeStateInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'removeTreeState'. + /// Defaults to calling `self.makeInterceptors()`. + func makeRemoveTreeStateInterceptors() -> [ServerInterceptor] + + /// - Returns: Interceptors to use when handling 'clearAllTreeStates'. + /// Defaults to calling `self.makeInterceptors()`. + func makeClearAllTreeStatesInterceptors() -> [ServerInterceptor] +} + +internal enum DarksideStreamerServerMetadata { + internal static let serviceDescriptor = GRPCServiceDescriptor( + name: "DarksideStreamer", + fullName: "cash.z.wallet.sdk.rpc.DarksideStreamer", + methods: [ + DarksideStreamerServerMetadata.Methods.reset, + DarksideStreamerServerMetadata.Methods.stageBlocksStream, + DarksideStreamerServerMetadata.Methods.stageBlocks, + DarksideStreamerServerMetadata.Methods.stageBlocksCreate, + DarksideStreamerServerMetadata.Methods.stageTransactionsStream, + DarksideStreamerServerMetadata.Methods.stageTransactions, + DarksideStreamerServerMetadata.Methods.applyStaged, + DarksideStreamerServerMetadata.Methods.getIncomingTransactions, + DarksideStreamerServerMetadata.Methods.clearIncomingTransactions, + DarksideStreamerServerMetadata.Methods.addAddressUtxo, + DarksideStreamerServerMetadata.Methods.clearAddressUtxo, + DarksideStreamerServerMetadata.Methods.addTreeState, + DarksideStreamerServerMetadata.Methods.removeTreeState, + DarksideStreamerServerMetadata.Methods.clearAllTreeStates, + ] + ) + + internal enum Methods { + internal static let reset = GRPCMethodDescriptor( + name: "Reset", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/Reset", + type: GRPCCallType.unary + ) + + internal static let stageBlocksStream = GRPCMethodDescriptor( + name: "StageBlocksStream", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/StageBlocksStream", + type: GRPCCallType.clientStreaming + ) + + internal static let stageBlocks = GRPCMethodDescriptor( + name: "StageBlocks", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/StageBlocks", + type: GRPCCallType.unary + ) + + internal static let stageBlocksCreate = GRPCMethodDescriptor( + name: "StageBlocksCreate", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/StageBlocksCreate", + type: GRPCCallType.unary + ) + + internal static let stageTransactionsStream = GRPCMethodDescriptor( + name: "StageTransactionsStream", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/StageTransactionsStream", + type: GRPCCallType.clientStreaming + ) + + internal static let stageTransactions = GRPCMethodDescriptor( + name: "StageTransactions", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/StageTransactions", + type: GRPCCallType.unary + ) + + internal static let applyStaged = GRPCMethodDescriptor( + name: "ApplyStaged", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/ApplyStaged", + type: GRPCCallType.unary + ) + + internal static let getIncomingTransactions = GRPCMethodDescriptor( + name: "GetIncomingTransactions", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/GetIncomingTransactions", + type: GRPCCallType.serverStreaming + ) + + internal static let clearIncomingTransactions = GRPCMethodDescriptor( + name: "ClearIncomingTransactions", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/ClearIncomingTransactions", + type: GRPCCallType.unary + ) + + internal static let addAddressUtxo = GRPCMethodDescriptor( + name: "AddAddressUtxo", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/AddAddressUtxo", + type: GRPCCallType.unary + ) + + internal static let clearAddressUtxo = GRPCMethodDescriptor( + name: "ClearAddressUtxo", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/ClearAddressUtxo", + type: GRPCCallType.unary + ) + + internal static let addTreeState = GRPCMethodDescriptor( + name: "AddTreeState", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/AddTreeState", + type: GRPCCallType.unary + ) + + internal static let removeTreeState = GRPCMethodDescriptor( + name: "RemoveTreeState", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/RemoveTreeState", + type: GRPCCallType.unary + ) + + internal static let clearAllTreeStates = GRPCMethodDescriptor( + name: "ClearAllTreeStates", + path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/ClearAllTreeStates", + type: GRPCCallType.unary + ) + } +} diff --git a/Tests/TestUtils/proto/darkside.pb.swift b/Tests/TestUtils/proto/darkside.pb.swift index 20479ebe..f8d1291e 100644 --- a/Tests/TestUtils/proto/darkside.pb.swift +++ b/Tests/TestUtils/proto/darkside.pb.swift @@ -35,6 +35,10 @@ struct DarksideMetaState { var chainName: String = String() + var startSaplingCommitmentTreeSize: UInt32 = 0 + + var startOrchardCommitmentTreeSize: UInt32 = 0 + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -131,6 +135,8 @@ extension DarksideMetaState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem 1: .same(proto: "saplingActivation"), 2: .same(proto: "branchID"), 3: .same(proto: "chainName"), + 4: .same(proto: "startSaplingCommitmentTreeSize"), + 5: .same(proto: "startOrchardCommitmentTreeSize"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -142,6 +148,8 @@ extension DarksideMetaState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem case 1: try { try decoder.decodeSingularInt32Field(value: &self.saplingActivation) }() case 2: try { try decoder.decodeSingularStringField(value: &self.branchID) }() case 3: try { try decoder.decodeSingularStringField(value: &self.chainName) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self.startSaplingCommitmentTreeSize) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self.startOrchardCommitmentTreeSize) }() default: break } } @@ -157,6 +165,12 @@ extension DarksideMetaState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem if !self.chainName.isEmpty { try visitor.visitSingularStringField(value: self.chainName, fieldNumber: 3) } + if self.startSaplingCommitmentTreeSize != 0 { + try visitor.visitSingularUInt32Field(value: self.startSaplingCommitmentTreeSize, fieldNumber: 4) + } + if self.startOrchardCommitmentTreeSize != 0 { + try visitor.visitSingularUInt32Field(value: self.startOrchardCommitmentTreeSize, fieldNumber: 5) + } try unknownFields.traverse(visitor: &visitor) } @@ -164,6 +178,8 @@ extension DarksideMetaState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem if lhs.saplingActivation != rhs.saplingActivation {return false} if lhs.branchID != rhs.branchID {return false} if lhs.chainName != rhs.chainName {return false} + if lhs.startSaplingCommitmentTreeSize != rhs.startSaplingCommitmentTreeSize {return false} + if lhs.startOrchardCommitmentTreeSize != rhs.startOrchardCommitmentTreeSize {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 9cb94439..5468e769 100644 --- a/Tests/TestUtils/proto/darkside.proto +++ b/Tests/TestUtils/proto/darkside.proto @@ -12,6 +12,8 @@ message DarksideMetaState { int32 saplingActivation = 1; string branchID = 2; string chainName = 3; + uint32 startSaplingCommitmentTreeSize = 4; + uint32 startOrchardCommitmentTreeSize = 5; } // A block is a hex-encoded string.