Hello folks at CKB DevRel,
I noticed that concurrent CCC client requests can change which RPC endpoint each other retries. A request may retry a failed endpoint and reject without trying a healthy fallback.
Minimal Reproduction
import { ccc } from "@ckb-ccc/core";
globalThis.fetch = async (url, { body }) => {
const { id } = JSON.parse(body);
const endpoint = url === "https://a.invalid/" ? "A" : "B";
console.log(`${id + 1} -> ${endpoint}`);
if (endpoint === "A") {
throw new Error("A unavailable");
}
return Response.json({ jsonrpc: "2.0", id, result: "0x1" });
};
const client = new ccc.ClientPublicTestnet({
url: "https://a.invalid/",
fallbacks: ["https://b.invalid/"],
timeout: 10,
});
const results = await Promise.allSettled([
client.getTip(),
client.getTip(),
]);
console.log(results.map(({ status }) => status));
And to run it:
Behavior
Endpoint A is unavailable and endpoint B is healthy. Both requests should try A, then B, and succeed. Instead:
1 -> A
2 -> A
1 -> B
2 -> A
[ 'fulfilled', 'rejected' ]
Request 1 correctly retries B and succeeds. Request 2 retries failed endpoint A instead. It uses both of its attempts on A and rejects without ever trying healthy endpoint B.
This happens because concurrent requests advance the same endpoint index. One request can therefore change which endpoint another request retries.
Environment
@ckb-ccc/core@1.17.0
- Node.js 24.18.0
- Linux
Impact
The bug turns requests that could succeed through the healthy fallback into errors. It does not corrupt a successful response; the request never reaches the healthy endpoint because its retry is routed back to the failed endpoint.
Keep up the Great Work
Phroi %53
Hello folks at CKB DevRel,
I noticed that concurrent CCC client requests can change which RPC endpoint each other retries. A request may retry a failed endpoint and reject without trying a healthy fallback.
Minimal Reproduction
And to run it:
Behavior
Endpoint A is unavailable and endpoint B is healthy. Both requests should try A, then B, and succeed. Instead:
Request 1 correctly retries B and succeeds. Request 2 retries failed endpoint A instead. It uses both of its attempts on A and rejects without ever trying healthy endpoint B.
This happens because concurrent requests advance the same endpoint index. One request can therefore change which endpoint another request retries.
Environment
@ckb-ccc/core@1.17.0Impact
The bug turns requests that could succeed through the healthy fallback into errors. It does not corrupt a successful response; the request never reaches the healthy endpoint because its retry is routed back to the failed endpoint.
Keep up the Great Work
Phroi %53