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:
@@ -37,6 +37,7 @@ func (s *Server) Handler() http.Handler {
|
||||
mux.HandleFunc("GET /api/blocks", s.blocks)
|
||||
mux.HandleFunc("GET /api/snapshot", s.snapshot)
|
||||
mux.HandleFunc("GET /api/ws", s.handleWS)
|
||||
mux.HandleFunc("GET /api/admin/debug-blocks", s.debugBlocks)
|
||||
mux.Handle("/", spaHandler(webui.FS()))
|
||||
return mux
|
||||
}
|
||||
@@ -159,3 +160,51 @@ func (s *Server) clients(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) blocks(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, s.Agg.Blocks())
|
||||
}
|
||||
|
||||
// debugBlocks exposes raw block state from both the in-memory snapshot
|
||||
// (what the UI sees) and the DB (ground truth) for troubleshooting.
|
||||
func (s *Server) debugBlocks(w http.ResponseWriter, r *http.Request) {
|
||||
type row struct {
|
||||
Height int64 `json:"height"`
|
||||
Hash string `json:"hash"`
|
||||
Chain string `json:"chain"`
|
||||
OrphanedAt string `json:"orphaned_at"`
|
||||
FoundAt string `json:"found_at"`
|
||||
RewardBTC float64 `json:"reward_btc"`
|
||||
Source string `json:"source"`
|
||||
}
|
||||
fmtBlock := func(b state.BlockRecord) row {
|
||||
orphaned := ""
|
||||
if b.OrphanedAt != nil {
|
||||
orphaned = b.OrphanedAt.Format("2006-01-02T15:04:05Z")
|
||||
}
|
||||
return row{
|
||||
Height: b.Height,
|
||||
Hash: b.Hash,
|
||||
Chain: b.Chain,
|
||||
OrphanedAt: orphaned,
|
||||
FoundAt: b.FoundAt.Format("2006-01-02T15:04:05Z"),
|
||||
RewardBTC: b.RewardBT,
|
||||
Source: b.Source,
|
||||
}
|
||||
}
|
||||
|
||||
// In-memory blocks (what /api/blocks and /api/snapshot serve)
|
||||
memBlocks := s.Agg.Blocks()
|
||||
mem := make([]row, 0, len(memBlocks))
|
||||
for _, b := range memBlocks {
|
||||
mem = append(mem, fmtBlock(b))
|
||||
}
|
||||
|
||||
// DB blocks (ground truth)
|
||||
dbBlocks := s.Agg.BlocksFromStore()
|
||||
db := make([]row, 0, len(dbBlocks))
|
||||
for _, b := range dbBlocks {
|
||||
db = append(db, fmtBlock(b))
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"memory": mem,
|
||||
"db": db,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user