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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ func TestUpdateEnrichment(t *testing.T) {
|
||||
s := openTemp(t)
|
||||
s.InsertBlock(Block{Height: 300, FoundAt: time.Now(), Chain: "main"})
|
||||
|
||||
if err := s.UpdateEnrichment(300, "newhash", 3.125); err != nil {
|
||||
if err := s.UpdateEnrichment(300, "newhash", 3.125, "bc1qtest.rig1"); err != nil {
|
||||
t.Fatalf("UpdateEnrichment: %v", err)
|
||||
}
|
||||
|
||||
@@ -135,6 +135,9 @@ func TestUpdateEnrichment(t *testing.T) {
|
||||
if blocks[0].RewardBT != 3.125 {
|
||||
t.Errorf("RewardBT = %v, want 3.125", blocks[0].RewardBT)
|
||||
}
|
||||
if blocks[0].Miner != "bc1qtest.rig1" {
|
||||
t.Errorf("Miner = %q, want bc1qtest.rig1", blocks[0].Miner)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlocksNeedingEnrichment(t *testing.T) {
|
||||
@@ -146,7 +149,7 @@ func TestBlocksNeedingEnrichment(t *testing.T) {
|
||||
// Block with hash but no reward → needs enrichment.
|
||||
s.InsertBlock(Block{Height: 2, Hash: "abc", RewardBT: 0, FoundAt: now, Chain: "main"})
|
||||
// Block fully enriched → does NOT need enrichment.
|
||||
s.InsertBlock(Block{Height: 3, Hash: "def", RewardBT: 3.125, FoundAt: now, Chain: "main"})
|
||||
s.InsertBlock(Block{Height: 3, Hash: "def", RewardBT: 3.125, FoundAt: now, Chain: "main", Miner: "bc1q.rig1"})
|
||||
|
||||
since := now.Add(-time.Hour)
|
||||
missing, err := s.BlocksNeedingEnrichment(since)
|
||||
|
||||
Reference in New Issue
Block a user