Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -244,14 +244,24 @@ public actor CmxConnectivityEngine {
///
/// Peers whose material route content is unchanged keep their live
/// sessions; every other peer is invalidated before the new revision
/// becomes visible.
/// becomes visible. Account route revisions are monotonic, so an older
/// completion of an overlapping reconciliation cannot roll back a newer
/// installed revision or its content baseline.
public func didInstallRouteRevision(
_ revision: UInt64,
routes: CmxIrohDiscoveryResponse
) async {
if let routeRevision, revision < routeRevision { return }
let content = CmxConnectivityRouteContent(snapshot: routes)
guard routeRevision != revision else {
routeContent = content
// The recorded revision can lack a content baseline when a sync
// stored it from an unchanged response without a snapshot. A
// missing or differing baseline fails closed like any other
// material change before the content becomes the baseline.
if routeContent != content {
await invalidatePeersSuperseded(by: content)
routeContent = content
}
return
}
await invalidatePeersSuperseded(by: content)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,16 @@ actor CmxConnectivityPeerSession {
}

if let installed = activeConnection {
if installed.id != pending.id {
await connected.close()
if installed.id == pending.id {
return installed.session
}
if let winner = await settleRedundantDial(
connected,
installedID: installed.id
) {
return winner
}
return installed.session
continue redial
}
if await connected.isClosed() {
await connected.close()
Expand All @@ -205,10 +211,16 @@ actor CmxConnectivityPeerSession {
// installing over it would leak its session and double-record
// an established lifecycle for the same peer.
if let installed = activeConnection {
if installed.id != pending.id {
await connected.close()
if installed.id == pending.id {
return installed.session
}
if let winner = await settleRedundantDial(
connected,
installedID: installed.id
) {
return winner
}
return installed.session
continue redial
}
install(
connected,
Expand Down Expand Up @@ -278,6 +290,25 @@ actor CmxConnectivityPeerSession {
publishSnapshot()
}

/// Closes a redundant dial that lost to an installed winner.
///
/// Closing suspends this actor, so the winner can be invalidated,
/// replaced, or remotely closed before the close settles. Only a
/// still-installed live winner may be handed out; a nil result means
/// the caller must redial.
private func settleRedundantDial(
_ connected: any CmxConnectivitySession,
installedID: UUID
) async -> (any CmxConnectivitySession)? {
await connected.close()
guard let current = activeConnection,
current.id == installedID,
!(await current.session.isClosed()) else {
return nil
}
return current.session
}

private func install(
_ connected: any CmxConnectivitySession,
id: UUID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,28 @@
/// keep healthy sessions whose routes did not materially change.
struct CmxConnectivityRouteContent: Equatable, Sendable {
/// Trust material shared by every route in one account snapshot.
///
/// Relay fleet and verification key order carries no trust meaning, so
/// both are canonicalized here and a reorder-only revision compares
/// equal to the installed material.
struct AccountMaterial: Equatable, Sendable {
let relayFleet: [String]
let lanRendezvous: CmxIrohLANRendezvous
let grantVerificationKeys: CmxIrohGrantVerificationKeySet

init(snapshot: CmxIrohDiscoveryResponse) {
relayFleet = snapshot.relayFleet.sorted()
lanRendezvous = snapshot.lanRendezvous
let keySet = snapshot.grantVerificationKeys
grantVerificationKeys = CmxIrohGrantVerificationKeySet(
version: keySet.version,
currentKeyID: keySet.currentKeyID,
keys: keySet.keys.sorted {
($0.kid, $0.alg, $0.spkiDerBase64)
< ($1.kid, $1.alg, $1.spkiDerBase64)
}
)
}
}

/// Admission-relevant material of one broker binding.
Expand All @@ -30,19 +48,16 @@ struct CmxConnectivityRouteContent: Equatable, Sendable {
platform = binding.platform
identityGeneration = binding.identityGeneration
pairingEnabled = binding.pairingEnabled
capabilities = binding.capabilities
// The admission policy reads capabilities with set semantics.
capabilities = binding.capabilities.sorted()
}
}

let account: AccountMaterial
private let peerRoutes: [CmxConnectivityPeerID: [BindingMaterial]]

init(snapshot: CmxIrohDiscoveryResponse) {
account = AccountMaterial(
relayFleet: snapshot.relayFleet,
lanRendezvous: snapshot.lanRendezvous,
grantVerificationKeys: snapshot.grantVerificationKeys
)
account = AccountMaterial(snapshot: snapshot)
var routes: [CmxConnectivityPeerID: [BindingMaterial]] = [:]
for binding in snapshot.bindings {
let peerID = CmxConnectivityPeerID(
Expand Down
Loading