Wire ClientHello / JoinPools / FusionBegin / StartRound /
PlayerCommit plus covert component reveal through FusionCoordinator and
runWireRound, then assemble an unsigned ALP SEND via OneShotRound.
Pedersen / blind-auth verify, Chronik, and signing stay out of scope.
Details
- Reviewers
Fabien - Group Reviewers
Restricted Project - Commits
- rABC1b7e1e3bcb40: [alp-fusion] Drive one-shot rounds over control and covert RPCs
- cd apps/alp-fusion && pnpm test
- Confirm the wire tests complete a two-player hello→join→covert→unsigned SEND and reject version mismatch / atom burn
Diff Detail
- Repository
- rABC Bitcoin ABC
- Lint
Lint Not Applicable - Unit
Tests Not Applicable
Event Timeline
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) Finish what you start. Where possible, the routine or object that allocates a resource should be responsible for deallocating it.
────────────────────────────────────────────────────────────────────────
major [Stability & Availability]
→ ]8;;vscode://file//work/apps/alp-fusion/src/coordinator/wire.ts:502apps/alp-fusion/src/coordinator/wire.ts:502-505]8;;
Unregister the player from matcher when the session ends.
handleSession registers playerId in this.matcher at Line 443 but the
finally block only removes the session. If a client disconnects after
joinpools and before assembly, the matcher keeps the stale playerId.
Two consequences follow. matcher.isReady can report a pool ready from
disconnected players. takeReady then returns that playerId, and
beginRound throws missing session ${playerId}, which fails the whole
round for the remaining live players.
Add a matcher removal in the finally block for every entry in
session.joined.
🐛 Sketch of the fix (needs a matcher removal API)
} finally {
this.sessions.delete(playerId);
+ for (const j of session.joined) {
+ this.matcher.unregister?.(playerId, j.tokenId, j.atomTier);
+ }
+ session.joined = [];
conn.close();
}────────────────────────────────────────────────────────────────────────
minor [Stability & Availability]
→ ]8;;vscode://file//work/apps/alp-fusion/src/coordinator/wire.ts:638apps/alp-fusion/src/coordinator/wire.ts:638-642]8;;
Clear the round timer after the race settles.
timeout() creates a setTimeout that is never cleared. When
round.result wins the race at Line 474, the timer stays armed for
roundTimeoutMs (default 10 s). The pending timer keeps the Node event
loop alive, so the process or the test run does not exit promptly after
close().
Return a handle or use unref().
🐛 Proposed fix
-function timeout(ms: number): Promise<void> {
- return new Promise(resolve => {
- setTimeout(resolve, ms);
- });
-}
+function timeout(ms: number): Promise<void> {
+ return new Promise(resolve => {
+ setTimeout(resolve, ms).unref();
+ });
+}────────────────────────────────────────
Review complete
2 findings ✔
Major 1
Minor 1
7 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/src/client/oneshot.ts
- apps/alp-fusion/src/client/wire.ts
- apps/alp-fusion/src/coordinator/wire.ts
- apps/alp-fusion/src/protocol/components.ts
- apps/alp-fusion/test/coordinator/wire.test.ts
────────────────────────────────────────
Print all AI prompts: coderabbit review --show-prompts
Unregister disconnected waiters from PoolMatcher so a dropped join cannot ghost-ready a pool; clear the round timeout when assemble wins the race.
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) Ah yes, the classic pattern: 'parse first, ask questions never'.
────────────────────────────────────────────────────────────────────────
major [Stability & Availability]
→ ]8;;vscode://file//work/apps/alp-fusion/src/coordinator/wire.ts:335apps/alp-fusion/src/coordinator/wire.ts:335]8;;
Bound earlyCovert.
The covert listener accepts any peer without authentication. When no round
is active, every accepted component blob is stored in earlyCovert with
no size limit. The map is cleared only when a round starts. A remote peer
can therefore grow coordinator memory without limit by opening covert
connections and sending distinct components.
Add a cap (for example NUM_COMPONENTS minPlayers small factor) and
reject further components once it is reached.
🛡️ Proposed fix
} else {
+ if (this.earlyCovert.size >= MAX_EARLY_COVERT) {
+ return { ok: false, message: 'too many early components' };
+ }
this.earlyCovert.set(hashHex, component);
}────────────────────────────────────────────────────────────────────────
major [Functional Correctness]
→ ]8;;vscode://file//work/apps/alp-fusion/src/client/wire.ts:116apps/alp-fusion/src/client/wire.ts:116-119]8;;
Tolerate PoolStatusUpdate after FusionBegin.
The coordinator broadcasts poolstatusupdate to every session, including
sessions that are already in a round (broadcastPoolStatus in
coordinator/wire.ts, called from handleSession on each new join). Each
send call queues on the same per-session sendChain, so an update can
be delivered between fusionbegin and startround, or between
playercommit and fusionresult.
If a third client joins while a round runs, the in-round clients throw
expected startround, got poolstatusupdate or `expected fusionresult, got
poolstatusupdate`, and the round then fails for everyone. Skip
poolstatusupdate in every receive step, or stop broadcasting to in-round
sessions on the coordinator side.
🐛 Proposed fix: skip status updates while waiting for StartRound
- const start = await recvServer(conn, types, recvMs);
- if (start.field !== 'startround') {
- throw new Error(`expected startround, got ${start.field}`);
- }
+ let start = await recvServer(conn, types, recvMs);
+ while (start.field === 'poolstatusupdate') {
+ start = await recvServer(conn, types, recvMs);
+ }
+ if (start.field === 'error') {
+ throw new Error(errorMessage(start.payload));
+ }
+ if (start.field !== 'startround') {
+ throw new Error(`expected startround, got ${start.field}`);
+ }
Apply the same tolerance to the result loop at lines 171-179:
while (
msg.field === 'sharecovertcomponents' ||
msg.field === 'poolstatusupdate'
) {
if (msg.field === 'sharecovertcomponents') {
const raw = msg.payload.components;
if (Array.isArray(raw)) {
shared = raw.map(c => Buffer.from(c as Uint8Array));
}
}
msg = await recvServer(conn, types, recvMs);
}────────────────────────────────────────
Review complete
2 findings ✔
Major 2
9 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/src/client/oneshot.ts
- apps/alp-fusion/src/client/wire.ts
- apps/alp-fusion/src/coordinator/pool.ts
- apps/alp-fusion/src/coordinator/wire.ts
- apps/alp-fusion/src/protocol/components.ts
- apps/alp-fusion/test/coordinator/pool.test.ts
- apps/alp-fusion/test/coordinator/wire.test.ts
────────────────────────────────────────
Print all AI prompts: coderabbit review --show-prompts
Cap unmatched covert blobs (earlyCovert / pending). Skip PoolStatusUpdate on in-round clients and stop broadcasting status into an active round so a late join cannot abort FusionBegin/StartRound/FusionResult.
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) It is easier to change the specification to fit the program than vice versa. - Alan Perlis
────────────────────────────────────────────────────────────────────────
major [Stability & Availability]
→ ]8;;vscode://file//work/apps/alp-fusion/src/coordinator/wire.ts:642apps/alp-fusion/src/coordinator/wire.ts:642-646]8;;
Re-evaluate ready pools when a round ends.
tryStartReady runs only after a JoinPools message (Line 490). When a
round settles, this handler clears activeRound but never re-checks the
matcher. Waiters that were left behind by takeReady stay parked until an
unrelated player joins.
Example with minPlayers: 2 and three waiters: A and B run the round, C
keeps waiting after the round ends, and C only starts a round when a
fourth player joins. The pool then lags one player behind forever.
🛠️ Proposed fix
void round.result.finally(() => {
if (this.activeRound === round) {
this.activeRound = null;
+ // Defer so the finished sessions can unregister first.
+ setImmediate(() => this.tryStartReady());
}
});
Add a test that keeps a third waiter connected and asserts a second round
starts after the first one settles.────────────────────────────────────────────────────────────────────────
major [Stability & Availability]
→ ]8;;vscode://file//work/apps/alp-fusion/src/coordinator/wire.ts:659apps/alp-fusion/src/coordinator/wire.ts:659-678]8;;
Send FusionBegin / StartRound to all players concurrently, and bound the
send.
The loop awaits each player's sends before it resolves the next player's
roundAssigned. A peer that stops reading its socket blocks this.send
with no deadline. The remaining players then never receive FusionBegin,
their wait at Line 492 has no deadline, and this.activeRound is never
cleared. The coordinator then starts no further round. One unauthenticated
client can stall the whole coordinator.
Fan the sends out and apply the round deadline.
🛠️ Proposed fix
- for (const playerId of round.playerIds) {
- const session = this.sessions.get(playerId);
- if (!session) {
- throw new Error(`missing session ${playerId}`);
- }
- session.resolveRound(round);
- await this.send(session, 'fusionbegin', {
- tokenId: Buffer.from(tokenIdToBytes(round.tokenId)),
- atomTier: round.atomTier,
- covertDomain,
- covertPort: this.covertPort,
- covertSsl: false,
- serverTime,
- });
- await this.send(session, 'startround', {
- roundPubkey,
- blindNoncePoints: [] as Buffer[],
- serverTime,
- });
- }
+ const sessions = round.playerIds.map(playerId => {
+ const session = this.sessions.get(playerId);
+ if (!session) {
+ throw new Error(`missing session ${playerId}`);
+ }
+ return session;
+ });
+ const budget = delay(this.opts.roundTimeoutMs ?? 10_000);
+ try {
+ await Promise.all(
+ sessions.map(async session => {
+ session.resolveRound(round);
+ const sends = (async () => {
+ await this.send(session, 'fusionbegin', {
+ tokenId: Buffer.from(
+ tokenIdToBytes(round.tokenId),
+ ),
+ atomTier: round.atomTier,
+ covertDomain,
+ covertPort: this.covertPort,
+ covertSsl: false,
+ serverTime,
+ });
+ await this.send(session, 'startround', {
+ roundPubkey,
+ blindNoncePoints: [] as Buffer[],
+ serverTime,
+ });
+ })();
+ await Promise.race([
+ sends,
+ budget.promise.then(() => {
+ throw new Error('round start send timed out');
+ }),
+ ]);
+ }),
+ );
+ } finally {
+ budget.clear();
+ }
The caller at Line 637 already converts a rejection into round.fail, so
a stalled peer then fails only that round.────────────────────────────────────────
Review complete
2 findings ✔
Major 2
9 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/src/client/oneshot.ts
- apps/alp-fusion/src/client/wire.ts
- apps/alp-fusion/src/coordinator/pool.ts
- apps/alp-fusion/src/coordinator/wire.ts
- apps/alp-fusion/src/protocol/components.ts
- apps/alp-fusion/test/coordinator/pool.test.ts
- apps/alp-fusion/test/coordinator/wire.test.ts
────────────────────────────────────────
Print all AI prompts: coderabbit review --show-prompts
After a round settles, tryStartReady again so leftover waiters are not stuck until an unrelated join. Send FusionBegin/StartRound to all players concurrently with a round-start timeout so one mute peer cannot stall the coordinator.
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) Code Wars Episode VI: Return of the Unit Tests.
────────────────────────────────────────────────────────────────────────
major [Stability & Availability]
→ ]8;;vscode://file//work/apps/alp-fusion/src/coordinator/wire.ts:308apps/alp-fusion/src/coordinator/wire.ts:308-312]8;;
Handle rejections from serveCovertPeer.
The control listener wraps handleSession in .catch(), but the covert
listener discards the promise with void. If serveCovertPeer rejects,
for example on a socket error or a malformed frame, the rejection is
unhandled. Node terminates the process on unhandled rejections by default,
so one bad covert peer can kill the coordinator.
Add a rejection handler that closes the peer connection.
🛡️ Proposed fix
this.covert = await listen(host, covertPort, conn => {
- void serveCovertPeer(conn, (field, payload) =>
- this.onCovert(field, payload),
- );
+ void serveCovertPeer(conn, (field, payload) =>
+ this.onCovert(field, payload),
+ ).catch(() => {
+ conn.close();
+ });
});────────────────────────────────────────────────────────────────────────
major [Stability & Availability]
→ ]8;;vscode://file//work/apps/alp-fusion/src/coordinator/wire.ts:489apps/alp-fusion/src/coordinator/wire.ts:489]8;;
Do not block a join on broadcasts to other peers.
broadcastPoolStatus awaits Promise.all over this.send(...) for every
other session. send has no write timeout, and .catch() only converts
rejections. A peer that stops reading its socket applies TCP backpressure,
so its sendMessage stays pending forever. The joining session then never
reaches tryStartReady on line 490, and pool matching stalls for every
pool.
beginRound already bounds its sends with budget. The disconnect path
on line 562 already broadcasts without awaiting. Apply the same treatment
here, or bound each broadcast send with a timeout.
♻️ Proposed fix
- await this.broadcastPoolStatus();
+ void this.broadcastPoolStatus();
this.tryStartReady();────────────────────────────────────────
Review complete
2 findings ✔
Major 2
9 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/src/client/oneshot.ts
- apps/alp-fusion/src/client/wire.ts
- apps/alp-fusion/src/coordinator/pool.ts
- apps/alp-fusion/src/coordinator/wire.ts
- apps/alp-fusion/src/protocol/components.ts
- apps/alp-fusion/test/coordinator/pool.test.ts
- apps/alp-fusion/test/coordinator/wire.test.ts
────────────────────────────────────────
Print all AI prompts: coderabbit review --show-prompts
Catch covert serveCovertPeer rejections so a bad peer cannot kill the process. Do not await PoolStatusUpdate broadcasts on join so a mute waiter cannot block tryStartReady.
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) Always code as if the person who ends up maintaining your code is a violent psychopath who knows where you live. - John Woods
────────────────────────────────────────────────────────────────────────
major [Stability & Availability]
→ ]8;;vscode://file//work/apps/alp-fusion/src/coordinator/wire.ts:367apps/alp-fusion/src/coordinator/wire.ts:367-376]8;;
Evict the oldest early covert blob instead of refusing new ones.
The covert port needs no authentication and earlyCovert has no eviction.
Any peer can send maxEarlyCovert junk components before a round starts.
Every later blob is then refused, including genuine components from real
participants, and their round fails at roundTimeoutMs. The test at
test/coordinator/wire.test.ts line 459 shows this fill behavior.
Keep the memory bound but evict in FIFO order, so junk cannot crowd out
live components. Map preserves insertion order, so the oldest key is
keys().next().value.
🔧 Proposed fix
- if (
- !this.earlyCovert.has(hashHex) &&
- this.earlyCovert.size >= this.maxEarlyCovert
- ) {
- return {
- ok: false,
- message: 'too many early components',
- };
- }
- this.earlyCovert.set(hashHex, component);
+ if (!this.earlyCovert.has(hashHex)) {
+ while (this.earlyCovert.size >= this.maxEarlyCovert) {
+ const oldest = this.earlyCovert.keys().next().value;
+ if (oldest === undefined) {
+ break;
+ }
+ this.earlyCovert.delete(oldest);
+ }
+ }
+ this.earlyCovert.set(hashHex, component);
The maxEarlyCovert test then asserts ['ok', 'ok', 'ok'] plus
earlyCovert.size === 2; assert the bound, not the rejection.────────────────────────────────────────────────────────────────────────
major [Functional Correctness]
→ ]8;;vscode://file//work/apps/alp-fusion/src/coordinator/wire.ts:631apps/alp-fusion/src/coordinator/wire.ts:631]8;;
Drop taken players from their other pools.
joinpools registers one player in every pool they request (lines
484-489), but takeReady removes them from this pool key only. A player
who joined (T,100) and (T,200) and is taken into the (T,100) round
stays a waiter in (T,200).
After the first round settles, tryStartReady can select that stale
registration. beginRound then calls session.resolveRound a second
time, which is a no-op because roundAssigned is already settled, and
handleSession has already consumed its single playercommit. The new
round never reaches its commit count, so tryAssemble never runs and the
round fails at roundTimeoutMs. The other players in that round lose it
through no fault of their own.
Unregister every taken player from their remaining pools when the round is
formed.
🔧 Proposed fix
const ready = this.matcher.takeReady(j.tokenId, j.atomTier);
+ // A session commits to exactly one round, so remove its other
+ // pool registrations; otherwise a later round picks a session
+ // that can no longer commit.
+ for (const takenId of ready.playerIds) {
+ const taken = this.sessions.get(takenId);
+ for (const other of taken?.joined ?? []) {
+ if (
+ other.tokenId === ready.tokenId &&
+ other.atomTier === ready.atomTier
+ ) {
+ continue;
+ }
+ this.matcher.unregister(
+ takenId,
+ other.tokenId,
+ other.atomTier,
+ );
+ }
+ }────────────────────────────────────────────────────────────────────────
major [Stability & Availability]
→ ]8;;vscode://file//work/apps/alp-fusion/src/client/wire.ts:92apps/alp-fusion/src/client/wire.ts:92]8;;
Separate the pool-fill wait from recvTimeoutMs.
This recv waits for FusionBegin, which arrives only after the pool
reaches minPlayers. The coordinator sends poolstatusupdate only when
another peer joins or leaves, so a client that joins a quiet pool receives
nothing and recvMessage rejects after the default 15s. The client then
aborts instead of waiting for the pool. DEFAULT_MIN_PLAYERS = 8 makes
this the normal case on a public coordinator.
Add a distinct, much longer budget for the FusionBegin wait and keep
recvTimeoutMs for in-round frames.
🔧 Proposed fix
export interface RunWireRoundOptions {
...
recvTimeoutMs?: number;
+ /** Wait for FusionBegin while the pool fills. Default 10 min. */
+ poolWaitTimeoutMs?: number;
- const begin = await recvServerSkipStatus(conn, types, recvMs);
+ const begin = await recvServerSkipStatus(
+ conn,
+ types,
+ opts.poolWaitTimeoutMs ?? 600_000,
+ );────────────────────────────────────────
Review complete
3 findings ✔
Major 3
10 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/src/client/oneshot.ts
- apps/alp-fusion/src/client/wire.ts
- apps/alp-fusion/src/coordinator/pool.ts
- apps/alp-fusion/src/coordinator/wire.ts
- apps/alp-fusion/src/protocol/components.ts
- apps/alp-fusion/src/protocol/covert.ts
- apps/alp-fusion/test/coordinator/pool.test.ts
- apps/alp-fusion/test/coordinator/wire.test.ts
────────────────────────────────────────
Print all AI prompts: coderabbit review --show-prompts
When a session is taken into a round, unregister it from its other JoinPools keys so tryStartReady cannot pick a player who already committed.
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) To iterate is human, to recurse divine. - L. Peter Deutsch
────────────────────────────────────────────────────────────────────────
minor [Security & Privacy] → ]8;;vscode://file//work/apps/alp-fusion/DEPLOY.md:18apps/alp-fusion/DEPLOY.md:18]8;; State that the covert reveal does not yet hide components from the coordinator. This row advertises "covert reveal → unsigned tx". In this slice the commitment is sha256(componentBytes) (componentCommitment in src/protocol/components.ts), so the coordinator matches each revealed component to the player who committed it. The coordinator therefore still learns the full input↔output map. The privacy reminders section only warns about tokenId visibility and missing Tor, so a reader can conclude that Tor is the last remaining gap. Add a reminder that in-round unlinkability needs salted commitments plus blind-signature reveal. 📝 Proposed addition to "Privacy / ops reminders" (outside the changed range) - Covert reveal is **not yet unlinkable to the coordinator**: commitments are `sha256(component)`, so the coordinator maps every revealed component to its committer. Salted commitments + blind-signature reveal are still required.
────────────────────────────────────────────────────────────────────────
minor [Functional Correctness]
→ ]8;;vscode://file//work/apps/alp-fusion/src/client/wire.ts:109apps/alp-fusion/src/client/wire.ts:109-111]8;;
Validate FusionBegin.atomTier as well as tokenId.
opts.pools can advertise several tiers, and the coordinator selects one
pool for the round (tryStartReady in src/coordinator/wire.ts). This
code checks only tokenId, so a round for a tier the caller did not pass
as opts.atomTier is accepted silently. Compare the tier too, or record
the accepted tier in WireRoundResult.
🛡️ Proposed check
if (beginToken !== opts.tokenId.toLowerCase()) {
throw new Error('FusionBegin tokenId mismatch');
}
+ if (begin.payload.atomTier !== opts.atomTier) {
+ throw new Error('FusionBegin atomTier mismatch');
+ }
Only apply this when the caller does not intend to accept any advertised
tier. If any advertised tier is acceptable, validate against joinKeys
instead.────────────────────────────────────────────────────────────────────────
major [Stability & Availability]
→ ]8;;vscode://file//work/apps/alp-fusion/src/coordinator/wire.ts:532apps/alp-fusion/src/coordinator/wire.ts:532-552]8;;
Bound the terminal sends with the round budget.
beginRound wraps its fusionbegin / startround sends in a
delay(roundTimeoutMs) race and closes the connections on timeout. These
two terminal sends have no such budget. If a peer completes playercommit
and then stops reading its socket, send blocks on sendChain forever.
handleSession never leaves the try block, so the finally block never
runs: the session stays in this.sessions, the socket stays open, and
matcher.unregister is never called for it. Every later
broadcastPoolStatus then chains another pending send onto that blocked
chain, so the chain grows without bound.
The existing mute-peer tests exercise the pre-commit and round-start paths
only, so this path is uncovered.
🛡️ Proposed fix: reuse the delay budget for the result sends
- if (result.components.length > 0) {
- await this.send(session, 'sharecovertcomponents', {
- components: result.components,
- skipSignatures: true,
- sessionHash: Buffer.from(
- sha256(Buffer.from(round.tokenId)),
- ),
- });
- }
- if (result.ok && result.rawTx) {
- await this.send(session, 'fusionresult', {
- ok: true,
- rawTx: Buffer.from(result.rawTx),
- txid: result.txid ?? '',
- });
- } else {
- await this.send(session, 'fusionresult', {
- ok: false,
- error: result.error ?? 'round failed',
- });
- }
+ const sendBudget = delay(this.opts.roundTimeoutMs ?? 10_000);
+ try {
+ await Promise.race([
+ (async () => {
+ if (result.components.length > 0) {
+ await this.send(session, 'sharecovertcomponents', {
+ components: result.components,
+ skipSignatures: true,
+ sessionHash: Buffer.from(
+ sha256(Buffer.from(round.tokenId)),
+ ),
+ });
+ }
+ if (result.ok && result.rawTx) {
+ await this.send(session, 'fusionresult', {
+ ok: true,
+ rawTx: Buffer.from(result.rawTx),
+ txid: result.txid ?? '',
+ });
+ } else {
+ await this.send(session, 'fusionresult', {
+ ok: false,
+ error: result.error ?? 'round failed',
+ });
+ }
+ })(),
+ sendBudget.promise.then(() => {
+ throw new Error('result send timed out');
+ }),
+ ]);
+ } finally {
+ sendBudget.clear();
+ }
The finally block already closes the connection, so the timeout releases
the session and the socket.────────────────────────────────────────
Review complete
3 findings ✔
Major 1
Minor 2
10 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/src/client/oneshot.ts
- apps/alp-fusion/src/client/wire.ts
- apps/alp-fusion/src/coordinator/pool.ts
- apps/alp-fusion/src/coordinator/wire.ts
- apps/alp-fusion/src/protocol/components.ts
- apps/alp-fusion/src/protocol/covert.ts
- apps/alp-fusion/test/coordinator/pool.test.ts
- apps/alp-fusion/test/coordinator/wire.test.ts
────────────────────────────────────────
Print all AI prompts: coderabbit review --show-prompts
Bound FusionResult/ShareCovertComponents sends with the round timeout so a mute peer after commit cannot pin the session. Reject FusionBegin for a pool the client did not join. Note in DEPLOY that sha256(component) commitments are not coordinator-unlinkable.
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) Code Wars Episode I: The Phantom Reference.
────────────────────────────────────────
Review complete
No findings ✔
10 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/src/client/oneshot.ts
- apps/alp-fusion/src/client/wire.ts
- apps/alp-fusion/src/coordinator/pool.ts
- apps/alp-fusion/src/coordinator/wire.ts
- apps/alp-fusion/src/protocol/components.ts
- apps/alp-fusion/src/protocol/covert.ts
- apps/alp-fusion/test/coordinator/pool.test.ts
- apps/alp-fusion/test/coordinator/wire.test.ts
────────────────────────────────────────