Port alp-fusion.proto and encode/decode helpers (ClientMessage /
ServerMessage, components, dual Pedersen commitments) so framed TCP/TLS
payloads can carry CashFusion-shaped round messages with ALP pool fields.
Unit tests cover message round-trips and a ClientHello/ServerHello exchange
over FusionConnection. No round driver, covert/Tor, Chronik, or signing.
Details
- Reviewers
Fabien - Group Reviewers
Restricted Project - Commits
- rABCb4de100fdf60: [alp-fusion] Add CashFusion-shaped protobuf control messages
- cd apps/alp-fusion && pnpm test
- Confirm protobuf tests encode/decode JoinPools and exchange hello over TCP
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
────────────────────────────────────────
(\(\
(• .•) Measuring programming progress by lines of code is like measuring aircraft building progress by weight. - Bill Gates
────────────────────────────────────────────────────────────────────────
major [Data Integrity & Integration] → ]8;;vscode://file//work/apps/alp-fusion/src/protocol/messages.ts:64apps/alp-fusion/src/protocol/messages.ts:64-74]8;; Preserve uint64 values exactly. Type.create and Type.verify do not convert decimal strings or bigint. Convert payloads with Type.fromObject, reject unsafe number values, and define a lossless contract using bigint, Long, or decimal strings. Add a round-trip test above 2^53 - 1.
────────────────────────────────────────────────────────────────────────
minor [Functional Correctness] → ]8;;vscode://file//work/apps/alp-fusion/src/protocol/messages.ts:101apps/alp-fusion/src/protocol/messages.ts:101-104]8;; Validate direct encoder payloads before encoding. Type.create and Type.encode do not validate required fields. Call Type.verify before Type.create in encodeComponent and encodeInitialCommitment, as encodeMessage does.
────────────────────────────────────────────────────────────────────────
major [Functional Correctness]
→ ]8;;vscode://file//work/apps/alp-fusion/src/protocol/messages.ts:64apps/alp-fusion/src/protocol/messages.ts:64-74]8;;
Validate oneofField before encoding.
If oneofField is unknown, Type.create accepts it and Type.verify
ignores the unknown property. Type.encode then emits an empty envelope
instead of reporting the invalid message type.
Proposed fix
export function encodeMessage(
Type: protobuf.Type,
oneofField: string,
payload: Record<string, unknown>,
): Buffer {
- const msg = Type.create({ msg: oneofField, [oneofField]: payload });
- const err = Type.verify(msg);
+ const oneof = Type.oneofs.msg;
+ if (!oneof?.oneof.includes(oneofField)) {
+ throw new Error(`Unsupported message field: ${oneofField}`);
+ }
+ const value = { [oneofField]: payload };
+ const err = Type.verify(value);
if (err) {
throw new Error(String(err));
}
+ const msg = Type.create(value);
return Buffer.from(Type.encode(msg).finish());
}────────────────────────────────────────
Review complete
3 findings ✔
Major 2
Minor 1
8 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/SPEC.md
- apps/alp-fusion/package.json
- apps/alp-fusion/proto/alp-fusion.proto
- apps/alp-fusion/src/protocol/messages.ts
- apps/alp-fusion/test/protocol/messages.test.ts
- pnpm-lock.yaml
────────────────────────────────────────
Print all AI prompts: coderabbit review --show-prompts
Address CodeRabbit: bigint in/out for uint64 (fromObject + Long→bigint, reject unsafe numbers); assert proto2 required/oneof presence (verify alone is insufficient); reject unknown encodeMessage oneof fields. Tests for huge atom tiers, missing required fields, and bad oneof names.
Tail of the build log:
Done in 847ms using pnpm v10.24.0 > ecashaddrjs@2.0.0 build /work/modules/ecashaddrjs > tsc Preparing pnpm@10.24.0 for immediate activation... /work /work/abc-ci-builds/alp-fusion-tests Scope: 2 of 26 workspace projects Lockfile is up to date, resolution step is skipped Done in 838ms using pnpm v10.24.0 > chronik-client@4.3.0 build /work/modules/chronik-client > tsc Preparing pnpm@10.24.0 for immediate activation... /work /work/abc-ci-builds/alp-fusion-tests Scope: 4 of 26 workspace projects Lockfile is up to date, resolution step is skipped Progress: resolved 1, reused 0, downloaded 0, added 0 . | +6 + Progress: resolved 6, reused 1, downloaded 5, added 6, done Done in 974ms using pnpm v10.24.0 > ecash-lib@4.13.0 build /work/modules/ecash-lib > tsc && tsc -p ./tsconfig.build.json && rm -rf ./dist/ffi && cp -r ./src/ffi ./dist /work /work/abc-ci-builds/alp-fusion-tests Scope: all 26 workspace projects Lockfile is up to date, resolution step is skipped Progress: resolved 1, reused 0, downloaded 0, added 0 Packages: +1601 Progress: resolved 1601, reused 115, downloaded 386, added 272 Progress: resolved 1601, reused 115, downloaded 925, added 644 Progress: resolved 1601, reused 115, downloaded 1464, added 1283 Progress: resolved 1601, reused 115, downloaded 1471, added 1601, done ╭ Warning ─────────────────────────────────────────────────────────────────────╮ │ │ │ Ignored build scripts: @firebase/util, core-js, cypress, esbuild, sharp, │ │ styled-components. │ │ Run "pnpm approve-builds" to pick which dependencies should be allowed │ │ to run scripts. │ │ │ ╰──────────────────────────────────────────────────────────────────────────────╯ Done in 6.2s using pnpm v10.24.0 Scope: 5 of 26 workspace projects Lockfile is up to date, resolution step is skipped Done in 1s using pnpm v10.24.0 /work/abc-ci-builds/alp-fusion-tests /work/apps/alp-fusion /work/abc-ci-builds/alp-fusion-tests CI configured to test build. Building... > alp-fusion@0.1.0 build /work/apps/alp-fusion > tsc src/protocol/messages.ts(157,29): error TS2339: Property 'isLong' does not exist on type 'Constructor<Long>'. src/protocol/messages.ts(158,23): error TS18046: 'value' is of type 'unknown'. ELIFECYCLE Command failed with exit code 2. Build alp-fusion-tests failed with exit code 2
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) Untangling your code like a pair of headphones.
────────────────────────────────────────────────────────────────────────
minor [Stability & Availability]
→ ]8;;vscode://file//work/apps/alp-fusion/test/protocol/messages.test.ts:210apps/alp-fusion/test/protocol/messages.test.ts:210-253]8;;
Close the client connection in finally.
client.close() runs at Line 249 inside the try block. If any assertion
above it fails, the socket stays open. The mocha process then holds an
open handle after the failure.
Move the close into the finally block.
🧹 Proposed cleanup
const serverConns: FusionConnection[] = [];
const { port, close } = await listen('127.0.0.1', 0, conn => {
serverConns.push(conn);
});
+ let client: FusionConnection | undefined;
try {
- const client = await connect('127.0.0.1', port);
+ client = await connect('127.0.0.1', port);
@@
expect(reply.payload.atomTiers).to.deep.equal([1000n]);
-
- client.close();
} finally {
+ client?.close();
await close();
}────────────────────────────────────────────────────────────────────────
minor [Data Integrity & Integration]
→ ]8;;vscode://file//work/apps/alp-fusion/src/protocol/messages.ts:67apps/alp-fusion/src/protocol/messages.ts:67-95]8;;
Range-check bigint inputs for 64-bit fields.
assertSafeNumbers validates number only. A bigint passes unchecked.
protobufjs then reduces the value to 64 bits, so -1n encodes as
18446744073709551615 and values above 2^64-1 truncate. Both cases
produce a valid frame with a wrong amount for sats, token_atoms,
excess_fee, and atom_tier.
Reject out-of-range bigint values in the same guard.
🛡️ Proposed range check
function assertSafeNumbers(value: unknown, path = ''): void {
+ if (typeof value === 'bigint') {
+ if (value < 0n || value > 0xffffffffffffffffn) {
+ throw new Error(
+ `Out-of-range bigint${path ? ` at ${path}` : ''}: ` +
+ 'uint64/fixed64 fields accept 0 .. 2^64-1',
+ );
+ }
+ return;
+ }
if (typeof value === 'number') {
if (!Number.isSafeInteger(value)) {────────────────────────────────────────
Review complete
2 findings ✔
Minor 2
8 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/SPEC.md
- apps/alp-fusion/package.json
- apps/alp-fusion/proto/alp-fusion.proto
- apps/alp-fusion/src/protocol/messages.ts
- apps/alp-fusion/test/protocol/messages.test.ts
- pnpm-lock.yaml
────────────────────────────────────────
Print all AI prompts: coderabbit review --show-prompts
Rebase on master; fix alp-fusion-tests tsc (duck-type Long — protobufjs 8 typings omit isLong). Address CodeRabbit: close FusionConnection client in finally; reject out-of-range bigint for uint64 (0..2^64-1).
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) Bug-free code is the new black.
────────────────────────────────────────────────────────────────────────
major [Data Integrity & Integration]
→ ]8;;vscode://file//work/apps/alp-fusion/src/protocol/messages.ts:235apps/alp-fusion/src/protocol/messages.ts:235-254]8;;
Enforce required fields on the decode path.
The decode functions receive bytes from a remote peer. Type.decode does
not reject missing proto2 required fields, as the comment on lines
107-108 already states. A peer can omit ClientHello.version,
Component.salt_commitment, or InitialCommitment.sats_commitment, and
the decoded payload then carries defaults such as empty bytes or 0.
Downstream round logic cannot distinguish that from a legitimate value.
Apply assertRequiredFields after every decode, and validate the nested
payload of the oneof wrapper against its resolved type.
🛡️ Proposed fix for the decode paths
const payload = (msg as unknown as Record<string, unknown>)[field] as
| Record<string, unknown>
| undefined;
if (!payload) {
throw new Error(`Missing payload for ${field}`);
}
+ const nested = Type.fields[field]?.resolvedType;
+ if (nested instanceof protobuf.Type) {
+ assertRequiredFields(nested, payload, `${Type.name}.${field}`);
+ }
return {
field,
payload: longsToBigInt(payload) as Record<string, unknown>,
};
}
export function decodeComponent(buf: Buffer): Record<string, unknown> {
const Type = requireProto().lookupType('alpfusion.Component');
- return longsToBigInt(Type.decode(buf)) as Record<string, unknown>;
+ const msg = Type.decode(buf);
+ assertRequiredFields(Type, msg);
+ return longsToBigInt(msg) as Record<string, unknown>;
}
export function decodeInitialCommitment(buf: Buffer): Record<string, unknown> {
const Type = requireProto().lookupType('alpfusion.InitialCommitment');
- return longsToBigInt(Type.decode(buf)) as Record<string, unknown>;
+ const msg = Type.decode(buf);
+ assertRequiredFields(Type, msg);
+ return longsToBigInt(msg) as Record<string, unknown>;
}
Also applies to: 274-277, 299-302────────────────────────────────────────────────────────────────────────
minor [Data Integrity & Integration]
→ ]8;;vscode://file//work/apps/alp-fusion/test/protocol/messages.test.ts:147apps/alp-fusion/test/protocol/messages.test.ts:147-153]8;;
Assert the decoded version bytes, and confirm the type of
PROTOCOL_VERSION.
ClientHello.version is required bytes. If PROTOCOL_VERSION is a
string, Type.fromObject base64-decodes it, and the encoded bytes then
differ from the intended value. This test asserts only decoded.field, so
that corruption stays invisible. Add a byte-level assertion, and confirm
that PROTOCOL_VERSION in src/protocol/constants.js is a Uint8Array
or Buffer.
💚 Proposed assertion
const decoded = decodeMessage(types.ClientMessage, hello);
expect(decoded.field).to.equal('clienthello');
+ expect(
+ Buffer.from(decoded.payload.version as Uint8Array).equals(
+ Buffer.from(PROTOCOL_VERSION),
+ ),
+ ).to.equal(true);────────────────────────────────────────
Review complete
2 findings ✔
Major 1
Minor 1
8 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/SPEC.md
- apps/alp-fusion/package.json
- apps/alp-fusion/proto/alp-fusion.proto
- apps/alp-fusion/src/protocol/messages.ts
- apps/alp-fusion/test/protocol/messages.test.ts
- pnpm-lock.yaml
────────────────────────────────────────
Print all AI prompts: coderabbit review --show-prompts
Address CodeRabbit: assertRequiredFields on decode (reject empty required bytes); assert ClientHello.version round-trips PROTOCOL_VERSION bytes.
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) Code Wars Episode II: Attack of the Git Clones.
────────────────────────────────────────────────────────────────────────
minor [Data Integrity & Integration] → ]8;;vscode://file//work/apps/alp-fusion/src/protocol/messages.ts:186apps/alp-fusion/src/protocol/messages.ts:186-222]8;; Guard the decode fallback If protobuf.util.Long is unavailable, protobufjs uses toNumber() for 64-bit fields. longsToBigInt leaves that number unchanged. Values above Number.MAX_SAFE_INTEGER can lose precision and remain number. Ensure Long is configured before decoding, or reject unsafe numeric results.
────────────────────────────────────────
Review complete
1 finding ✔
Minor 1
8 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/SPEC.md
- apps/alp-fusion/package.json
- apps/alp-fusion/proto/alp-fusion.proto
- apps/alp-fusion/src/protocol/messages.ts
- apps/alp-fusion/test/protocol/messages.test.ts
- pnpm-lock.yaml
────────────────────────────────────────
Print all AI prompts: coderabbit review --show-prompts
Address CodeRabbit: require protobufjs Long in initProto; reject unsafe numbers on decode so uint64 cannot silently lose precision if Long is missing.
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) Hold on, let me under-engineer this!
────────────────────────────────────────────────────────────────────────
major [Data Integrity & Integration] → ]8;;vscode://file//work/apps/alp-fusion/src/protocol/messages.ts:82apps/alp-fusion/src/protocol/messages.ts:82-90]8;; Reject negative plain numbers for unsigned fields. Type.verify accepts negative integers for uint32, uint64, and fixed64. protobufjs encodes sats: -1 as 18446744073709551615. Add a value < 0 check in the number branch.
────────────────────────────────────────
Review complete
1 finding ✔
Major 1
8 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/SPEC.md
- apps/alp-fusion/package.json
- apps/alp-fusion/proto/alp-fusion.proto
- apps/alp-fusion/src/protocol/messages.ts
- apps/alp-fusion/test/protocol/messages.test.ts
- pnpm-lock.yaml
────────────────────────────────────────
Print all AI prompts: coderabbit review --show-prompts
Address CodeRabbit: reject negative plain numbers in assertSafeNumbers (unsigned wire fields must not wrap or coerce to 0).
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) This is O(n) in theory and O(🤡) in practice.
────────────────────────────────────────
Review complete
No findings ✔
8 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/SPEC.md
- apps/alp-fusion/package.json
- apps/alp-fusion/proto/alp-fusion.proto
- apps/alp-fusion/src/protocol/messages.ts
- apps/alp-fusion/test/protocol/messages.test.ts
- pnpm-lock.yaml
────────────────────────────────────────
| apps/alp-fusion/proto/alp-fusion.proto | ||
|---|---|---|
| 4–5 ↗ | (On Diff #60909) | why not go proto3 ? If it's because electrum uses proto 2 then it might be worth moving to 3 (where all fields default to optional) and reuse the file to keep the implementations in sync |
| apps/alp-fusion/src/protocol/messages.ts | ||
| 91 ↗ | (On Diff #60909) | I don't think that dealing with all the possible types is a good idea. Also it feels like these checks already exist somewhere else, please check there is no code duplicate |
Address Fabien: replace deep assertSafeNumbers walk with schema-guided uint64/fixed64 bigint checks; reuse ecash-lib bigintToU64Be for the range bound (no duplicate uint64 validator).
Rebase on master after Fabien feedback fix (schema-guided uint64 bigint checks via bigintToU64Be).
| apps/alp-fusion/proto/alp-fusion.proto | ||
|---|---|---|
| 4–5 ↗ | (On Diff #60909) | I think the implementations diverge too much for this to be value-add. we could still make this proto3. but I don't know if there's a need for that. |
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) I'm sorry, Dave. I'm afraid I can't let you write that bug.
────────────────────────────────────────────────────────────────────────
minor [Data Integrity & Integration]
→ ]8;;vscode://file//work/apps/alp-fusion/src/protocol/messages.ts:88apps/alp-fusion/src/protocol/messages.ts:88-141]8;;
Handle a repeated field that receives a non-array value.
At Line 103 the branch requires field.repeated && Array.isArray(child).
If a caller passes a scalar for a repeated uint64 field, for example
atomTiers: 5n, the code falls through to Line 124 and passes the
bigint check, so validation reports no error. fromObject then ignores
or mishandles the value. The same gap exists at Line 196 for repeated
message fields. Reject a non-array value for a repeated field explicitly.
🛡️ Reject non-array values for repeated fields
const child = obj[field.name];
const childPath = `${path}.${field.name}`;
- if (field.repeated && Array.isArray(child)) {
+ if (field.repeated) {
+ if (!Array.isArray(child)) {
+ throw new Error(`${childPath}: expected array for repeated field`);
+ }
for (let i = 0; i < child.length; i++) {
Also applies to: 149-217────────────────────────────────────────
Review complete
1 finding ✔
Minor 1
8 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/SPEC.md
- apps/alp-fusion/package.json
- apps/alp-fusion/proto/alp-fusion.proto
- apps/alp-fusion/src/protocol/messages.ts
- apps/alp-fusion/test/protocol/messages.test.ts
- pnpm-lock.yaml
────────────────────────────────────────
Print all AI prompts: coderabbit review --show-prompts
Address CodeRabbit: reject non-array values for repeated protobuf fields in assertUint64Fields and assertRequiredFields.
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) Make quality a requirements issue. Involve your users in determining the project's real quality requirements.
────────────────────────────────────────────────────────────────────────
minor [Stability & Availability]
→ ]8;;vscode://file//work/apps/alp-fusion/test/protocol/messages.test.ts:343apps/alp-fusion/test/protocol/messages.test.ts:343-346]8;;
Close the accepted server connections.
The finally block closes the client and the listener. It leaves the
accepted connection in serverConns open. If close() only stops
accepting, that socket keeps a handle alive and can delay mocha exit.
🧹 Proposed cleanup
} finally {
client?.close();
+ for (const conn of serverConns) {
+ conn.close();
+ }
await close();
}────────────────────────────────────────
Review complete
1 finding ✔
Minor 1
8 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/SPEC.md
- apps/alp-fusion/package.json
- apps/alp-fusion/proto/alp-fusion.proto
- apps/alp-fusion/src/protocol/messages.ts
- apps/alp-fusion/test/protocol/messages.test.ts
- pnpm-lock.yaml
────────────────────────────────────────
Print all AI prompts: coderabbit review --show-prompts
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) Pulling the bugs out of the hat.
────────────────────────────────────────
Review complete
No findings ✔
8 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/SPEC.md
- apps/alp-fusion/package.json
- apps/alp-fusion/proto/alp-fusion.proto
- apps/alp-fusion/src/protocol/messages.ts
- apps/alp-fusion/test/protocol/messages.test.ts
- pnpm-lock.yaml
────────────────────────────────────────
| apps/alp-fusion/proto/alp-fusion.proto | ||
|---|---|---|
| 4–5 ↗ | (On Diff #60909) | After some research it seems that google is not willing to deprecate proto2. I suppose it's fine to use that and avoid introducing mistakes |
| apps/alp-fusion/src/protocol/messages.ts | ||
| 93 ↗ | (On Diff #60929) | so a string is fine? I don't get it |
| 201–202 ↗ | (On Diff #60929) | |
Address Fabien: reject non-object payloads in assertUint64Fields; flatten repeated non-array checks per review suggestion.
Fix tsc narrowing after Fabien repeated-field flatten (cast array after non-array throw).
Fix remaining tsc unknown[] narrowing in assertUint64Fields after repeated-field flatten.
Tail of the build log:
> ecashaddrjs@2.0.0 build /work/modules/ecashaddrjs > tsc Preparing pnpm@10.24.0 for immediate activation... /work /work/abc-ci-builds/alp-fusion-tests Scope: 2 of 26 workspace projects Lockfile is up to date, resolution step is skipped Done in 797ms using pnpm v10.24.0 > chronik-client@4.3.0 build /work/modules/chronik-client > tsc Preparing pnpm@10.24.0 for immediate activation... /work /work/abc-ci-builds/alp-fusion-tests Scope: 4 of 26 workspace projects Lockfile is up to date, resolution step is skipped Progress: resolved 1, reused 0, downloaded 0, added 0 . | +6 + Progress: resolved 6, reused 1, downloaded 5, added 6, done Done in 914ms using pnpm v10.24.0 > ecash-lib@4.13.0 build /work/modules/ecash-lib > tsc && tsc -p ./tsconfig.build.json && rm -rf ./dist/ffi && cp -r ./src/ffi ./dist /work /work/abc-ci-builds/alp-fusion-tests Scope: all 26 workspace projects Lockfile is up to date, resolution step is skipped Progress: resolved 1, reused 0, downloaded 0, added 0 Packages: +1601 Progress: resolved 1601, reused 115, downloaded 396, added 436 Progress: resolved 1601, reused 115, downloaded 983, added 796 Progress: resolved 1601, reused 115, downloaded 1466, added 1194 Progress: resolved 1601, reused 115, downloaded 1471, added 1601, done ╭ Warning ─────────────────────────────────────────────────────────────────────╮ │ │ │ Ignored build scripts: @firebase/util, core-js, cypress, esbuild, sharp, │ │ styled-components. │ │ Run "pnpm approve-builds" to pick which dependencies should be allowed │ │ to run scripts. │ │ │ ╰──────────────────────────────────────────────────────────────────────────────╯ Done in 5.9s using pnpm v10.24.0 Scope: 5 of 26 workspace projects Lockfile is up to date, resolution step is skipped Done in 979ms using pnpm v10.24.0 /work/abc-ci-builds/alp-fusion-tests /work/apps/alp-fusion /work/abc-ci-builds/alp-fusion-tests CI configured to test build. Building... > alp-fusion@0.1.0 build /work/apps/alp-fusion > tsc src/protocol/messages.ts(113,33): error TS18046: 'child' is of type 'unknown'. src/protocol/messages.ts(114,30): error TS18046: 'child' is of type 'unknown'. src/protocol/messages.ts(211,33): error TS18046: 'child' is of type 'unknown'. src/protocol/messages.ts(212,30): error TS18046: 'child' is of type 'unknown'. ELIFECYCLE Command failed with exit code 2. Build alp-fusion-tests failed with exit code 2
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) Zero-day? Zero chance on my watch.
────────────────────────────────────────────────────────────────────────
critical [Functional Correctness]
→ ]8;;vscode://file//work/apps/alp-fusion/src/protocol/messages.ts:204apps/alp-fusion/src/protocol/messages.ts:204-222]8;;
Apply the same narrowing fix here.
child on Line 204 has type unknown. The compound guard on Line 205
does not narrow it, so child.length on Line 211 and child[i] on Line
212 fail to compile for the same reason as in assertUint64Fields.
🐛 Proposed fix for the narrowing
const child = (msg as Record<string, unknown>)[field.name];
- if (field.repeated && !Array.isArray(child)) {
- throw new Error(
- `${path}.${field.name}: expected array for repeated field`,
- );
- }
if (field.repeated) {
+ if (!Array.isArray(child)) {
+ throw new Error(
+ `${path}.${field.name}: expected array for repeated field`,
+ );
+ }
for (let i = 0; i < child.length; i++) {
- const item = child[i];
+ const item: unknown = child[i];────────────────────────────────────────────────────────────────────────
critical [Functional Correctness]
→ ]8;;vscode://file//work/apps/alp-fusion/src/protocol/messages.ts:107apps/alp-fusion/src/protocol/messages.ts:107-132]8;;
child stays unknown, so tsc fails on child.length and child[i].
child has type unknown. The guard on Line 109 uses the compound
condition field.repeated && !Array.isArray(child). TypeScript does not
narrow child after that statement, because the negated condition is not
a type guard on child alone. child.length on Line 113 and child[i]
on Line 114 then fail to compile.
The unit tests do not catch this. pnpm test runs mocha --import=tsx,
which strips types without checking them. Only pnpm build fails.
Move the Array.isArray check inside the field.repeated branch so the
narrowing applies.
🐛 Proposed fix for the narrowing
- if (field.repeated && !Array.isArray(child)) {
- throw new Error(`${childPath}: expected array for repeated field`);
- }
if (field.repeated) {
+ if (!Array.isArray(child)) {
+ throw new Error(
+ `${childPath}: expected array for repeated field`,
+ );
+ }
for (let i = 0; i < child.length; i++) {
- const item = child[i];
+ const item: unknown = child[i];
const itemPath = `${childPath}[${i}]`;────────────────────────────────────────
Review complete
2 findings ✔
Critical 2
8 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/SPEC.md
- apps/alp-fusion/package.json
- apps/alp-fusion/proto/alp-fusion.proto
- apps/alp-fusion/src/protocol/messages.ts
- apps/alp-fusion/test/protocol/messages.test.ts
- pnpm-lock.yaml
────────────────────────────────────────
Print all AI prompts: coderabbit review --show-prompts
Tail of the build log:
Done in 803ms using pnpm v10.24.0 > ecashaddrjs@2.0.0 build /work/modules/ecashaddrjs > tsc Preparing pnpm@10.24.0 for immediate activation... /work /work/abc-ci-builds/alp-fusion-tests Scope: 2 of 26 workspace projects Lockfile is up to date, resolution step is skipped Done in 826ms using pnpm v10.24.0 > chronik-client@4.3.0 build /work/modules/chronik-client > tsc Preparing pnpm@10.24.0 for immediate activation... /work /work/abc-ci-builds/alp-fusion-tests Scope: 4 of 26 workspace projects Lockfile is up to date, resolution step is skipped Progress: resolved 1, reused 0, downloaded 0, added 0 . | +6 + Progress: resolved 6, reused 1, downloaded 5, added 6, done Done in 1s using pnpm v10.24.0 > ecash-lib@4.13.0 build /work/modules/ecash-lib > tsc && tsc -p ./tsconfig.build.json && rm -rf ./dist/ffi && cp -r ./src/ffi ./dist /work /work/abc-ci-builds/alp-fusion-tests Scope: all 26 workspace projects Lockfile is up to date, resolution step is skipped Progress: resolved 1, reused 0, downloaded 0, added 0 Packages: +1601 Progress: resolved 1601, reused 115, downloaded 414, added 408 Progress: resolved 1601, reused 115, downloaded 1021, added 734 Progress: resolved 1601, reused 115, downloaded 1470, added 1257 Progress: resolved 1601, reused 115, downloaded 1471, added 1601, done ╭ Warning ─────────────────────────────────────────────────────────────────────╮ │ │ │ Ignored build scripts: @firebase/util, core-js, cypress, esbuild, sharp, │ │ styled-components. │ │ Run "pnpm approve-builds" to pick which dependencies should be allowed │ │ to run scripts. │ │ │ ╰──────────────────────────────────────────────────────────────────────────────╯ Done in 5.9s using pnpm v10.24.0 Scope: 5 of 26 workspace projects Lockfile is up to date, resolution step is skipped Done in 1s using pnpm v10.24.0 /work/abc-ci-builds/alp-fusion-tests /work/apps/alp-fusion /work/abc-ci-builds/alp-fusion-tests CI configured to test build. Building... > alp-fusion@0.1.0 build /work/apps/alp-fusion > tsc src/protocol/messages.ts(113,33): error TS18046: 'child' is of type 'unknown'. src/protocol/messages.ts(114,30): error TS18046: 'child' is of type 'unknown'. ELIFECYCLE Command failed with exit code 2. Build alp-fusion-tests failed with exit code 2
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) This is the kind of code that makes on-call believe in ghosts.
────────────────────────────────────────
Review complete
No findings ✔
8 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/SPEC.md
- apps/alp-fusion/package.json
- apps/alp-fusion/proto/alp-fusion.proto
- apps/alp-fusion/src/protocol/messages.ts
- apps/alp-fusion/test/protocol/messages.test.ts
- pnpm-lock.yaml
────────────────────────────────────────
Build Bitcoin ABC Diffs / Diff Testing (ai-review) passed.
CodeRabbit Review
Diff : committed changes only
Compare : HEAD → master
Directory : work
────────────────────────────────────────
(\(\
(• .•) No code too small, no bug too tall.
────────────────────────────────────────
Review complete
No findings ✔
8 files reviewed:
- apps/alp-fusion/DEPLOY.md
- apps/alp-fusion/README.md
- apps/alp-fusion/SPEC.md
- apps/alp-fusion/package.json
- apps/alp-fusion/proto/alp-fusion.proto
- apps/alp-fusion/src/protocol/messages.ts
- apps/alp-fusion/test/protocol/messages.test.ts
- pnpm-lock.yaml
────────────────────────────────────────