Fix cross-network block detection and remove false orphan artifacts

- 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
This commit is contained in:
satoshi
2026-05-10 17:22:56 +03:00
parent 92fcb70d93
commit 0787f092ff
5 changed files with 391 additions and 62 deletions
+38
View File
@@ -287,3 +287,41 @@ func (s *BlockStore) MarkOrphaned(height int64, at time.Time) error {
)
return err
}
// UnmarkOrphaned clears the orphaned_at stamp set by MarkOrphaned.
// Used to correct false positives caused by a network switch: a block
// found on testnet is not "orphaned" when the node switches to mainnet —
// it simply belongs to a different chain and must not be compared against
// mainnet's canonical history.
func (s *BlockStore) UnmarkOrphaned(height int64) error {
_, err := s.db.Exec(
`UPDATE blocks SET orphaned_at = 0 WHERE height = ?`,
height,
)
return err
}
// UnmarkOrphanedStampChain clears orphaned_at and simultaneously sets the
// chain field. Used for legacy rows (chain == "") that were falsely orphaned
// during a network switch: by stamping a non-mainnet chain value we prevent
// the reconcile reorg-detection pass from re-checking the block against the
// current chain's canonical hashes.
func (s *BlockStore) UnmarkOrphanedStampChain(height int64, chain string) error {
_, err := s.db.Exec(
`UPDATE blocks SET orphaned_at = 0, chain = ? WHERE height = ?`,
chain, height,
)
return err
}
// StampChain sets the chain field on a block without touching orphaned_at.
// Used when the reconcile loop determines a block belongs to a different
// network — the block is not orphaned (a reorg), it just doesn't belong
// to the current chain.
func (s *BlockStore) StampChain(height int64, chain string) error {
_, err := s.db.Exec(
`UPDATE blocks SET chain = ? WHERE height = ?`,
chain, height,
)
return err
}