feat(stats): add daily Bitcoin merge-mining metrics snapshot#258
feat(stats): add daily Bitcoin merge-mining metrics snapshot#258nicov-iov wants to merge 6 commits into
Conversation
Add a daily cron job that samples the latest Bitcoin blocks, detects the RSKBLOCK: merge-mining tag in each coinbase, and persists a snapshot (bitcoin_hashrate, rootstock_secured_hashrate, merge_mining_percentage, bitcoin_blocks_sampled, merge_mined_blocks) to btc_merge_mining_stats. - RSK tag detection scans the coinbase OP_RETURN outputs and input scriptSig, classifying by tag presence alone. - Bitcoin data comes from the mempool.space REST API (no dedicated node); the client treats it as untrusted: bounded timeout, retries on 429/5xx only, throttling, and block-hash validation before path interpolation. - The walk tolerates per-block fetch failures and aborts only if coverage drops below a configurable floor, so the ratio is never skewed by a failing provider.
Dependency ReviewThe following issues were found:
License Issuespackage.json
OpenSSF Scorecard
Scanned Files
|
There was a problem hiding this comment.
Pull request overview
Adds a daily persisted snapshot of Bitcoin merge-mining security metrics so the read API can expose them via /api/v3/stats, backed by a new btc_merge_mining_stats table and a scheduled indexer job.
Changes:
- Introduces merge-mining tag detection (
RSKBLOCK:) and merge-mining stats computation over a sampled BTC block window. - Adds an Esplora-compatible mempool.space client with retries/timeouts/throttling and wires it into a new daily cron upsert.
- Extends the DB schema (Prisma + SQL DDL) to store daily merge-mining metrics.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| test/rskMergeMiningTag.spec.js | Unit tests for RSK merge-mining tag detection (Esplora + Core shapes). |
| test/getMergeMiningStats.spec.js | Unit tests for sampling/coverage math and fail-fast behavior. |
| test/btcMempoolClient.spec.js | Unit tests for retries, non-retryable failures, and input validation. |
| src/lib/rskMergeMiningTag.js | Implements tag detection across coinbase input/output script hex. |
| src/lib/getMergeMiningStats.js | Computes sampled merge-mining percentage and secured hashrate. |
| src/lib/defaultConfig.js | Adds bitcoin config block for mempool API + sampling parameters. |
| src/lib/cronJobs.js | Adds a daily cron job to compute and upsert the snapshot. |
| src/lib/btcMempoolClient.js | Adds a mempool.space REST client with bounded timeout and retries. |
| prisma/schema.prisma | Adds Prisma model for btc_merge_mining_stats. |
| prisma/rsk-explorer-database.sql | Adds DDL for the new btc_merge_mining_stats table. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Reject non-positive sampleSize / out-of-range minCoverage so the ratio can never divide by zero and persist NaN. Use the correct OP_RETURN push opcode (0x29 = RSKBLOCK: + 32 bytes) in the test fixtures.
global.fetch is unavailable on the Node 16 deploy runtime, so the client would throw at runtime. Switch to axios (already a dependency, promoted to runtime) which provides timeout and HTTP-status errors natively, dropping the global fetch/AbortController usage.
Surface bitcoin.mempoolApiUrl in config-example.json so production deploys explicitly set the right network endpoint (mainnet default shown; testnet is https://mempool.space/testnet/api). Defaults for all keys live in src/lib/defaultConfig.js.
|
Note: Closed and reopened PR to force reflect latest branch commit ede8004 (PR got desynced somehow) |
The btc_merge_mining_stats table was added without bumping the schema version header / changelog. Record it as V1.2.6.
- Correct the BTC client header: it retries on network/timeout and 429/5xx (not other 4xx), and axios is used because a fetch global is absent before Node 18. - Stop the block walk early once remaining blocks can no longer reach the minimum coverage, so a provider outage doesn't drag the cron through the whole window.
| "@rsksmart/rsk-contract-parser": "^2.2.0", | ||
| "@rsksmart/rsk-js-cli": "^1.0.0", | ||
| "@rsksmart/rsk-utils": "^1.1.0", | ||
| "axios": "^1.8.4", |
There was a problem hiding this comment.
instead of moving axios from devDependencies to dependencies, we can complete remove it, and use node fetch instead.
| import Logger from './Logger' | ||
|
|
||
| const DEFAULTS = { | ||
| baseUrl: 'https://mempool.space/api', |
There was a problem hiding this comment.
This service is not reliable, almost every request fails due to time out. Consider using a reliable RPC service like Alchemy.
There was a problem hiding this comment.
Also, the service runs once per day. If it fails due to time out, and after 3 retries, then, for that day, the stats will not be calculated. Consider adding an alternative service, or a fallback in case that for for that day all 3 retries have been exhausted, and there is no calculated hashrate.
There was a problem hiding this comment.
see Bitcoin RCP documentation https://www.alchemy.com/docs/chains/bitcoin/bitcoin-api-endpoints/getblock
There was a problem hiding this comment.
also, using devault JSON-RPC methods will allow us to switch providers easier.
https://developer.bitcoin.org/reference/rpc/getrawtransaction.html
By Getting the raw transaction of the conibase transaction we can find the RSK_TAG
| @@ -0,0 +1,69 @@ | |||
| import axios from 'axios' | |||
There was a problem hiding this comment.
consider using node fetch instead of axios, and remove axios from the project.
What
Adds a daily snapshot of Bitcoin merge-mining security metrics, persisted by the indexer for the read API (
explorer-api) to expose on/api/v3/stats. Companion PR: rsksmart/explorer-api#256 (same branch name).Metrics persisted to the new
btc_merge_mining_statstable (one row per UTC day):bitcoin_hashrate— estimated Bitcoin network hashratemerge_mining_percentage— share of sampled BTC blocks carrying the RSK tagrootstock_secured_hashrate—bitcoin_hashrate × merge_mining_percentagebitcoin_blocks_sampled,merge_mined_blocks— sampling transparencyHow
src/lib/rskMergeMiningTag.js): scans each coinbase's OP_RETURN outputs and input scriptSig for theRSKBLOCK:tag (RSKIP-110). Classifies by tag presence alone, never by the presence of RSK transactions. Handles both Esplora and Bitcoin Core field shapes.src/lib/btcMempoolClient.js): mempool.space REST API — no dedicated Bitcoin node required. Treated as untrusted input: bounded timeout, retries on 429/5xx only, throttle between calls, and block-hash validation before any path interpolation. Base URL is configurable (src/lib/defaultConfig.js→bitcoin), so testnet or a self-hosted Esplora mirror is a one-line override.src/lib/getMergeMiningStats.js): fetches the network hashrate first (fail fast), walks the latestblocksSample(1000 ≈ 7 days) blocks, tolerates per-block fetch failures, and aborts only if coverage drops belowminCoverage(default 0.9) so a failing provider can't skew the ratio.src/lib/cronJobs.js): a new daily cron (03:20 UTC) computes and upserts the snapshot keyed by date (idempotent). On any failure it logs and leaves the previous snapshot in place.prisma/rsk-explorer-database.sql(DDL source of truth) and mirrored inprisma/schema.prisma.Production database
This repo owns DDL for the shared database. Run once against existing production databases before deploying (the read API depends on this table):
Testing
npx mocha test/rskMergeMiningTag.spec.js test/getMergeMiningStats.spec.js test/btcMempoolClient.spec.js→ 15 passing.npm run lintclean.Follow-up
Configuration (prod)
Set the Bitcoin endpoint per deployment in
config.json(seeconfig-example.json, now includes abitcoinblock). The key that must match the network isbitcoin.mempoolApiUrl:https://mempool.space/api(default)https://mempool.space/testnet/apiAll
bitcoin.*defaults live insrc/lib/defaultConfig.js(sample size, hashrate period,minCoverage, timeouts/retries) — override inconfig.jsononly as needed.Minor doc debt (not addressed here): the config block in
README.mdpredates this change and doesn't list thebitcoinsection, andconfig-example.json'sdbblock is stale from the old stack (server/port27017). Both are pre-existing and out of scope for this PR.