From e750e453b53378cb43d02de014f763b1fdfa4dab Mon Sep 17 00:00:00 2001 From: "Evgeny @ SimpleX Chat" <259188159+evgeny-simplex@users.noreply.github.com> Date: Sat, 15 Aug 2026 18:20:26 +0000 Subject: [PATCH] corrections --- .../duplex-messaging/queue-rotation-fast.mmd | 23 ++++--- .../duplex-messaging/queue-rotation-fast.svg | 2 +- rfcs/2026-08-09-fast-queue-rotation.md | 64 ++++++++++--------- 3 files changed, 50 insertions(+), 39 deletions(-) diff --git a/protocol/diagrams/duplex-messaging/queue-rotation-fast.mmd b/protocol/diagrams/duplex-messaging/queue-rotation-fast.mmd index 75887dd0b..952729f4a 100644 --- a/protocol/diagrams/duplex-messaging/queue-rotation-fast.mmd +++ b/protocol/diagrams/duplex-messaging/queue-rotation-fast.mmd @@ -5,13 +5,18 @@ sequenceDiagram participant S as Server
that has A's send queue
(B's receive queue) participant B as Bob - A ->> R': NEW: create new queue
(allow SKEY) - A ->> S: SEND: QADD (R'): send address
of the new queue(s) + A ->> R': NEW: create new queue (SKEY allowed) + A ->> S: SEND: QADD (R') S ->> B: MSG: QADD (R') - B ->> R': SKEY: secure new queue - B ->> R': SEND: QTEST - R' ->> A: MSG: QTEST - A ->> R: DEL: delete the old queue - B ->> R': SEND: send messages to the new queue - R' ->> A: MSG: receive messages from the new queue - \ No newline at end of file + B ->> R: SEND: messages (also scheduled on R') + R ->> A: MSG: messages + B ->> R': SKEY: authorize B as sender + B ->> R': SEND: confirmation (establishes R' secret) + R' ->> A: MSG: confirmation (A secures R') + B ->> R': SEND: held copies (deduped), then new messages + R' ->> A: MSG: messages + B ->> R: SEND: remaining tail, then QEND + R ->> A: MSG: QEND + B ->> R': SEND: QEND + R' ->> A: MSG: QEND + A ->> R: DEL: delete the current queue diff --git a/protocol/diagrams/duplex-messaging/queue-rotation-fast.svg b/protocol/diagrams/duplex-messaging/queue-rotation-fast.svg index 823074823..77b986d75 100644 --- a/protocol/diagrams/duplex-messaging/queue-rotation-fast.svg +++ b/protocol/diagrams/duplex-messaging/queue-rotation-fast.svg @@ -1,3 +1,3 @@ -BobServerthat has A's send queue(B's receive queue)New serverthat has the new A'sreceive queueCurrent serverthat has A'sreceive queueAliceBobServerthat has A's send queue(B's receive queue)New serverthat has the new A'sreceive queueCurrent serverthat has A'sreceive queueAliceNEW: create new queue(allow SKEY)SEND: QADD (R'): send addressof the new queue(s)MSG: QADD (R')SKEY: secure new queueSEND: QTESTMSG: QTESTDEL: delete the old queueSEND: send messages to the new queueMSG: receive messages from the new queue \ No newline at end of file +BobServerthat has A's send queue(B's receive queue)New serverthat has the new A'sreceive queueCurrent serverthat has A'sreceive queueAliceBobServerthat has A's send queue(B's receive queue)New serverthat has the new A'sreceive queueCurrent serverthat has A'sreceive queueAliceNEW: create new queue (SKEY allowed)SEND: QADD (R')MSG: QADD (R')SEND: messages (also scheduled on R')MSG: messagesSKEY: authorize B as senderSEND: confirmation (establishes R' secret)MSG: confirmation (A secures R')SEND: held copies (deduped), then new messagesMSG: messagesSEND: remaining tail, then QENDMSG: QENDSEND: QENDMSG: QENDDEL: delete the current queue \ No newline at end of file diff --git a/rfcs/2026-08-09-fast-queue-rotation.md b/rfcs/2026-08-09-fast-queue-rotation.md index 83dcb2904..d5cb3cf55 100644 --- a/rfcs/2026-08-09-fast-queue-rotation.md +++ b/rfcs/2026-08-09-fast-queue-rotation.md @@ -1,10 +1,10 @@ --- Proposed: 2026-08-09 Protocol: agent-protocol v8 -Diagram: ./diagrams/duplex-messaging/queue-rotation-fast.svg +Diagram: ../protocol/diagrams/duplex-messaging/queue-rotation-fast.svg --- -# Fast queue rotation (redundant delivery) +# Fast queue rotation ## Problem @@ -14,33 +14,38 @@ cannot move away from a failed server. ## Solution -When both agents support v8, rotation is redundant delivery rather than a switch. The initiator adds -a new queue with `QADD`; from that point the peer writes every message to both the current queue and -the new queue, and secures the new queue in parallel. Once the new queue is secured the peer writes -new messages to it alone, while the current queue delivers whatever was already scheduled on it and a -final `QEND`, and is then removed. The recipient already drops duplicate messages, so the order of -delivery and which queue delivers a given message do not matter, provided every message arrives on at -least one queue. There is no boundary and no flip. +Both the current rotation and v8 add a queue, deliver to both queues while the rotation is in +progress, and remove the old queue; the recipient drops duplicates in both. The main difference is where +the new queue's secret is established. In the current rotation it is established over the current +queue, by `QKEY`, so it cannot complete when the current server is down. In v8 the peer establishes the +new queue's secret over the new queue itself — a confirmation it sends on R' — so establishing the +secret no longer depends on the current queue, and the rotation completes even when the current server +is down. -Rotation away from a dead current server works because every message is also scheduled on the new -queue: the recipient reads it there. A dead new queue does not stop delivery either, because the -current queue keeps delivering until the new one is secured. +v8 also starts writing to both queues earlier: from the moment the queue is added, including the +current queue's not-yet-delivered backlog. So the initiator adds the new queue with `QADD`; from that +point the peer writes every message to both the current queue and R'. Once R' is secured the peer +writes new messages to it alone, while the current queue delivers whatever was already scheduled on it +and a final `QEND`, and is then removed. Because the recipient drops duplicates, neither the order of +arrival nor which queue carries a message matters, provided each message arrives on at least one queue +— with one exception, the confirmation, which is always the first message on R'. A dead new queue does +not stop delivery either, because the current queue keeps delivering until R' is secured. Roles: A initiates (its receiving queue rotates; A receives on the new queue R'). B is the peer (B holds the sending queue to A, secures R', and delivers to both). -Sequence (as drawn in the diagram): +Sequence: - A -> R' : create new queue (messaging mode, SKEY allowed) - A -> S -> B : QADD(R') (over A's sending queue; A's current server untouched) - B : from now, schedule every message on both the current queue and R' - B -> both : messages (current queue and R', duplicates dropped by A) - B -> R' : SKEY (B secures R') - B -> R' : confirmation (empty; establishes R' secret; first message on R') - B : R' secured — new messages now go to R' only - B -> current : remaining tail, then QEND(current) - B -> R' : QEND(current), then new messages - A : on QEND, delete the current queue; keep receiving on R' + A -> R' : create new queue (messaging mode, SKEY allowed) + A -> S -> B : QADD(R') (over A's sending queue; A's current server untouched) + B : from QADD, schedule every message and the current backlog on both queues + B -> current : deliver the scheduled messages (R' holds its copies while securing) + B -> R' : SKEY (authorize B as sender) + B -> R' : confirmation (empty; establishes R' secret; first message on R') + B -> R' : deliver R''s held copies (A dedups), then new messages to R' only + B -> current : deliver the remaining tail, then QEND(current) + B -> R' : QEND(current) + A : on QEND, delete the current queue; keep receiving on R' ## Confirmation @@ -58,10 +63,11 @@ additionally encrypted with the double ratchet, so rotation does not advance the `QEND` names a queue to remove and is delivered on both queues. On receipt the recipient deletes the named queue; on send the peer removes its sending queue of that address. `QEND` is a general -queue-removal message — the peer can remove either queue with it — so rotation is the addition of a -queue (`QADD`) followed by the removal of a queue (`QEND`), with no distinct switch step. Delivering -`QEND` on the removed queue is best effort; the copy on the surviving queue removes it and reaches the -recipient even when the removed server is dead. +queue-removal message — the peer can remove either queue with it — so on the wire a rotation is the +addition of a queue (`QADD`) and the later removal of the replaced one (`QEND`), each an ordinary +operation on the queue set rather than a `QTEST`-style completion. Delivering `QEND` on the removed +queue is best effort; the copy on the surviving queue removes it and reaches the recipient even when +the removed server is dead. ## Per-queue secret @@ -75,7 +81,7 @@ Fast rotation runs only when the connection's agreed agent protocol version is 8 chooses it. Otherwise the `QKEY`/`QUSE` exchange is used. `QEND` is defined at version 8 and is only sent during fast rotation, so peers below version 8 never receive it. - new A / new B : fast (QADD, confirmation, redundant delivery, QEND) + new A / new B : fast (QADD, confirmation on R', QEND) new A / old B : slow (old B returns QKEY; new A keeps the QKEY/QUSE handling) old A / new B : slow (agreed version below 8; new B returns QKEY) old A / old B : slow @@ -87,5 +93,5 @@ confirmation on R' followed later by `QEND`. The initiator keeps reading messages on the new queue and removes the current queue when `QEND` arrives there, without waiting for the current server. Nothing is lost, because every message is -scheduled on the new queue as well; the only cleanup that a dead current server delays is the deletion +scheduled on the new queue; the only cleanup that a dead current server delays is the deletion of its queue, which is retried a bounded number of times and then abandoned.