Persist onion request paths to database

pull/197/head
nielsandriesse 4 years ago
parent 41d2758a8d
commit 10e30f8179

@ -16,6 +16,12 @@ final class PathStatusView : UIView {
private func setUpViewHierarchy() {
layer.cornerRadius = Values.pathStatusViewSize / 2
layer.masksToBounds = false
if OnionRequestAPI.paths.count < OnionRequestAPI.pathCount {
let storage = OWSPrimaryStorage.shared()
storage.dbReadConnection.read { transaction in
OnionRequestAPI.paths = storage.getOnionRequestPaths(in: transaction)
}
}
let color = (OnionRequestAPI.paths.count >= OnionRequestAPI.pathCount) ? Colors.accent : Colors.pathsBuilding
setColor(to: color, isAnimated: false)
}

@ -123,7 +123,13 @@ public enum OnionRequestAPI {
}
}.map(on: LokiAPI.workQueue) { paths in
OnionRequestAPI.paths = paths
// Dispatch async on the main queue to avoid nested write transactions
DispatchQueue.main.async {
let storage = OWSPrimaryStorage.shared()
storage.dbReadWriteConnection.readWrite { transaction in
print("[Loki] Persisting onion request paths to database.")
storage.setOnionRequestPaths(paths, in: transaction)
}
NotificationCenter.default.post(name: .pathsBuilt, object: nil)
}
return paths
@ -136,6 +142,12 @@ public enum OnionRequestAPI {
/// - Note: Exposed for testing purposes.
internal static func getPath(excluding snode: LokiAPITarget) -> Promise<Path> {
guard pathSize >= 1 else { preconditionFailure("Can't build path of size zero.") }
if paths.count < pathCount {
let storage = OWSPrimaryStorage.shared()
storage.dbReadConnection.read { transaction in
paths = storage.getOnionRequestPaths(in: transaction)
}
}
// randomElement() uses the system's default random generator, which is cryptographically secure
if paths.count >= pathCount {
return Promise<Path> { seal in
@ -148,8 +160,15 @@ public enum OnionRequestAPI {
}
}
private static func dropPath(containing snode: LokiAPITarget) {
paths = paths.filter { !$0.contains(snode) }
private static func dropPaths() {
paths.removeAll()
// Dispatch async on the main queue to avoid nested write transactions
DispatchQueue.main.async {
let storage = OWSPrimaryStorage.shared()
storage.dbReadWriteConnection.readWrite { transaction in
storage.clearOnionRequestPaths(in: transaction)
}
}
}
private static func dropGuardSnode(_ snode: LokiAPITarget) {
@ -231,7 +250,7 @@ public enum OnionRequestAPI {
}
promise.catch(on: LokiAPI.workQueue) { error in // Must be invoked on LokiAPI.workQueue
guard case HTTP.Error.httpRequestFailed(_, _) = error else { return }
dropPath(containing: guardSnode) // A snode in the path is bad; retry with a different path
dropPaths() // A snode in the path is bad; retry with a different path
dropGuardSnode(guardSnode)
}
promise.handlingErrorsIfNeeded(forTargetSnode: snode, associatedWith: hexEncodedPublicKey)
@ -262,7 +281,7 @@ private extension Promise where T == JSON {
DispatchQueue.main.async {
let storage = OWSPrimaryStorage.shared()
storage.dbReadWriteConnection.readWrite { transaction in
storage.clearSnodePool(in: transaction)
storage.dropSnode(snode, in: transaction)
}
}
LokiAPI.failureCount[snode] = 0

@ -30,6 +30,42 @@ public extension OWSPrimaryStorage {
// MARK: - Onion Request Path
private static let onionRequestPathCollection = "LokiOnionRequestPathCollection"
public func setOnionRequestPaths(_ paths: [OnionRequestAPI.Path], in transaction: YapDatabaseReadWriteTransaction) {
// FIXME: This is a bit of a dirty approach that assumes 2 paths of length 3 each. We should do better than this.
guard paths.count == 2 else { return }
let path0 = paths[0]
let path1 = paths[1]
guard path0.count == 3, path1.count == 3 else { return }
let collection = OWSPrimaryStorage.onionRequestPathCollection
transaction.setObject(path0[0], forKey: "0-0", inCollection: collection)
transaction.setObject(path0[1], forKey: "0-1", inCollection: collection)
transaction.setObject(path0[2], forKey: "0-2", inCollection: collection)
transaction.setObject(path1[0], forKey: "1-0", inCollection: collection)
transaction.setObject(path1[1], forKey: "1-1", inCollection: collection)
transaction.setObject(path1[2], forKey: "1-2", inCollection: collection)
}
public func getOnionRequestPaths(in transaction: YapDatabaseReadTransaction) -> [OnionRequestAPI.Path] {
let collection = OWSPrimaryStorage.onionRequestPathCollection
guard
let path0Snode0 = transaction.object(forKey: "0-0", inCollection: collection) as? LokiAPITarget,
let path0Snode1 = transaction.object(forKey: "0-1", inCollection: collection) as? LokiAPITarget,
let path0Snode2 = transaction.object(forKey: "0-2", inCollection: collection) as? LokiAPITarget,
let path1Snode0 = transaction.object(forKey: "1-0", inCollection: collection) as? LokiAPITarget,
let path1Snode1 = transaction.object(forKey: "1-1", inCollection: collection) as? LokiAPITarget,
let path1Snode2 = transaction.object(forKey: "1-2", inCollection: collection) as? LokiAPITarget else { return [] }
return [ [ path0Snode0, path0Snode1, path0Snode2 ], [ path1Snode0, path1Snode1, path1Snode2 ] ]
}
public func clearOnionRequestPaths(in transaction: YapDatabaseReadWriteTransaction) {
transaction.removeAllObjects(inCollection: OWSPrimaryStorage.onionRequestPathCollection)
}
// MARK: - Session Requests
private static let sessionRequestTimestampCollection = "LokiSessionRequestTimestampCollection"

Loading…
Cancel
Save