Skip to content

Commit

Permalink
[Electric-Coin-Company#1475] Adopt transaction data requests
Browse files Browse the repository at this point in the history
- RustBackend extended for the new 2 rust methods 'transactionDataRequests' and 'setTransactionStatus'
- lightwalletservice extended to add a new method 'getTaddressTxids'
- Enhance action has been refactored to handle transactionDataRequests

[Electric-Coin-Company#1475] Adopt transaction data requests

- fixes

[Electric-Coin-Company#1475] Adopt transaction data requests

- Error codes for specific rust and service errors defined
- Fix for the txId

[Electric-Coin-Company#1475] Adopt transaction data requests

- Checkpoints added
- Code cleanup
  • Loading branch information
LukasKorba committed Aug 16, 2024
1 parent a3e15f0 commit 5f63581
Show file tree
Hide file tree
Showing 35 changed files with 554 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"revision" : "4de1b42f99aebfc5e4f0340da8a66a4f719db9a6"
"branch" : "ffi_transaction_requests_preview",
"revision" : "784b45a8998f1371bb5cbb2e1bbb06ab958f9399"
}
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ enum DemoAppConfig {
static let host = ZcashSDK.isMainnet ? "zec.rocks" : "lightwalletd.testnet.electriccoin.co"
static let port: Int = 443

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

// static let defaultSeed = try! Mnemonic.deterministicSeedBytes(from: """
// wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame
// """)

static let defaultSeed = try! Mnemonic.deterministicSeedBytes(from: """
live combine flight accident slow soda mind bright absent bid hen shy decade biology amazing mix enlist ensure biology rhythm snap duty soap armor
rhythm tide ignore unknown penalty helmet square secret nature sea ethics hint only since behind spawn crater ozone festival birth dynamic idea ceiling arch
""")

static let otherSynchronizers: [SynchronizerInitData] = [
Expand Down
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ let package = Package(
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1"),
// .package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", exact: "0.8.1")
// Compiled from 2516a94f8bdc540d951c38b66e9c07e2b8c29cb4
.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", revision: "4de1b42f99aebfc5e4f0340da8a66a4f719db9a6")
// .package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", branch: "ffi_transaction_requests_preview")
.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", revision: "784b45a8998f1371bb5cbb2e1bbb06ab958f9399")
],
targets: [
.target(
Expand Down
192 changes: 156 additions & 36 deletions Sources/ZcashLightClientKit/Block/Enhance/BlockEnhancer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,53 @@ struct BlockEnhancerImpl {
let rustBackend: ZcashRustBackendWelding
let transactionRepository: TransactionRepository
let metrics: SDKMetrics
let service: LightWalletService
let logger: Logger

private func enhance(transaction: ZcashTransaction.Overview) async throws -> ZcashTransaction.Overview {
logger.debug("Zoom.... Enhance... Tx: \(transaction.rawID.toHexStringTxId())")
private func enhance(txId: Data) async throws -> ZcashTransaction.Overview {
//logger.debug("Zoom.... Enhance... Tx: \(transaction.rawID.toHexStringTxId())")

let fetchedTransaction = try await blockDownloaderService.fetchTransaction(txId: transaction.rawID)
let fetchedTransaction = try await blockDownloaderService.fetchTransaction(txId: txId)

let transactionID = fetchedTransaction.rawID.toHexStringTxId()
let block = String(describing: transaction.minedHeight)
logger.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)")

try await rustBackend.decryptAndStoreTransaction(
txBytes: fetchedTransaction.raw.bytes,
minedHeight: Int32(fetchedTransaction.minedHeight)
)
//fetchedTransaction

// fetchTransaction needs to change to return 3 possible states of the call, based on rawID

// based on the result, it calls either decryptAndStoreTransaction() or NEW setTransactionStatus()

// not recognized -> setTransactionStatus // txidNotRecognized
// recognized -> decryptAndStoreTransaction // mined
// status req -> setTransactionStatus //notInMainChain

// var transactionStatus: TransactionStatus?

if fetchedTransaction.minedHeight == -1 {
print("__LD enhance .txidNotRecognized")
try await rustBackend.setTransactionStatus(txId: fetchedTransaction.rawID, status: .txidNotRecognized)
// transactionStatus = .txidNotRecognized
} else if fetchedTransaction.minedHeight == 0 {
print("__LD enhance .notInMainChain")
try await rustBackend.setTransactionStatus(txId: fetchedTransaction.rawID, status: .notInMainChain)
// transactionStatus = .notInMainChain
} else if fetchedTransaction.minedHeight > 0 {
print("__LD enhance .mined \(fetchedTransaction.minedHeight)")
//try await rustBackend.setTransactionStatus(txId: fetchedTransaction.rawID, status: .mined(fetchedTransaction.minedHeight))
// transactionStatus = .mined(fetchedTransaction.minedHeight)
try await rustBackend.decryptAndStoreTransaction(
txBytes: fetchedTransaction.raw.bytes,
minedHeight: Int32(fetchedTransaction.minedHeight)
)
}


// let transactionID = fetchedTransaction.rawID.toHexStringTxId()
// let block = String(describing: transaction.minedHeight)
// logger.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)")
//
// try await rustBackend.decryptAndStoreTransaction(
// txBytes: fetchedTransaction.raw.bytes,
// minedHeight: Int32(fetchedTransaction.minedHeight)
// )

return try await transactionRepository.find(rawID: fetchedTransaction.rawID)
}
Expand All @@ -92,54 +124,142 @@ extension BlockEnhancerImpl: BlockEnhancer {
// fetch transactions
do {
let startTime = Date()
let transactions = try await transactionRepository.find(in: range, limit: Int.max, kind: .all)
//let transactions = try await transactionRepository.find(in: range, limit: Int.max, kind: .all)

let transactionDataRequests = try await rustBackend.transactionDataRequests()

guard !transactions.isEmpty else {
logger.debug("no transactions detected on range: \(range.lowerBound)...\(range.upperBound)")
logger.sync("No transactions detected on range: \(range.lowerBound)...\(range.upperBound)")
return nil
}
// guard !transactionDataRequests.isEmpty else {
// logger.debug("no transactions detected on range: \(range.lowerBound)...\(range.upperBound)")
// logger.sync("No transactions detected on range: \(range.lowerBound)...\(range.upperBound)")
// return nil
// }

let chainTipHeight = try await blockDownloaderService.latestBlockHeight()

let newlyMinedLowerBound = chainTipHeight - ZcashSDK.expiryOffset

let newlyMinedRange = newlyMinedLowerBound...chainTipHeight

for index in 0 ..< transactions.count {
let transaction = transactions[index]
for index in 0 ..< transactionDataRequests.count {
let transactionDataRequest = transactionDataRequests[index]
var retry = true

//print("__LD transactionDataRequest \(transactionDataRequest)")

while retry && retries < maxRetries {
try Task.checkCancellation()
do {
let confirmedTx = try await enhance(transaction: transaction)
retry = false
switch transactionDataRequest {
case .getStatus(let txId), .enhancement(let txId):
if case TransactionDataRequest.getStatus(let txId) = transactionDataRequest {
print("__LD TDR: getStatus \(txId.map { String(format: "%02x", $0) }.joined()) \(txId)")
}
if case TransactionDataRequest.enhancement(let txId) = transactionDataRequest {
print("__LD TDR: enhancement \(txId.map { String(format: "%02x", $0) }.joined()) \(txId)")
}

let confirmedTx = try await enhance(txId: txId.data)
retry = false

let progress = EnhancementProgress(
totalTransactions: transactionDataRequests.count,
enhancedTransactions: index + 1,
lastFoundTransaction: confirmedTx,
range: range,
newlyMined: confirmedTx.isSentTransaction && newlyMinedRange.contains(confirmedTx.minedHeight ?? 0)
)

await didEnhance(progress)
case .spendsFromAddress(let sfa):
print("__LD TDR: spendsFromAddress \(transactionDataRequest)")
var filter = TransparentAddressBlockFilter()
filter.address = sfa.address
filter.range = BlockRange(startHeight: Int(sfa.blockRangeStart), endHeight: Int(sfa.blockRangeEnd))
let stream = service.getTaddressTxids(filter)

for try await rawTransaction in stream {
//print("__LD count \(rawTransaction.data.bytes.count)")
print("__LD spendsFromAddress decryptAndStoreTransaction [\(rawTransaction.data.bytes.count)] \(rawTransaction.height)")

try await rustBackend.decryptAndStoreTransaction(
txBytes: rawTransaction.data.bytes,
minedHeight: Int32(rawTransaction.height)
)
}
retry = false
}
//
// if case TransactionDataRequest.spendsFromAddress(let sfa) = transactionDataRequest {
// print("__LD spendsFromAddress")
// var filter = TransparentAddressBlockFilter()
// filter.address = sfa.address
// filter.range = BlockRange(startHeight: Int(sfa.blockRangeStart), endHeight: Int(sfa.blockRangeEnd))
// let stream = service.getTaddressTxids(filter)
//
// for try await rawTransaction in stream {
// print("__LD count \(rawTransaction.data.bytes.count)")
//
// try await rustBackend.decryptAndStoreTransaction(
// txBytes: rawTransaction.data.bytes,
// minedHeight: Int32(rawTransaction.height)
// )
// }
// retry = false
//
// } else if case TransactionDataRequest.getStatus(let txId) = transactionDataRequest {
// print("__LD getStatus \(txId.map { String(format: "%02x", $0) }.joined())")
// let confirmedTx = try await enhance(txId: txId.data)
// retry = false
//
// let progress = EnhancementProgress(
// totalTransactions: transactionDataRequests.count,
// enhancedTransactions: index + 1,
// lastFoundTransaction: confirmedTx,
// range: range,
// newlyMined: confirmedTx.isSentTransaction && newlyMinedRange.contains(confirmedTx.minedHeight ?? 0)
// )
//
// await didEnhance(progress)
// } else if case TransactionDataRequest.enhancement(let txId) = transactionDataRequest {
// print("__LD enhancement \(txId.map { String(format: "%02x", $0) }.joined())")
// let confirmedTx = try await enhance(txId: txId.data)
// retry = false
//
// let progress = EnhancementProgress(
// totalTransactions: transactionDataRequests.count,
// enhancedTransactions: index + 1,
// lastFoundTransaction: confirmedTx,
// range: range,
// newlyMined: confirmedTx.isSentTransaction && newlyMinedRange.contains(confirmedTx.minedHeight ?? 0)
// )
//
// await didEnhance(progress)
// }

let progress = EnhancementProgress(
totalTransactions: transactions.count,
enhancedTransactions: index + 1,
lastFoundTransaction: confirmedTx,
range: range,
newlyMined: confirmedTx.isSentTransaction && newlyMinedRange.contains(confirmedTx.minedHeight ?? 0)
)

await didEnhance(progress)
// SpendsFromAddress {
// address: TransparentAddress,
// block_range_start: BlockHeight,
// block_range_end: Option<BlockHeight>,
// }
// status address -> GetTaddressTxids -> stream<RawTransaction>
// returned ids go through the decryptAndStoreTransaction

// 2 cases go through this process (Enhancement(TxId), GetStatus(TxId))
} catch {
retries += 1
logger.error("could not enhance txId \(transaction.rawID.toHexStringTxId()) - Error: \(error)")
// logger.error("could not enhance txId \(transaction.rawID.toHexStringTxId()) - Error: \(error)")
if retries > maxRetries {
throw error
}
}
}
}

let endTime = Date()
let diff = endTime.timeIntervalSince1970 - startTime.timeIntervalSince1970
let logMsg = "Enhanced \(transactions.count) transaction(s) in \(diff) for range \(range.lowerBound)...\(range.upperBound)"
logger.sync(logMsg)
metrics.actionDetail(logMsg, for: .enhance)
// let endTime = Date()
// let diff = endTime.timeIntervalSince1970 - startTime.timeIntervalSince1970
// let logMsg = "Enhanced \(transactions.count) transaction(s) in \(diff) for range \(range.lowerBound)...\(range.upperBound)"
// logger.sync(logMsg)
// metrics.actionDetail(logMsg, for: .enhance)
} catch {
logger.error("error enhancing transactions! \(error)")
throw error
Expand Down
12 changes: 12 additions & 0 deletions Sources/ZcashLightClientKit/Error/ZcashError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public enum ZcashError: Equatable, Error {
/// LightWalletService.getSubtreeRoots failed.
/// ZSRVC0009
case serviceSubtreeRootsStreamFailed(_ error: LightWalletServiceError)
/// LightWalletService.getTaddressTxids failed.
/// ZSRVC0010
case serviceGetTaddressTxidsFailed(_ error: LightWalletServiceError)
/// SimpleConnectionProvider init of Connection failed.
/// ZSCPC0001
case simpleConnectionProvider(_ error: Error)
Expand Down Expand Up @@ -352,6 +355,11 @@ public enum ZcashError: Equatable, Error {
/// sourcery: code="ZRUST0063"
/// ZRUST0063
case rustTorClientGet(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.transactionDataRequests
/// - `rustError` contains error generated by the rust layer.
/// sourcery: code="ZRUST0064"
/// ZRUST0064
case rustTransactionDataRequests(_ rustError: String)
/// SQLite query failed when fetching all accounts from the database.
/// - `sqliteError` is error produced by SQLite library.
/// ZADAO0001
Expand Down Expand Up @@ -646,6 +654,7 @@ public enum ZcashError: Equatable, Error {
case .serviceFetchUTXOsFailed: return "LightWalletService.fetchUTXOs failed."
case .serviceBlockStreamFailed: return "LightWalletService.blockStream failed."
case .serviceSubtreeRootsStreamFailed: return "LightWalletService.getSubtreeRoots failed."
case .serviceGetTaddressTxidsFailed: return "LightWalletService.getTaddressTxids failed."
case .simpleConnectionProvider: return "SimpleConnectionProvider init of Connection failed."
case .saplingParamsInvalidSpendParams: return "Downloaded file with sapling spending parameters isn't valid."
case .saplingParamsInvalidOutputParams: return "Downloaded file with sapling output parameters isn't valid."
Expand Down Expand Up @@ -720,6 +729,7 @@ public enum ZcashError: Equatable, Error {
case .rustPutOrchardSubtreeRoots: return "Error from rust layer when calling ZcashRustBackend.putOrchardSubtreeRoots"
case .rustTorClientInit: return "Error from rust layer when calling TorClient.init"
case .rustTorClientGet: return "Error from rust layer when calling TorClient.get"
case .rustTransactionDataRequests: return "Error from rust layer when calling ZcashRustBackend.transactionDataRequests"
case .accountDAOGetAll: return "SQLite query failed when fetching all accounts from the database."
case .accountDAOGetAllCantDecode: return "Fetched accounts from SQLite but can't decode them."
case .accountDAOFindBy: return "SQLite query failed when seaching for accounts in the database."
Expand Down Expand Up @@ -829,6 +839,7 @@ public enum ZcashError: Equatable, Error {
case .serviceFetchUTXOsFailed: return .serviceFetchUTXOsFailed
case .serviceBlockStreamFailed: return .serviceBlockStreamFailed
case .serviceSubtreeRootsStreamFailed: return .serviceSubtreeRootsStreamFailed
case .serviceGetTaddressTxidsFailed: return .serviceGetTaddressTxidsFailed
case .simpleConnectionProvider: return .simpleConnectionProvider
case .saplingParamsInvalidSpendParams: return .saplingParamsInvalidSpendParams
case .saplingParamsInvalidOutputParams: return .saplingParamsInvalidOutputParams
Expand Down Expand Up @@ -903,6 +914,7 @@ public enum ZcashError: Equatable, Error {
case .rustPutOrchardSubtreeRoots: return .rustPutOrchardSubtreeRoots
case .rustTorClientInit: return .rustTorClientInit
case .rustTorClientGet: return .rustTorClientGet
case .rustTransactionDataRequests: return .rustTransactionDataRequests
case .accountDAOGetAll: return .accountDAOGetAll
case .accountDAOGetAllCantDecode: return .accountDAOGetAllCantDecode
case .accountDAOFindBy: return .accountDAOFindBy
Expand Down
Loading

0 comments on commit 5f63581

Please sign in to comment.