@mikeargento/bitgraph-audit 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/audit.d.ts +8 -6
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +28 -8
- package/dist/audit.js.map +1 -1
- package/dist/ceilings.d.ts +25 -0
- package/dist/ceilings.d.ts.map +1 -1
- package/dist/ceilings.js +159 -31
- package/dist/ceilings.js.map +1 -1
- package/dist/cli.js +40 -3
- package/dist/cli.js.map +1 -1
- package/dist/exports.d.ts +39 -0
- package/dist/exports.d.ts.map +1 -0
- package/dist/exports.js +371 -0
- package/dist/exports.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/ingest.d.ts.map +1 -1
- package/dist/ingest.js +179 -19
- package/dist/ingest.js.map +1 -1
- package/dist/report-json.d.ts.map +1 -1
- package/dist/report-json.js +15 -0
- package/dist/report-json.js.map +1 -1
- package/dist/report-md.d.ts.map +1 -1
- package/dist/report-md.js +225 -0
- package/dist/report-md.js.map +1 -1
- package/dist/settlement-blobs.d.ts +131 -0
- package/dist/settlement-blobs.d.ts.map +1 -0
- package/dist/settlement-blobs.js +682 -0
- package/dist/settlement-blobs.js.map +1 -0
- package/dist/types.d.ts +254 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -3
- package/src/__tests__/fixtures/settlement/0x018efc046e94610e2530344caf1574e507a3182e1a975fb25b032ec1434a3f5c.bin +0 -0
- package/src/__tests__/fixtures/settlement/ceiling-51979918.json +101 -0
- package/src/__tests__/fixtures/settlement/pointer-51979918.json +69 -0
- package/src/__tests__/settlement.test.ts +254 -0
- package/src/audit.ts +30 -8
- package/src/ceilings.ts +167 -33
- package/src/cli.ts +39 -3
- package/src/exports.ts +478 -0
- package/src/index.ts +16 -0
- package/src/ingest.ts +192 -18
- package/src/report-json.ts +15 -0
- package/src/report-md.ts +248 -0
- package/src/settlement-blobs.ts +689 -0
- package/src/types.ts +234 -2
package/src/ceilings.ts
CHANGED
|
@@ -9,10 +9,27 @@
|
|
|
9
9
|
*
|
|
10
10
|
* The writer address is a trust input, like a measurement allowlist: the
|
|
11
11
|
* default is the address BitGraph publishes, and a reader can pass another.
|
|
12
|
+
*
|
|
13
|
+
* A ceiling file may carry its SETTLEMENT on Ethereum (bitgraph-settlement/1,
|
|
14
|
+
* the `settlement` field): the batcher transaction whose blobs hold the Base
|
|
15
|
+
* block. Two layers, both offline, reported as separate lines: the POINTER
|
|
16
|
+
* (verifySettlementPointer, against the Base mainnet pins or ones the reader
|
|
17
|
+
* passes) and the BLOBS (blob bytes kept in the bundle next to the ceiling
|
|
18
|
+
* file or under a `blobs/` folder, verified against the listed KZG
|
|
19
|
+
* commitments and decoded down to the ceiling transaction). Blob bytes
|
|
20
|
+
* absent from the bundle are reported, never a failure; a pointer or blob
|
|
21
|
+
* that is present and wrong fails the ceiling like any other bad evidence.
|
|
22
|
+
* The settlement is checked whether or not the ceiling's proof is in the
|
|
23
|
+
* bundle: it is evidence about the ceiling file itself.
|
|
12
24
|
*/
|
|
13
25
|
|
|
14
|
-
import {
|
|
15
|
-
|
|
26
|
+
import {
|
|
27
|
+
verifyCeiling, checkCeilingOnline, verifySettlementPointer, BASE_MAINNET_SETTLEMENT_PINS, decodeHeader, evmHexToBytes,
|
|
28
|
+
type CeilingSidecar, type SettlementPointer, type SettlementPins,
|
|
29
|
+
} from "@mikeargento/bitgraph-verify";
|
|
30
|
+
import { streamArtifactsByHash } from "./ingest.js";
|
|
31
|
+
import { verifySettlementBlobs } from "./settlement-blobs.js";
|
|
32
|
+
import type { CeilingAnalysis, CeilingCheck, CeilingSettlementCheck, IngestResult } from "./types.js";
|
|
16
33
|
|
|
17
34
|
/** BitGraph's published ceiling writer on Base mainnet (bitgraph.ing/ceilings). */
|
|
18
35
|
export const BITGRAPH_CEILING_WRITER = "0xf3972408D853c975F86351C311f4310220bbF2a3";
|
|
@@ -27,51 +44,64 @@ export interface CeilingAuditOptions {
|
|
|
27
44
|
* network call (its zero-network guarantee), so the CLI never sets this.
|
|
28
45
|
*/
|
|
29
46
|
getBlockHash?: (blockNumber: number) => Promise<string | null>;
|
|
47
|
+
/** The batch inbox and batcher a settlement pointer must name. Base mainnet's by default. */
|
|
48
|
+
settlementPins?: SettlementPins;
|
|
30
49
|
}
|
|
31
50
|
|
|
32
51
|
export async function verifyCeilings(ingest: IngestResult, options: CeilingAuditOptions = {}): Promise<CeilingAnalysis> {
|
|
33
52
|
const writer = options.writer ?? BITGRAPH_CEILING_WRITER;
|
|
34
53
|
const chainId = options.chainId ?? BASE_MAINNET_CHAIN_ID;
|
|
54
|
+
const pins = options.settlementPins ?? BASE_MAINNET_SETTLEMENT_PINS;
|
|
35
55
|
const byHash = new Map(ingest.proofs.map((p) => [p.proofHash, p]));
|
|
36
56
|
const checks: CeilingCheck[] = [];
|
|
37
57
|
for (const f of ingest.ceilings ?? []) {
|
|
38
58
|
const side = f.json as unknown as CeilingSidecar;
|
|
39
59
|
const proofHash = typeof side.proofHash === "string" ? side.proofHash : null;
|
|
40
60
|
const observed = proofHash ? byHash.get(proofHash) : undefined;
|
|
61
|
+
let check: CeilingCheck;
|
|
41
62
|
if (!observed) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
check = { path: f.path, proofHash, status: "unmatched", reason: "no proof in this bundle has this ceiling's proofHash", onChain: null };
|
|
64
|
+
} else {
|
|
65
|
+
const r = await verifyCeiling(observed.proof, side, { writerAddress: writer, chainId });
|
|
66
|
+
if (!r.ok) {
|
|
67
|
+
const pending = r.status === "pending";
|
|
68
|
+
check = { path: f.path, proofHash, status: pending ? "pending" : "failed", ...(r.reason ? { reason: r.reason } : {}), onChain: null };
|
|
69
|
+
} else {
|
|
70
|
+
const w = r.window!;
|
|
71
|
+
check = {
|
|
72
|
+
path: f.path,
|
|
73
|
+
proofHash,
|
|
74
|
+
status: "verified",
|
|
75
|
+
...(r.label ? { label: r.label } : {}),
|
|
76
|
+
window: {
|
|
77
|
+
floorBlock: w.floor.blockNumber || null,
|
|
78
|
+
floorTime: w.floor.blockTimestamp,
|
|
79
|
+
ceilingChainId: w.ceiling.chainId,
|
|
80
|
+
ceilingBlock: w.ceiling.blockNumber,
|
|
81
|
+
ceilingTime: w.ceiling.blockTimestamp,
|
|
82
|
+
widthSeconds: w.widthSeconds,
|
|
83
|
+
},
|
|
84
|
+
onChain: null,
|
|
85
|
+
onChainDetail: "header not checked against chain",
|
|
86
|
+
};
|
|
87
|
+
if (options.getBlockHash) {
|
|
88
|
+
const online = await checkCeilingOnline(side, options.getBlockHash);
|
|
89
|
+
check.onChain = online.onChain;
|
|
90
|
+
check.onChainDetail = online.detail;
|
|
91
|
+
if (online.onChain === false) {
|
|
92
|
+
check.status = "failed";
|
|
93
|
+
check.reason = `chain: ${online.detail}`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
50
97
|
}
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
status
|
|
56
|
-
...(r.label ? { label: r.label } : {}),
|
|
57
|
-
window: {
|
|
58
|
-
floorBlock: w.floor.blockNumber || null,
|
|
59
|
-
floorTime: w.floor.blockTimestamp,
|
|
60
|
-
ceilingChainId: w.ceiling.chainId,
|
|
61
|
-
ceilingBlock: w.ceiling.blockNumber,
|
|
62
|
-
ceilingTime: w.ceiling.blockTimestamp,
|
|
63
|
-
widthSeconds: w.widthSeconds,
|
|
64
|
-
},
|
|
65
|
-
onChain: null,
|
|
66
|
-
onChainDetail: "header not checked against chain",
|
|
67
|
-
};
|
|
68
|
-
if (options.getBlockHash) {
|
|
69
|
-
const online = await checkCeilingOnline(side, options.getBlockHash);
|
|
70
|
-
check.onChain = online.onChain;
|
|
71
|
-
check.onChainDetail = online.detail;
|
|
72
|
-
if (online.onChain === false) {
|
|
98
|
+
const carried = f.json["settlement"];
|
|
99
|
+
if (carried !== null && carried !== undefined) {
|
|
100
|
+
const settlement = await auditSettlement(f.path, side, carried, ingest, pins);
|
|
101
|
+
check.settlement = settlement;
|
|
102
|
+
if (settlement.status === "failed" && check.status !== "failed") {
|
|
73
103
|
check.status = "failed";
|
|
74
|
-
check.reason =
|
|
104
|
+
check.reason = settlement.lines.find((l) => l.includes("FAILED")) ?? "settlement: failed";
|
|
75
105
|
}
|
|
76
106
|
}
|
|
77
107
|
checks.push(check);
|
|
@@ -83,3 +113,107 @@ export async function verifyCeilings(ingest: IngestResult, options: CeilingAudit
|
|
|
83
113
|
}));
|
|
84
114
|
return { writer, chainId, checks, statuses };
|
|
85
115
|
}
|
|
116
|
+
|
|
117
|
+
// ── settlement ─────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
function whenUtc(unix: number): string {
|
|
120
|
+
const iso = new Date(unix * 1000).toISOString();
|
|
121
|
+
return `${iso.slice(0, 10)} ${iso.slice(11, 19)} UTC`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** The bundle paths a blob file named `file` may sit at, next to the ceiling file first. */
|
|
125
|
+
function blobCandidatePaths(ceilingPath: string, file: string): string[] {
|
|
126
|
+
const slash = ceilingPath.lastIndexOf("/");
|
|
127
|
+
const dir = slash < 0 ? "" : ceilingPath.slice(0, slash + 1);
|
|
128
|
+
return [`${dir}${file}`, `${dir}blobs/${file}`, `blobs/${file}`, file];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function auditSettlement(
|
|
132
|
+
path: string,
|
|
133
|
+
side: CeilingSidecar,
|
|
134
|
+
carried: unknown,
|
|
135
|
+
ingest: IngestResult,
|
|
136
|
+
pins: SettlementPins,
|
|
137
|
+
): Promise<CeilingSettlementCheck> {
|
|
138
|
+
const lines: string[] = [];
|
|
139
|
+
const failed = (pointer: CeilingSettlementCheck["pointer"], blobs: CeilingSettlementCheck["blobs"]): CeilingSettlementCheck =>
|
|
140
|
+
({ status: "failed", pointer, blobs, lines });
|
|
141
|
+
const noBlobs = (detail: string, listed: number): CeilingSettlementCheck["blobs"] => ({ status: "absent", detail, listed, supplied: 0 });
|
|
142
|
+
|
|
143
|
+
// 1. The pointer: header, inclusion, batcher, blob commitments.
|
|
144
|
+
const pointer = carried as SettlementPointer;
|
|
145
|
+
const p = verifySettlementPointer(pointer, pins);
|
|
146
|
+
const listed = Array.isArray(pointer?.blobs) ? pointer.blobs.length : 0;
|
|
147
|
+
if (!p.ok) {
|
|
148
|
+
lines.push(`settlement: pointer FAILED (${p.reason ?? "invalid"})`);
|
|
149
|
+
return failed({ ok: false, reason: p.reason ?? "invalid" }, noBlobs("not checked: the pointer failed", listed));
|
|
150
|
+
}
|
|
151
|
+
const existedBy = p.existedBy!;
|
|
152
|
+
// The pointer must be about THIS ceiling's Base block and transaction.
|
|
153
|
+
const anchor = side.anchor;
|
|
154
|
+
let mismatch: string | null = null;
|
|
155
|
+
if (!anchor) mismatch = "the ceiling has no Base transaction yet";
|
|
156
|
+
else if (pointer.baseBlockNumber !== anchor.blockNumber || pointer.baseBlockHash.toLowerCase() !== anchor.blockHash.toLowerCase()) {
|
|
157
|
+
mismatch = `the pointer names Base block ${pointer.baseBlockNumber} (${pointer.baseBlockHash.slice(0, 10)}…), this ceiling is in block ${anchor.blockNumber} (${anchor.blockHash.slice(0, 10)}…)`;
|
|
158
|
+
} else if (pointer.located && pointer.located.txHash.toLowerCase() !== anchor.txHash.toLowerCase()) {
|
|
159
|
+
mismatch = `the pointer locates transaction ${pointer.located.txHash.slice(0, 10)}…, this ceiling's is ${anchor.txHash.slice(0, 10)}…`;
|
|
160
|
+
}
|
|
161
|
+
if (mismatch) {
|
|
162
|
+
lines.push(`settlement: pointer FAILED (${mismatch})`);
|
|
163
|
+
return failed({ ok: false, reason: mismatch, existedBy }, noBlobs("not checked: the pointer is for another block", listed));
|
|
164
|
+
}
|
|
165
|
+
lines.push(`settlement: pointer ok, Ethereum block ${existedBy.blockNumber} at ${whenUtc(existedBy.blockTimestamp)}`);
|
|
166
|
+
const pointerOk: CeilingSettlementCheck["pointer"] = { ok: true, existedBy };
|
|
167
|
+
|
|
168
|
+
// 2. The blobs, when the bundle holds their bytes.
|
|
169
|
+
const wanted = new Map<string, string>(); // sha256 hex -> versioned hash
|
|
170
|
+
const byPath = new Map<string, string>(); // bundle path -> sha256 hex
|
|
171
|
+
for (const a of ingest.artifacts) for (const ap of a.paths) byPath.set(ap, a.sha256Hex);
|
|
172
|
+
const byBasename = new Map<string, string>();
|
|
173
|
+
for (const a of ingest.artifacts) for (const ap of a.paths) byBasename.set(ap.slice(ap.lastIndexOf("/") + 1), a.sha256Hex);
|
|
174
|
+
for (const b of pointer.blobs) {
|
|
175
|
+
const file = typeof b.file === "string" ? b.file : `${b.versionedHash.toLowerCase()}.bin`;
|
|
176
|
+
if (file.includes("/") || file.includes("\\") || file.includes("..")) continue;
|
|
177
|
+
const hex = blobCandidatePaths(path, file).map((c) => byPath.get(c)).find((h) => h !== undefined) ?? byBasename.get(file);
|
|
178
|
+
if (hex) wanted.set(hex, b.versionedHash.toLowerCase());
|
|
179
|
+
}
|
|
180
|
+
if (wanted.size === 0) {
|
|
181
|
+
lines.push("settlement: blobs not in bundle");
|
|
182
|
+
return { status: "pointer-only", pointer: pointerOk, blobs: noBlobs("blobs not in bundle", listed), lines };
|
|
183
|
+
}
|
|
184
|
+
const bytesByHash = new Map<string, Uint8Array>();
|
|
185
|
+
for await (const m of streamArtifactsByHash(ingest, wanted.keys())) bytesByHash.set(wanted.get(m.sha256Hex)!, m.bytes);
|
|
186
|
+
let expectParent: string | undefined;
|
|
187
|
+
try {
|
|
188
|
+
expectParent = anchor ? decodeHeader(evmHexToBytes(anchor.blockHeader)).parentHash : undefined;
|
|
189
|
+
} catch {
|
|
190
|
+
expectParent = undefined;
|
|
191
|
+
}
|
|
192
|
+
const r = await verifySettlementBlobs(pointer, bytesByHash, {
|
|
193
|
+
expect: {
|
|
194
|
+
txHash: anchor!.txHash,
|
|
195
|
+
rawTx: anchor!.rawTx,
|
|
196
|
+
...(expectParent ? { baseParentHash: expectParent } : {}),
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
if (!r.ok) {
|
|
200
|
+
lines.push(`settlement: blobs FAILED (${r.reason ?? "invalid"})`);
|
|
201
|
+
return failed(pointerOk, { status: "failed", detail: r.reason ?? "invalid", listed, supplied: bytesByHash.size });
|
|
202
|
+
}
|
|
203
|
+
const c = r.channel!;
|
|
204
|
+
const frames = c.complete ? `all ${c.framesDecoded} frames` : `frames 0..${c.framesDecoded - 1}${c.framesTotal ? ` of ${c.framesTotal}` : ""}`;
|
|
205
|
+
const loc = r.located!;
|
|
206
|
+
lines.push(`settlement: blobs decoded (${frames}), ceiling tx located in Base block ${loc.baseBlockNumber}${loc.batchComplete ? "" : " (batch cut at the last frame supplied)"}`);
|
|
207
|
+
return {
|
|
208
|
+
status: "verified",
|
|
209
|
+
pointer: pointerOk,
|
|
210
|
+
blobs: {
|
|
211
|
+
status: "verified",
|
|
212
|
+
detail: `${bytesByHash.size} of ${listed} blobs supplied; ${frames} decoded; ${r.checks.map((k) => k.detail ?? k.name).join("; ")}`,
|
|
213
|
+
listed,
|
|
214
|
+
supplied: bytesByHash.size,
|
|
215
|
+
located: { baseBlockNumber: loc.baseBlockNumber, baseTxIndex: loc.baseTxIndex, txHash: loc.txHash, batchComplete: loc.batchComplete, framesDecoded: c.framesDecoded, framesTotal: c.framesTotal },
|
|
216
|
+
},
|
|
217
|
+
lines,
|
|
218
|
+
};
|
|
219
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { parseCarrier, verifyCarrier } from "@mikeargento/bitgraph-verify";
|
|
|
25
25
|
import type { VerificationPolicy } from "@mikeargento/bitgraph-verify";
|
|
26
26
|
import { auditToolVersion, computeExitFlags, runAudit } from "./audit.js";
|
|
27
27
|
import { attestationTimestampMs } from "./attestation.js";
|
|
28
|
+
import { exportRunClaims } from "./exports.js";
|
|
28
29
|
import { buildJsonReport } from "./report-json.js";
|
|
29
30
|
import { buildMarkdownReport } from "./report-md.js";
|
|
30
31
|
import type { ExitFlags } from "./types.js";
|
|
@@ -67,6 +68,14 @@ function helpText(): string {
|
|
|
67
68
|
"printed first.",
|
|
68
69
|
"archive. The audit runs entirely offline: no RPC, no HTTP, no DNS.",
|
|
69
70
|
"",
|
|
71
|
+
"Exports (bitgraph-export/1) anywhere in the bundle are found by their",
|
|
72
|
+
"format field. Each is checked with verifyExport once per file in the",
|
|
73
|
+
"bundle it covers (by SHA-256: a member's committed bytes or original;",
|
|
74
|
+
"for the owner's export, any leaf's), or once without a file when none",
|
|
75
|
+
"is there, and its proof joins the proof analysis. Every claim, the",
|
|
76
|
+
"covered files and the three time claims (floor; ceiling on Base,",
|
|
77
|
+
"provisional; ceiling on Ethereum) are reported, never merged.",
|
|
78
|
+
"",
|
|
70
79
|
"Options:",
|
|
71
80
|
" --out <dir> Directory to write the report files into",
|
|
72
81
|
" (default: current directory; created if missing).",
|
|
@@ -98,7 +107,12 @@ function helpText(): string {
|
|
|
98
107
|
" are absent from the bundle is NOT a failure by itself: its",
|
|
99
108
|
" bytes-free checks decide, unless a supplied trust policy makes",
|
|
100
109
|
" them fail (for example requireSlot), in which case it counts",
|
|
101
|
-
" here.",
|
|
110
|
+
" here. Also: an export (bitgraph-export/1) with any FALSE claim,",
|
|
111
|
+
" its attestation claims included (an export is checked as one",
|
|
112
|
+
" self-contained object, as a carrier is), or an export-shaped",
|
|
113
|
+
" file that is malformed, of an unsupported format, or too large",
|
|
114
|
+
" to read. A claim the export does not carry (NOT_CARRIED: a",
|
|
115
|
+
" pending ceiling, the covered file absent) is never a failure.",
|
|
102
116
|
" 2 Chain or authority anomalies, divergences between valid proofs,",
|
|
103
117
|
" or anchor witness verification failures: unexplained counter",
|
|
104
118
|
" positions, chain breaks, collisions, cross-kind position reuse,",
|
|
@@ -122,7 +136,8 @@ function helpText(): string {
|
|
|
122
136
|
"change the exit code on their own: an invalid attestation document on",
|
|
123
137
|
"an otherwise verified proof is reported without affecting the exit",
|
|
124
138
|
"code, and counts under exit bit 1 only when a supplied trust policy",
|
|
125
|
-
"made verification itself fail.",
|
|
139
|
+
"made verification itself fail. The exception is an export's own",
|
|
140
|
+
"attestation claims, which are part of the export's verdict (exit 1).",
|
|
126
141
|
].join("\n");
|
|
127
142
|
}
|
|
128
143
|
|
|
@@ -234,7 +249,7 @@ function loadTrustPolicy(path: string): VerificationPolicy {
|
|
|
234
249
|
function exitMeaning(flags: ExitFlags): string {
|
|
235
250
|
if (flags.code === 0) return "clean: no verification failures, no chain anomalies, no divergences";
|
|
236
251
|
const parts: string[] = [];
|
|
237
|
-
if (flags.verificationFailures) parts.push("verification failures");
|
|
252
|
+
if (flags.verificationFailures) parts.push("verification failures (a proof, or an export with a FALSE claim)");
|
|
238
253
|
if (flags.chainAnomaliesOrDivergences) {
|
|
239
254
|
parts.push("chain anomalies, divergences, or anchor witness verification failures (or a failed ceiling in time)");
|
|
240
255
|
}
|
|
@@ -375,8 +390,29 @@ async function main(): Promise<number> {
|
|
|
375
390
|
process.stdout.write(c.status === "verified"
|
|
376
391
|
? `ceiling VERIFIED ${c.path}: ${c.label ?? ""}${width}; ${c.onChainDetail ?? ""}\n`
|
|
377
392
|
: `ceiling ${c.status.toUpperCase()} ${c.path}: ${c.reason ?? ""}\n`);
|
|
393
|
+
for (const line of c.settlement?.lines ?? []) process.stdout.write(` ${line}\n`);
|
|
378
394
|
}
|
|
379
395
|
for (const s of result.ceilings?.statuses ?? []) process.stdout.write(`ceiling ${s.status} ${s.path}: ${s.note}\n`);
|
|
396
|
+
for (const e of result.exports?.checks ?? []) {
|
|
397
|
+
if (e.status !== "checked") {
|
|
398
|
+
process.stdout.write(`export NOT CHECKED ${e.path}: ${e.reason ?? e.status}\n`);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
const withFile = e.runs.filter((r) => r.file !== null).length;
|
|
402
|
+
const files = e.files.length === 0 ? "no covered file in the bundle" : `${withFile} of ${e.files.length} covered file${e.files.length === 1 ? "" : "s"} checked`;
|
|
403
|
+
process.stdout.write(`export ${e.verdict} ${e.path}: ${e.kind ?? "export"}, ${files}${e.failedClaims.length > 0 ? `; FALSE: ${e.failedClaims.join(", ")}` : ""}\n`);
|
|
404
|
+
// The three time claims, one line each, never merged.
|
|
405
|
+
const t = e.times;
|
|
406
|
+
const when = (unix: number) => new Date(unix * 1000).toISOString();
|
|
407
|
+
const firstClaims = e.runs[0] !== undefined ? exportRunClaims(e, e.runs[0]) : e.claims;
|
|
408
|
+
const why = (id: string) => {
|
|
409
|
+
const c = firstClaims.find((x) => x.id === id);
|
|
410
|
+
return c ? `not established (${c.result}: ${c.detail})` : "not established";
|
|
411
|
+
};
|
|
412
|
+
process.stdout.write(` floor: ${t.floor ? `committed bytes finished after Ethereum block ${t.floor.blockNumber} (mined ${when(t.floor.blockTimestamp)})` : why("floor.header")}\n`);
|
|
413
|
+
process.stdout.write(` ceiling on Base: ${t.ceilingBase ? `existed by Base block ${t.ceilingBase.blockNumber} (${when(t.ceilingBase.blockTimestamp)})${t.ceilingBase.provisional ? ", provisional until checked against Base" : ""}` : why("ceiling.base")}\n`);
|
|
414
|
+
process.stdout.write(` ceiling on Ethereum: ${t.ceilingEthereum ? `existed by Ethereum block ${t.ceilingEthereum.blockNumber} (${when(t.ceilingEthereum.blockTimestamp)})` : why("ceiling.ethereum")}\n`);
|
|
415
|
+
}
|
|
380
416
|
process.stdout.write(
|
|
381
417
|
`bitgraph-audit ${auditToolVersion()}: wrote ${written.join(", ")}\n` +
|
|
382
418
|
`exit ${flags.code}: ${exitMeaning(flags)}\n`
|