Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#1452] TX Resubmission-the wallet has to periodically resubmit unmined transactions #1454

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

## Checkpoints

Mainnet
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
LukasKorba marked this conversation as resolved.
Show resolved Hide resolved

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,12 @@ class SendViewController: UIViewController {
// swiftlint:disable:next force_try
memo: try! self.memoField.text.asMemo()
)
KRProgressHUD.dismiss()
loggerProxy.info("transaction created: \(pendingTransaction)")
KRProgressHUD.dismiss()
} catch {
fail(error)
loggerProxy.error("SEND FAILED: \(error)")
KRProgressHUD.dismiss()
fail(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)
LukasKorba marked this conversation as resolved.
Show resolved Hide resolved
}

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,75 @@
//
// 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 {
logger.info("TxResubmissionAction check started at \(latestBlockHeight) height.")
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 {
logger.info("TxResubmissionAction trying to resubmit transaction \(transaction.rawID.toHexStringTxId()).")
let encodedTransaction = try transaction.encodedTransaction()

try await transactionEncoder.submit(transaction: encodedTransaction)
}
} 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
12 changes: 12 additions & 0 deletions Sources/ZcashLightClientKit/DAO/TransactionDao.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,18 @@ class TransactionSQLDAO: TransactionRepository {
return try await execute(query) { try ZcashTransaction.Overview(row: $0) }
}

func findForResubmission(upTo: BlockHeight) async throws -> [ZcashTransaction.Overview] {
let query = transactionsView
.filter(
LukasKorba marked this conversation as resolved.
Show resolved Hide resolved
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
10 changes: 8 additions & 2 deletions docs/cbp_state_machine.puml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ updateChainTip -[#green,bold]-> download : Processing of scan range continues

processSuggestedScanRanges : ProcessSuggestedScanRangesAction
processSuggestedScanRanges -[#green,bold]-> download : Scan range available to process
processSuggestedScanRanges -[#green,bold]-> finished : Scan ranges FULLY processed
processSuggestedScanRanges -[#green,bold]-> txResubmissionA : Scan ranges FULLY processed

txResubmissionA : TxResubmissionAction
txResubmissionA -[#green,bold]-> finished

txResubmissionB : TxResubmissionAction
txResubmissionB -[#green,bold]-> updateChainTip

download : DownloadAction
download -[#green,bold]-> scan
Expand All @@ -42,7 +48,7 @@ clearAlreadyScannedBlocks : ClearAlreadyScannedBlocksAction
clearAlreadyScannedBlocks -[#green,bold]-> enhance

enhance : EnhanceAction
enhance -[#green,bold]-> updateChainTip : Range NOT finished
enhance -[#green,bold]-> txResubmissionB : Range NOT finished
enhance -[#green,bold]-> clearCache : Range finished, clear cache and check the scan ranges

note right of enhance
Expand Down
Binary file modified docs/images/cbp_state_machine.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading