Reference apps: a receipt proves against the earliest certified checkpoint above its block (the smallest proof, 10 headers and 143 KB instead of 289 headers and 2 MB), the certificate used travels in the receipt
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
e1a4a16aa1
commit
24341d3ae1
4 changed files with 30 additions and 7 deletions
|
|
@ -73,6 +73,23 @@ function shape(row, source) {
|
|||
};
|
||||
}
|
||||
|
||||
/** The earliest certified Devnet 3 checkpoint whose chain block number is at least `number` (the reference apps' receipt:
|
||||
* the smallest proof that a block is final), from the dn3_ rows; null when no certificate reaches that block yet.
|
||||
* `chainNumberOf(hash)` resolves a checkpoint hash to its chain block number (the exec RPC); rows are tried newest first
|
||||
* until one falls below `number`, so the cost is a few lookups. */
|
||||
export async function earliestCheckpointAbove(sql, number, chainNumberOf, table = 'dn3_live_certificates') {
|
||||
const rows = await sql(`SELECT index, hash FROM ${table} ORDER BY index DESC LIMIT 400`).catch(() => []);
|
||||
let pick = null;
|
||||
for (const r of rows) {
|
||||
const n = await chainNumberOf(r.hash).catch(() => null);
|
||||
if (n === null) continue;
|
||||
if (n >= number) pick = r; else break;
|
||||
}
|
||||
if (!pick) return null;
|
||||
const full = await sql(`SELECT * FROM ${table} WHERE index = $1 LIMIT 1`, [Number(pick.index)]);
|
||||
return full[0] ? shape(full[0], 'dn3') : null;
|
||||
}
|
||||
|
||||
/** The latest certified checkpoint of `want` ('live', 'test' or 'dn3') through `sql`, shaped for the verifier, or null. */
|
||||
export async function readCheckpoint(sql, want = 'live') {
|
||||
let row = null, source = null;
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@ export async function runReceipt(tx) {
|
|||
const cp = await getJson(`${API}/checkpoint`);
|
||||
setStatus(`checkpoint ${cp.index} fetched; fetching the inclusion proof…`, 'busy');
|
||||
const r = await getJson(`${API}/receipt?tx=${tx}&checkpoint=${cp.hash}&index=${cp.index}`);
|
||||
const receipt = { format: 'igneum-receipt-v1', issued: new Date().toISOString(), ...r, checkpoint: { ...r.checkpoint, certificate: cp } };
|
||||
// the service may answer with an earlier certificate (the first checkpoint above the block: the smallest proof); it is verified like any other
|
||||
const receipt = { format: 'igneum-receipt-v1', issued: new Date().toISOString(), ...r, checkpoint: { ...r.checkpoint, certificate: r.checkpoint.certificate || cp } };
|
||||
delete receipt.ok; delete receipt.now;
|
||||
setStatus('verifying in this tab…', 'busy');
|
||||
await new Promise(resolve => setTimeout(resolve, 20));
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ try {
|
|||
let tx = q.get('tx');
|
||||
if (!tx) { const cb = await rpc('eth_getBlockByHash', ['0x' + cp.hash, false]); const n = parseInt(cb.number, 16); for (let k = n - 1; k > n - 200 && !tx; k--) { const b = await rpc('eth_getBlockByNumber', ['0x' + k.toString(16), false]); if (b && b.transactions.length) tx = b.transactions[0]; } }
|
||||
const rr = await get(`${API}/receipt?tx=${tx}&checkpoint=${cp.hash}&index=${cp.index}`);
|
||||
const receipt = { format: 'igneum-receipt-v1', ...rr, checkpoint: { ...rr.checkpoint, certificate: cp } };
|
||||
const receipt = { format: 'igneum-receipt-v1', ...rr, checkpoint: { ...rr.checkpoint, certificate: rr.checkpoint.certificate || cp } };
|
||||
const R = [
|
||||
['raw transaction altered by one nibble', d => { d.raw_tx_hex = flipHex(d.raw_tx_hex, 40); }],
|
||||
['transaction hash altered', d => { d.tx_hash = flipHex(d.tx_hash, 10); }],
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import { createServer } from 'node:http';
|
|||
import { blake2b } from '@noble/hashes/blake2.js';
|
||||
import { keccak_256 } from '@noble/hashes/sha3.js';
|
||||
import { coinbaseTxHash, merkleRoot, merklePath, hexToBytes, bytesToHex, segmentRecordsOf } from '../../../site/lc/core.js';
|
||||
import { readCheckpoint, neon } from '../../../site/api/checkpoint.mjs';
|
||||
import { readCheckpoint, earliestCheckpointAbove, neon } from '../../../site/api/checkpoint.mjs';
|
||||
|
||||
const PORT = Number(process.env.LIGHT_PORT || 26890);
|
||||
const EXEC = process.env.EXEC_RPC || 'http://127.0.0.1:26881';
|
||||
|
|
@ -207,13 +207,18 @@ void merklePath;
|
|||
async function receipt(q) {
|
||||
const tx = q.get('tx') || '';
|
||||
if (!/^0x[0-9a-fA-F]{64}$/.test(tx)) throw httpError(400, 'tx: 0x and 64 hex digits');
|
||||
const cpHash = q.get('checkpoint'), cpIndex = Number(q.get('index'));
|
||||
if (!cpHash || !(cpIndex >= 0)) throw httpError(400, 'checkpoint and index are required (from /api/checkpoint?source=dn3)');
|
||||
let cpHash = q.get('checkpoint'), cpIndex = Number(q.get('index'));
|
||||
if (!cpHash || !(cpIndex >= 0)) throw httpError(400, 'checkpoint and index are required (from /checkpoint)');
|
||||
const t = await exec('eth_getTransactionByHash', [tx]);
|
||||
if (!t) throw httpError(404, 'the node has not executed a transaction with that hash (unknown, or not yet in a block)');
|
||||
const rcpt = await exec('eth_getTransactionReceipt', [tx]);
|
||||
const cpNumber = await chainNumberOf(cpHash);
|
||||
const chainNumber = Number(t.blockNumber);
|
||||
// the smallest proof: the earliest certified checkpoint at or above the block (from the Devnet 3 observer's rows), else
|
||||
// the checkpoint the caller named; the certificate used is returned so the page verifies against that one
|
||||
let certificate = null;
|
||||
if (process.env.DATABASE_URL) certificate = await earliestCheckpointAbove(neon(), chainNumber, chainNumberOf).catch(() => null);
|
||||
if (certificate) { cpHash = certificate.hash; cpIndex = Number(certificate.index); }
|
||||
const cpNumber = await chainNumberOf(cpHash);
|
||||
if (chainNumber > cpNumber) throw httpError(409, `not final yet: executed at chain block ${chainNumber}, the latest certified checkpoint is chain block ${cpNumber}; try again in about ${chainNumber - cpNumber + 30} s`);
|
||||
const including = strip(t.igneum.includingBlock);
|
||||
const b = await body(including);
|
||||
|
|
@ -226,7 +231,7 @@ async function receipt(q) {
|
|||
const headers = await headerPath(including, chainNumber, cpNumber, cpHash);
|
||||
return {
|
||||
ok: true, now: new Date().toISOString(), chain_id: 'igneum-devnet-3', tx_hash: strip(tx), raw_tx_hex: strip(raws[idx]),
|
||||
checkpoint: { hash: strip(cpHash), index: cpIndex, chain_block: cpNumber },
|
||||
checkpoint: { hash: strip(cpHash), index: cpIndex, chain_block: cpNumber, certificate: certificate || undefined },
|
||||
including_block: { header: headers[0], leaf_index: idx + 1, leaf_count: leaves.length, merkle_siblings: siblings(leaves, idx + 1) },
|
||||
headers,
|
||||
execution: rcpt ? { chain_block: chainNumber, chain_block_hash: strip(rcpt.blockHash), status: rcpt.status, gas_used: rcpt.gasUsed, from: rcpt.from, to: rcpt.to, contract_address: rcpt.contractAddress, logs: (rcpt.logs || []).length, effective_gas_price: rcpt.effectiveGasPrice, as_reported_by: 'the node (not proven here)' } : null,
|
||||
|
|
|
|||
Loading…
Reference in a new issue