Record and display winning share difficulty on found blocks

Logmon now captures the share diff from ckpool's "Possible block
solve" line preceding the confirmation and attaches it to the
BlockEvent. Persisted as share_diff alongside height/hash/reward
and rendered as a new column in the dashboard block history.
This commit is contained in:
satoshi
2026-04-14 18:15:44 +03:00
parent 89904d5e08
commit 64d8af407d
5 changed files with 112 additions and 44 deletions
+47 -12
View File
@@ -21,11 +21,12 @@ type BlockStore struct {
// package is the lower layer; the state package converts to/from this
// shape when reading and writing.
type Block struct {
Height int64
Hash string
RewardBT float64
FoundAt time.Time
Source string
Height int64
Hash string
RewardBT float64
FoundAt time.Time
Source string
ShareDiff float64
}
const schema = `
@@ -34,11 +35,40 @@ CREATE TABLE IF NOT EXISTS blocks (
hash TEXT NOT NULL DEFAULT '',
reward_btc REAL NOT NULL DEFAULT 0,
found_at INTEGER NOT NULL,
source TEXT NOT NULL DEFAULT ''
source TEXT NOT NULL DEFAULT '',
share_diff REAL NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS blocks_found_at_idx ON blocks(found_at);
`
// migrations additive only; safe to run every startup. SQLite ignores
// ADD COLUMN that already exists only if we check, so we query
// pragma table_info and apply missing ones.
func (s *BlockStore) migrate() error {
rows, err := s.db.Query(`PRAGMA table_info(blocks)`)
if err != nil {
return err
}
defer rows.Close()
have := map[string]bool{}
for rows.Next() {
var cid int
var name, ctype string
var notnull, pk int
var dflt sql.NullString
if err := rows.Scan(&cid, &name, &ctype, &notnull, &dflt, &pk); err != nil {
return err
}
have[name] = true
}
if !have["share_diff"] {
if _, err := s.db.Exec(`ALTER TABLE blocks ADD COLUMN share_diff REAL NOT NULL DEFAULT 0`); err != nil {
return fmt.Errorf("store: add share_diff: %w", err)
}
}
return nil
}
// Open initializes the store at path, creating the schema if needed.
// Callers are responsible for Close().
func Open(path string) (*BlockStore, error) {
@@ -50,7 +80,12 @@ func Open(path string) (*BlockStore, error) {
_ = db.Close()
return nil, fmt.Errorf("store: schema: %w", err)
}
return &BlockStore{db: db}, nil
s := &BlockStore{db: db}
if err := s.migrate(); err != nil {
_ = db.Close()
return nil, err
}
return s, nil
}
func (s *BlockStore) Close() error {
@@ -61,9 +96,9 @@ func (s *BlockStore) Close() error {
// log events after a restart don't trip the primary key constraint.
func (s *BlockStore) InsertBlock(b Block) error {
_, err := s.db.Exec(
`INSERT OR IGNORE INTO blocks(height, hash, reward_btc, found_at, source)
VALUES (?, ?, ?, ?, ?)`,
b.Height, b.Hash, b.RewardBT, b.FoundAt.Unix(), b.Source,
`INSERT OR IGNORE INTO blocks(height, hash, reward_btc, found_at, source, share_diff)
VALUES (?, ?, ?, ?, ?, ?)`,
b.Height, b.Hash, b.RewardBT, b.FoundAt.Unix(), b.Source, b.ShareDiff,
)
return err
}
@@ -74,7 +109,7 @@ func (s *BlockStore) Recent(limit int) ([]Block, error) {
limit = 256
}
rows, err := s.db.Query(
`SELECT height, hash, reward_btc, found_at, source
`SELECT height, hash, reward_btc, found_at, source, share_diff
FROM blocks ORDER BY height DESC LIMIT ?`,
limit,
)
@@ -86,7 +121,7 @@ func (s *BlockStore) Recent(limit int) ([]Block, error) {
for rows.Next() {
var b Block
var unix int64
if err := rows.Scan(&b.Height, &b.Hash, &b.RewardBT, &unix, &b.Source); err != nil {
if err := rows.Scan(&b.Height, &b.Hash, &b.RewardBT, &unix, &b.Source, &b.ShareDiff); err != nil {
return nil, err
}
b.FoundAt = time.Unix(unix, 0).UTC()