Explorer indexer: block hashes stored without 0x, the DAG convention of live_blocks, so a transaction joins its block

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-08 10:09:05 +00:00
parent c3957f2879
commit 64f74beb08
2 changed files with 5 additions and 5 deletions

View file

@ -18,7 +18,7 @@
// EXPLORER_TICK_MS the idle tick, default 2000
//
// Tables (created on start if missing; the prefix is written as T below):
// T live_txs one row per EVM transaction: hash, block_hash, block_number, tx_index, from_addr, to_addr, value_wei,
// T live_txs one row per EVM transaction: hash (0x), block_hash (the DAG block's hash, no 0x, as live_blocks), block_number, tx_index, from_addr, to_addr, value_wei,
// nonce, gas, gas_price, gas_used, effective_gas_price, status, contract_address, input_bytes, selector,
// logs_count, logs (jsonb), igneum (jsonb: the receipt's igneum section), tx_type, ts_ms, received_at
// T live_accounts one row per address seen: balance_wei, nonce, code_bytes, is_contract, sent, received, first_seen_number,
@ -53,7 +53,7 @@ export const lower = a => (a ? String(a).toLowerCase() : null);
export function txRow(tx, rc, block) {
const input = String(tx.input || '0x');
return {
hash: lower(tx.hash), block_hash: lower(block.hash), block_number: hexInt(block.number), tx_index: hexInt(tx.transactionIndex) ?? 0,
hash: lower(tx.hash), block_hash: lower(block.hash).replace(/^0x/, ''), block_number: hexInt(block.number), tx_index: hexInt(tx.transactionIndex) ?? 0,
from_addr: lower(tx.from), to_addr: lower(tx.to), value_wei: hexBig(tx.value) || '0', nonce: hexInt(tx.nonce), gas: hexInt(tx.gas),
gas_price: hexBig(tx.gasPrice ?? tx.maxFeePerGas) , gas_used: rc ? hexInt(rc.gasUsed) : null, effective_gas_price: rc ? hexBig(rc.effectiveGasPrice) : null,
status: rc && rc.status !== undefined && rc.status !== null ? hexInt(rc.status) : null, contract_address: rc ? lower(rc.contractAddress) : null,
@ -144,7 +144,7 @@ async function indexNumbers(sql, numbers) {
const byHash = new Map((receipts[i] || []).map(r => [lower(r.transactionHash), r]));
const txs = (b.transactions || []).map(t => txRow(t, byHash.get(lower(t.hash)) || null, b));
rows.push(...txs);
blockRows.push({ number: hexInt(b.number), hash: lower(b.hash), tx_count: txs.length, gas_used: hexInt(b.gasUsed), ts_ms: hexInt(b.timestamp) * 1000 });
blockRows.push({ number: hexInt(b.number), hash: lower(b.hash).replace(/^0x/, ''), tx_count: txs.length, gas_used: hexInt(b.gasUsed), ts_ms: hexInt(b.timestamp) * 1000 });
for (const [a, kinds] of touched(txs, b)) { const q = accountQueue.get(a) || { kinds: new Set(), number: 0 }; for (const k of kinds) q.kinds.add(k); q.number = Math.max(q.number, hexInt(b.number)); accountQueue.set(a, q); }
}
// a number re-indexed after a reorg drops its old rows first
@ -233,7 +233,7 @@ async function main() {
const lo = Math.max(0, indexed - 2);
const stored = await sql(`SELECT number, hash FROM ${T}explorer_blocks WHERE number BETWEEN $1 AND $2`, [lo, indexed]);
const live = await Promise.all(stored.map(s => rpc('eth_getBlockByNumber', [hex(Number(s.number)), false]).catch(() => null)));
const moved = stored.filter((s, i) => live[i] && lower(live[i].hash) !== s.hash).map(s => Number(s.number));
const moved = stored.filter((s, i) => live[i] && lower(live[i].hash).replace(/^0x/, '') !== s.hash).map(s => Number(s.number));
if (moved.length) { const from = Math.min(...moved); log(`reorg: chain block ${from} moved, re-indexing from it`); indexed = from - 1; }
}
const behind = tip - indexed;

View file

@ -15,7 +15,7 @@ test('hex helpers: null stays null, big values keep every digit', () => {
test('txRow: lower-case addresses and hashes, numbers decoded, the receipt folded in, the selector and the log list', () => {
const r = txRow(tx, rc, block);
assert.equal(r.hash, tx.hash.toLowerCase()); assert.equal(r.block_hash, block.hash.toLowerCase()); assert.equal(r.block_number, 25933); assert.equal(r.tx_index, 3);
assert.equal(r.hash, tx.hash.toLowerCase()); assert.equal(r.block_hash, block.hash.toLowerCase().slice(2)); assert.equal(r.block_number, 25933); assert.equal(r.tx_index, 3);
assert.equal(r.from_addr, '0xe617a7f966fc009934e943fe0453821ff0c8716f'); assert.equal(r.to_addr, '0x7fe2f74b45dd8f0a739c906817f197631fdeff85');
assert.equal(r.value_wei, '0'); assert.equal(r.nonce, 26); assert.equal(r.gas, 231734); assert.equal(r.gas_used, 28254); assert.equal(r.status, 1);
assert.equal(r.selector, '0xa9059cbb'); assert.equal(r.input_bytes, 16); assert.equal(r.logs_count, 1); assert.equal(r.logs[0].address, '0x7fe2f74b45dd8f0a739c906817f197631fdeff85');