diff --git a/SessionSnodeKit/OnionRequestAPI.swift b/SessionSnodeKit/OnionRequestAPI.swift index 3a3b2e9bc..4a08a0999 100644 --- a/SessionSnodeKit/OnionRequestAPI.swift +++ b/SessionSnodeKit/OnionRequestAPI.swift @@ -4,8 +4,11 @@ import SessionUtilitiesKit /// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information. public enum OnionRequestAPI { + /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. private static var pathFailureCount: [Path:UInt] = [:] + /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. private static var snodeFailureCount: [Snode:UInt] = [:] + /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. public static var guardSnodes: Set = [] public static var paths: [Path] = [] // Not a set to ensure we consistently show the same path to the user @@ -196,10 +199,16 @@ public enum OnionRequestAPI { } private static func dropGuardSnode(_ snode: Snode) { + #if DEBUG + dispatchPrecondition(condition: .onQueue(Threading.workQueue)) + #endif guardSnodes = guardSnodes.filter { $0 != snode } } private static func drop(_ snode: Snode) throws { + #if DEBUG + dispatchPrecondition(condition: .onQueue(Threading.workQueue)) + #endif // We repair the path here because we can do it sync. In the case where we drop a whole // path we leave the re-building up to getPath(excluding:) because re-building the path // in that case is async. @@ -224,6 +233,9 @@ public enum OnionRequestAPI { } private static func drop(_ path: Path) { + #if DEBUG + dispatchPrecondition(condition: .onQueue(Threading.workQueue)) + #endif OnionRequestAPI.pathFailureCount[path] = 0 var paths = OnionRequestAPI.paths guard let pathIndex = paths.firstIndex(of: path) else { return } @@ -392,7 +404,7 @@ public enum OnionRequestAPI { seal.reject(error) } } - promise.catch2 { error in // Must be invoked on LokiAPI.workQueue + promise.catch2 { error in // Must be invoked on Threading.workQueue guard case HTTP.Error.httpRequestFailed(let statusCode, let json) = error else { return } let path = paths.first { $0.contains(guardSnode) } func handleUnspecificError() { diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index c40f4728c..05c43399b 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -77,33 +77,35 @@ public final class SnodeAPI : NSObject { ] SNLog("Populating snode pool using: \(target).") let (promise, seal) = Promise.pending() - attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) { - HTTP.execute(.post, url, parameters: parameters, useSSLURLSession: true).map2 { json -> Snode in - guard let intermediate = json["result"] as? JSON, let rawSnodes = intermediate["service_node_states"] as? [JSON] else { throw Error.randomSnodePoolUpdatingFailed } - snodePool = Set(rawSnodes.compactMap { rawSnode in - guard let address = rawSnode["public_ip"] as? String, let port = rawSnode["storage_port"] as? Int, - let ed25519PublicKey = rawSnode["pubkey_ed25519"] as? String, let x25519PublicKey = rawSnode["pubkey_x25519"] as? String, address != "0.0.0.0" else { - SNLog("Failed to parse target from: \(rawSnode).") - return nil + Threading.workQueue.async { + attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) { + HTTP.execute(.post, url, parameters: parameters, useSSLURLSession: true).map2 { json -> Snode in + guard let intermediate = json["result"] as? JSON, let rawSnodes = intermediate["service_node_states"] as? [JSON] else { throw Error.randomSnodePoolUpdatingFailed } + snodePool = Set(rawSnodes.compactMap { rawSnode in + guard let address = rawSnode["public_ip"] as? String, let port = rawSnode["storage_port"] as? Int, + let ed25519PublicKey = rawSnode["pubkey_ed25519"] as? String, let x25519PublicKey = rawSnode["pubkey_x25519"] as? String, address != "0.0.0.0" else { + SNLog("Failed to parse target from: \(rawSnode).") + return nil + } + return Snode(address: "https://\(address)", port: UInt16(port), publicKeySet: Snode.KeySet(ed25519Key: ed25519PublicKey, x25519Key: x25519PublicKey)) + }) + // randomElement() uses the system's default random generator, which is cryptographically secure + if !snodePool.isEmpty { + return snodePool.randomElement()! + } else { + throw Error.randomSnodePoolUpdatingFailed } - return Snode(address: "https://\(address)", port: UInt16(port), publicKeySet: Snode.KeySet(ed25519Key: ed25519PublicKey, x25519Key: x25519PublicKey)) - }) - // randomElement() uses the system's default random generator, which is cryptographically secure - if !snodePool.isEmpty { - return snodePool.randomElement()! - } else { - throw Error.randomSnodePoolUpdatingFailed } + }.done2 { snode in + seal.fulfill(snode) + SNSnodeKitConfiguration.shared.storage.with { transaction in + SNLog("Persisting snode pool to database.") + SNSnodeKitConfiguration.shared.storage.setSnodePool(to: SnodeAPI.snodePool, using: transaction) + } + }.catch2 { error in + SNLog("Failed to contact seed node at: \(target).") + seal.reject(error) } - }.done2 { snode in - seal.fulfill(snode) - SNSnodeKitConfiguration.shared.storage.with { transaction in - SNLog("Persisting snode pool to database.") - SNSnodeKitConfiguration.shared.storage.setSnodePool(to: SnodeAPI.snodePool, using: transaction) - } - }.catch2 { error in - SNLog("Failed to contact seed node at: \(target).") - seal.reject(error) } return promise } else { @@ -115,6 +117,9 @@ public final class SnodeAPI : NSObject { } internal static func dropSnodeFromSnodePool(_ snode: Snode) { + #if DEBUG + dispatchPrecondition(condition: .onQueue(Threading.workQueue)) + #endif var snodePool = SnodeAPI.snodePool snodePool.remove(snode) SnodeAPI.snodePool = snodePool @@ -132,6 +137,9 @@ public final class SnodeAPI : NSObject { } public static func dropSnodeFromSwarmIfNeeded(_ snode: Snode, publicKey: String) { + #if DEBUG + dispatchPrecondition(condition: .onQueue(Threading.workQueue)) + #endif let swarm = SnodeAPI.swarmCache[publicKey] if var swarm = swarm, let index = swarm.firstIndex(of: snode) { swarm.remove(at: index) @@ -282,6 +290,9 @@ public final class SnodeAPI : NSObject { /// - Note: Should only be invoked from `Threading.workQueue` to avoid race conditions. @discardableResult internal static func handleError(withStatusCode statusCode: UInt, json: JSON?, forSnode snode: Snode, associatedWith publicKey: String? = nil) -> Error? { + #if DEBUG + dispatchPrecondition(condition: .onQueue(Threading.workQueue)) + #endif func handleBadSnode() { let oldFailureCount = SnodeAPI.snodeFailureCount[snode] ?? 0 let newFailureCount = oldFailureCount + 1