Add best share analysis page, miner column, and UI improvements

- Best share page: hex + binary hash comparison against network target,
  per-bit coloring showing exactly which bits prevented a valid block,
  toggle between network diff at time of finding vs current diff
- Capture best share hash from ckpool logs with one-time backfill
- Persist network difficulty at time of best share for historical accuracy
- Add miner (worker) column to blocks table via coinbase address matching
- Truncate block hashes in table with full hash on hover
- Increase hashrate chart Y-axis to 7 ticks for better readability
This commit is contained in:
satoshi
2026-05-18 17:19:35 +03:00
parent f1dc0a8a79
commit 1157f3501a
17 changed files with 1499 additions and 81 deletions
+18 -12
View File
@@ -29,6 +29,7 @@ type Block struct {
ShareDiff float64
OrphanedAt time.Time // zero value = not orphaned
Chain string // "main", "test", "signet", or "" for legacy rows
Miner string // workername (address.worker) who found the block
}
const schema = `
@@ -90,6 +91,11 @@ func (s *BlockStore) migrate() error {
return fmt.Errorf("store: add chain: %w", err)
}
}
if !have["miner"] {
if _, err := s.db.Exec(`ALTER TABLE blocks ADD COLUMN miner TEXT NOT NULL DEFAULT ''`); err != nil {
return fmt.Errorf("store: add miner: %w", err)
}
}
return nil
}
@@ -132,9 +138,9 @@ func (s *BlockStore) Close() error {
// unnoticed.
func (s *BlockStore) InsertBlock(b Block) (bool, error) {
res, err := s.db.Exec(
`INSERT OR IGNORE INTO blocks(height, hash, reward_btc, found_at, source, share_diff, chain)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
b.Height, b.Hash, b.RewardBT, b.FoundAt.Unix(), b.Source, b.ShareDiff, b.Chain,
`INSERT OR IGNORE INTO blocks(height, hash, reward_btc, found_at, source, share_diff, chain, miner)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
b.Height, b.Hash, b.RewardBT, b.FoundAt.Unix(), b.Source, b.ShareDiff, b.Chain, b.Miner,
)
if err != nil {
return false, err
@@ -212,7 +218,7 @@ func (s *BlockStore) Recent(limit int) ([]Block, error) {
limit = 256
}
rows, err := s.db.Query(
`SELECT height, hash, reward_btc, found_at, source, share_diff, orphaned_at, chain
`SELECT height, hash, reward_btc, found_at, source, share_diff, orphaned_at, chain, miner
FROM blocks ORDER BY height DESC LIMIT ?`,
limit,
)
@@ -224,7 +230,7 @@ func (s *BlockStore) Recent(limit int) ([]Block, error) {
for rows.Next() {
var b Block
var foundUnix, orphanedUnix int64
if err := rows.Scan(&b.Height, &b.Hash, &b.RewardBT, &foundUnix, &b.Source, &b.ShareDiff, &orphanedUnix, &b.Chain); err != nil {
if err := rows.Scan(&b.Height, &b.Hash, &b.RewardBT, &foundUnix, &b.Source, &b.ShareDiff, &orphanedUnix, &b.Chain, &b.Miner); err != nil {
return nil, err
}
b.FoundAt = time.Unix(foundUnix, 0).UTC()
@@ -245,9 +251,9 @@ func (s *BlockStore) Recent(limit int) ([]Block, error) {
// retrying will keep failing.
func (s *BlockStore) BlocksNeedingEnrichment(since time.Time) ([]Block, error) {
rows, err := s.db.Query(
`SELECT height, hash, reward_btc, found_at, source, share_diff, orphaned_at, chain
`SELECT height, hash, reward_btc, found_at, source, share_diff, orphaned_at, chain, miner
FROM blocks
WHERE found_at >= ? AND (hash = '' OR reward_btc = 0)
WHERE found_at >= ? AND (hash = '' OR reward_btc = 0 OR miner = '')
ORDER BY height ASC`,
since.Unix(),
)
@@ -259,7 +265,7 @@ func (s *BlockStore) BlocksNeedingEnrichment(since time.Time) ([]Block, error) {
for rows.Next() {
var b Block
var foundUnix, orphanedUnix int64
if err := rows.Scan(&b.Height, &b.Hash, &b.RewardBT, &foundUnix, &b.Source, &b.ShareDiff, &orphanedUnix, &b.Chain); err != nil {
if err := rows.Scan(&b.Height, &b.Hash, &b.RewardBT, &foundUnix, &b.Source, &b.ShareDiff, &orphanedUnix, &b.Chain, &b.Miner); err != nil {
return nil, err
}
b.FoundAt = time.Unix(foundUnix, 0).UTC()
@@ -271,12 +277,12 @@ func (s *BlockStore) BlocksNeedingEnrichment(since time.Time) ([]Block, error) {
return out, rows.Err()
}
// UpdateEnrichment fills in hash and reward for an already-recorded
// UpdateEnrichment fills in hash, reward, and miner for an already-recorded
// block. No-op if the row doesn't exist.
func (s *BlockStore) UpdateEnrichment(height int64, hash string, reward float64) error {
func (s *BlockStore) UpdateEnrichment(height int64, hash string, reward float64, miner string) error {
_, err := s.db.Exec(
`UPDATE blocks SET hash = ?, reward_btc = ? WHERE height = ?`,
hash, reward, height,
`UPDATE blocks SET hash = ?, reward_btc = ?, miner = ? WHERE height = ?`,
hash, reward, miner, height,
)
return err
}