- Change OrphanedAt from time.Time to *time.Time so JSON omitempty
correctly omits zero values (Go serializes zero time.Time as
"0001-01-01T00:00:00Z" which JS treats as truthy)
- Rewrite reconcileOnce with 4-pass architecture: enrichment, reorg
detection via GetBlock verification, false-positive un-orphaning,
and legacy block stamping
- Use inferOtherChain() to stamp cross-network blocks with the correct
chain identifier (testnet4 reports as "testnet4", not "test")
- Move tailer startup after aggregator's first refresh so blocks are
always stamped with the current chain
- Add StampChain store method, debug-blocks admin endpoint, and
comprehensive reconcile tests for cross-network scenarios
Tests cover the four subsystems that sit between a share submission and
a confirmed block reward:
bitcoind/rpc_test.go — isRetryable classification, 3-attempt backoff
on 503, no retry on semantic RPC errors (-5), retry on warm-up (-28),
CoinbaseReward calculation.
logmon/tailer_test.go — handleLine regex parsing, share-diff carry from
attempt line to block event, diff reset after use, unrelated-line
silence, file-read via goroutine, cursor save/resume across restart,
log rotation re-open.
store/blocks_test.go — InsertBlock roundtrip (all fields including
chain), idempotent dedup, MarkOrphaned, UpdateEnrichment,
BlocksNeedingEnrichment, KV get/set/overwrite, and migration of an
existing DB that lacks the chain column.
state/blocks_test.go — reconcileOnce chain-filter (testnet block not
orphaned on mainnet), genuine reorg detection (different canonical
hash → orphaned), transient RPC error (no orphan on -5), legacy
empty-chain block still checked for reorgs, IngestBlockEvents new
block persisted + confirmed counter incremented, dedup skips counter
and OnRefresh, chain stamped from current snapshot at ingest time.