Mainnet indexer fix + retire testnet copy

- chain.js: derive the chain tip as the MAX height across the RPC pool and scan
  getLogs against that same synced node. Load-balanced public RPCs (publicnode)
  intermittently answer eth_blockNumber from a replica lagging thousands of
  blocks behind, which stalled the mainnet scan (latest < lastBlock, 0 events)
  and could skip freshly-mined events. This unblocks the live ledger + the
  purchase-confirmation email (both driven by the event scan).
- disclaimer: replace the "rehearsal/testnet" section with a live-on-Polygon
  real-money notice (it's live now).
- Point the dead Amoy fallbacks (purchase email tx link, contract-page source
  link) at Polygon mainnet / the verified mainnet contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-08 11:20:47 -05:00
parent 7b131dad28
commit 873a9e5e4a
5 changed files with 30 additions and 9 deletions
+24 -3
View File
@@ -189,15 +189,36 @@ function saveState() {
fs.renameSync(tmp, STATE_FILE);
} catch (e) { console.error('chain state save failed', e.message); }
}
// Most-synced tip across the RPC pool. Public load-balanced nodes (publicnode)
// sometimes answer eth_blockNumber from a replica lagging thousands of blocks
// behind — which stalls the scan (latest < lastBlock) and, worse, could let a
// getLogs from a lagging node skip freshly-mined events for good. Take the MAX
// height and remember which node reported it, so we scan against a node we know
// is synced to that height.
async function bestTip() {
const urls = getConfig().rpcs;
let best = { block: 0, url: urls[0] };
await Promise.all(urls.map(async u => {
try { const b = parseInt(await rpcOnce(u, 'eth_blockNumber', []), 16);
if (b > best.block) best = { block: b, url: u }; } catch (e) {}
}));
return best;
}
async function tail() {
if (busy) return; busy = true;
try {
const latest = parseInt(await rpc('eth_blockNumber', []), 16);
const tip = await bestTip();
const latest = tip.block;
while (state.lastBlock < latest) {
const from = state.lastBlock + 1;
const to = Math.min(from + CHUNK - 1, latest);
const logs = await rpc('eth_getLogs', [{ address: getConfig().contract,
fromBlock: '0x' + from.toString(16), toBlock: '0x' + to.toString(16) }]);
// scan against the node we confirmed is synced to `latest`; fall back to
// the rotating pool only if that specific node errors on this range.
let logs;
try { logs = await rpcOnce(tip.url, 'eth_getLogs', [{ address: getConfig().contract,
fromBlock: '0x' + from.toString(16), toBlock: '0x' + to.toString(16) }]); }
catch (e) { logs = await rpc('eth_getLogs', [{ address: getConfig().contract,
fromBlock: '0x' + from.toString(16), toBlock: '0x' + to.toString(16) }]); }
for (const lg of logs) {
const ev = decodeLog(lg);
if (!ev) continue;