Skip to content

Commit

Permalink
[Electric-Coin-Company#1452] TX Resubmission-the wallet has to period…
Browse files Browse the repository at this point in the history
…ically resubmit unmined transactions

- functional version is done

[Electric-Coin-Company#1452] TX Resubmission-the wallet has to periodically resubmit unmined transactions

- code cleanup

[Electric-Coin-Company#1452] TX Resubmission-the wallet has to periodically resubmit unmined transactions

- changelog updated

[Electric-Coin-Company#1452] TX Resubmission-the wallet has to periodically resubmit unmined transactions

- unit tests fixed
- mocks generated
  • Loading branch information
LukasKorba committed Jun 26, 2024
1 parent d9a706d commit d6526c9
Show file tree
Hide file tree
Showing 15 changed files with 153 additions and 7 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

# Unreleased

## Added

### [#452] TX Resubmission-the wallet has to periodically resubmit unmined transactions
The Compact block processor's state machine has been extended to check whether there are any unmined and unexpired transactions, and it attempts to resubmit such transactions every 5 minutes.

# 2.1.10 - 2024-06-14

## Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ enum DemoAppConfig {
let seed: [UInt8]
}

static let host = ZcashSDK.isMainnet ? "mainnet.lightwalletd.com" : "lightwalletd.testnet.electriccoin.co"
static let port: Int = 9067
static let host = ZcashSDK.isMainnet ? "zec.rocks" : "lightwalletd.testnet.electriccoin.co"
static let port: Int = 443

static let defaultBirthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 935000 : 1386000

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ class SendViewController: UIViewController {
KRProgressHUD.dismiss()
loggerProxy.info("transaction created: \(pendingTransaction)")
} catch {
KRProgressHUD.dismiss()
fail(error)
loggerProxy.error("SEND FAILED: \(error)")
}
Expand Down
1 change: 1 addition & 0 deletions Sources/ZcashLightClientKit/Block/Actions/Action.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ enum CBPState: CaseIterable {
case fetchUTXO
case handleSaplingParams
case clearCache
case txResubmission
case finished
case failed
case stopped
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ final class EnhanceAction {
if lastScannedHeight >= latestBlockHeight {
await context.update(state: .clearCache)
} else {
await context.update(state: .updateChainTip)
await context.update(state: .txResubmission)
}

return context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ extension ProcessSuggestedScanRangesAction: Action {

await context.update(state: .download)
} else {
await context.update(state: .finished)
await context.update(state: .txResubmission)
}

return context
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// TxResubmissionAction.swift
//
//
// Created by Lukas Korba on 06-17-2024.
//

import Foundation

final class TxResubmissionAction {
private enum Constants {
static let thresholdToTrigger = TimeInterval(300.0)
}

var latestResolvedTime: TimeInterval = 0
let transactionRepository: TransactionRepository
let transactionEncoder: TransactionEncoder
let logger: Logger

init(container: DIContainer) {
transactionRepository = container.resolve(TransactionRepository.self)
transactionEncoder = container.resolve(TransactionEncoder.self)
logger = container.resolve(Logger.self)
}
}

extension TxResubmissionAction: Action {
var removeBlocksCacheWhenFailed: Bool { true }

func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
let latestBlockHeight = await context.syncControlData.latestBlockHeight

// find all candidates for the resubmission
do {
let transactions = try await transactionRepository.findForResubmission(upTo: latestBlockHeight)

// no candidates, update the time and continue with the next action
if transactions.isEmpty {
latestResolvedTime = Date().timeIntervalSince1970
} else {
let now = Date().timeIntervalSince1970
let diff = now - latestResolvedTime

// the last time resubmission was triggered is more than 5 minutes ago so try again
if diff > Constants.thresholdToTrigger {
// resubmission
do {
for transaction in transactions {
let encodedTransaction = try transaction.encodedTransaction()

try await transactionEncoder.submit(transaction: encodedTransaction)
logger.info("TxResubmissionAction trying to resubmit transaction")
}
} catch {
logger.error("TxResubmissionAction failed to resubmit candidates")
}

latestResolvedTime = Date().timeIntervalSince1970
}
}
} catch {
logger.error("TxResubmissionAction failed to find candidates")
}

if await context.prevState == .enhance {
await context.update(state: .updateChainTip)
} else {
await context.update(state: .finished)
}
return context
}

func stop() async { }
}
4 changes: 4 additions & 0 deletions Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ actor CompactBlockProcessor {
action = SaplingParamsAction(container: container)
case .clearCache:
action = ClearCacheAction(container: container)
case .txResubmission:
action = TxResubmissionAction(container: container)
case .finished, .failed, .stopped, .idle:
return nil
}
Expand Down Expand Up @@ -667,6 +669,8 @@ extension CompactBlockProcessor {
break
case .stopped:
break
case .txResubmission:
break
}
}

Expand Down
13 changes: 13 additions & 0 deletions Sources/ZcashLightClientKit/DAO/TransactionDao.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,19 @@ class TransactionSQLDAO: TransactionRepository {
return try await execute(query) { try ZcashTransaction.Overview(row: $0) }
}

func findForResubmission(upTo: BlockHeight) async throws -> [ZcashTransaction.Overview] {
let query = transactionsView
.order((ZcashTransaction.Overview.Column.minedHeight ?? BlockHeight.max).desc)
.filter(
ZcashTransaction.Overview.Column.minedHeight == nil &&
ZcashTransaction.Overview.Column.expiryHeight > upTo
)
.filterQueryFor(kind: .sent)
.limit(Int.max)

return try await execute(query) { try ZcashTransaction.Overview(row: $0) }
}

func findReceived(offset: Int, limit: Int) async throws -> [ZcashTransaction.Overview] {
let query = transactionsView
.filterQueryFor(kind: .received)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ protocol TransactionRepository {
func findPendingTransactions(latestHeight: BlockHeight, offset: Int, limit: Int) async throws -> [ZcashTransaction.Overview]
func findReceived(offset: Int, limit: Int) async throws -> [ZcashTransaction.Overview]
func findSent(offset: Int, limit: Int) async throws -> [ZcashTransaction.Overview]
func findForResubmission(upTo: BlockHeight) async throws -> [ZcashTransaction.Overview]
// sourcery: mockedName="findMemosForRawID"
func findMemos(for rawID: Data) async throws -> [Memo]
// sourcery: mockedName="findMemosForZcashTransaction"
Expand Down
19 changes: 19 additions & 0 deletions Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,25 @@ enum Dependencies {
container.register(type: ZcashFileManager.self, isSingleton: true) { _ in
FileManager.default
}

container.register(type: TransactionEncoder.self, isSingleton: true) { di in
let service = di.resolve(LightWalletService.self)
let logger = di.resolve(Logger.self)
let transactionRepository = di.resolve(TransactionRepository.self)
let rustBackend = di.resolve(ZcashRustBackendWelding.self)

return WalletTransactionEncoder(
rustBackend: rustBackend,
dataDb: urls.dataDbURL,
fsBlockDbRoot: urls.fsBlockDbRoot,
service: service,
repository: transactionRepository,
outputParams: urls.outputParamsURL,
spendParams: urls.spendParamsURL,
networkType: networkType,
logger: logger
)
}
}

static func setupCompactBlockProcessor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ final class EnhanceActionTests: ZcashTestCase {
XCTAssertTrue(acResult == .true, "Check of state failed with '\(acResult)'")
}

func testEnhanceAction_decideWhatToDoNext_UpdateChainTipExpected() async throws {
func testEnhanceAction_decideWhatToDoNext_txResubmissionExpected() async throws {
let enhanceAction = setupAction()
underlyingDownloadRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
underlyingScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000))
Expand All @@ -55,7 +55,7 @@ final class EnhanceActionTests: ZcashTestCase {

let nextContext = await enhanceAction.decideWhatToDoNext(context: syncContext, lastScannedHeight: 1500)

let acResult = nextContext.checkStateIs(.updateChainTip)
let acResult = nextContext.checkStateIs(.txResubmission)
XCTAssertTrue(acResult == .true, "Check of state failed with '\(acResult)'")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ final class ProcessSuggestedScanRangesActionTests: ZcashTestCase {

let nextContext = try await processSuggestedScanRangesActionAction.run(with: context) { _ in }

let acResult = nextContext.checkStateIs(.finished)
let acResult = nextContext.checkStateIs(.txResubmission)
XCTAssertTrue(acResult == .true, "Check of state failed with '\(acResult)'")
} catch {
XCTFail("testProcessSuggestedScanRangesAction_EmptyScanRanges is not expected to fail. \(error)")
Expand Down
4 changes: 4 additions & 0 deletions Tests/TestUtils/MockTransactionRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ extension MockTransactionRepository.Kind: Equatable {}

// MARK: - TransactionRepository
extension MockTransactionRepository: TransactionRepository {
func findForResubmission(upTo: ZcashLightClientKit.BlockHeight) async throws -> [ZcashLightClientKit.ZcashTransaction.Overview] {
[]
}

func getTransactionOutputs(for rawID: Data) async throws -> [ZcashLightClientKit.ZcashTransaction.Output] {
[]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2116,6 +2116,30 @@ class TransactionRepositoryMock: TransactionRepository {
}
}

// MARK: - findForResubmission

var findForResubmissionUpToThrowableError: Error?
var findForResubmissionUpToCallsCount = 0
var findForResubmissionUpToCalled: Bool {
return findForResubmissionUpToCallsCount > 0
}
var findForResubmissionUpToReceivedUpTo: BlockHeight?
var findForResubmissionUpToReturnValue: [ZcashTransaction.Overview]!
var findForResubmissionUpToClosure: ((BlockHeight) async throws -> [ZcashTransaction.Overview])?

func findForResubmission(upTo: BlockHeight) async throws -> [ZcashTransaction.Overview] {
if let error = findForResubmissionUpToThrowableError {
throw error
}
findForResubmissionUpToCallsCount += 1
findForResubmissionUpToReceivedUpTo = upTo
if let closure = findForResubmissionUpToClosure {
return try await closure(upTo)
} else {
return findForResubmissionUpToReturnValue
}
}

// MARK: - findMemos

var findMemosForRawIDThrowableError: Error?
Expand Down

0 comments on commit d6526c9

Please sign in to comment.