Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
7007f85
reworked health audit
Retropex Feb 5, 2025
41c7bd4
remove trade mark
Retropex Feb 5, 2025
7ed7704
rename data to spam
Retropex Feb 5, 2025
f596ff7
remove shitcoin explorer
Retropex Feb 5, 2025
d27f936
update docker
Retropex Apr 19, 2025
11f183c
remove doc
Retropex Apr 20, 2025
698a3b9
remove privacy and tos
Retropex Jul 15, 2025
98f0790
add annex to spam button
Retropex Jul 16, 2025
0d33666
more ocean style
Retropex Jul 22, 2025
5cc8dc2
add mempool guide logo
Retropex Jul 22, 2025
574ac9f
always enable tools
Retropex Jul 28, 2025
348fe2b
new ocean and knots and stats
Retropex Aug 20, 2025
46c480e
add tor nodes
Retropex Aug 21, 2025
07ef3ee
add pourcentage
Retropex Aug 21, 2025
38cb60a
show ocean and knots stats only on mainnet
Retropex Aug 21, 2025
2e80411
remove french translation
Retropex Aug 27, 2025
c08ed6a
remove inscription button
Retropex Aug 27, 2025
bd71885
add Knots progression bar
Retropex Aug 27, 2025
9275675
remove duplicate stat
Retropex Aug 27, 2025
2f745c0
add precious block
Retropex Sep 7, 2025
6764a82
switch meta to mempool.guide
Retropex Oct 3, 2025
d86334d
new preview layout
Retropex Oct 8, 2025
81928d3
Fix dashboard paddings (#7)
siulca Nov 5, 2025
1db6f8c
update to the next bitnodes API
Retropex Nov 6, 2025
4fd9f40
fetch asset from mempool guide fork
Retropex Nov 6, 2025
b1dbacc
use proper mempool guide preview
Retropex Dec 3, 2025
ca62890
still show the pool name of antpool proxy
Retropex Dec 12, 2025
3a4f32e
return to official pool
Retropex Dec 12, 2025
2baca03
add toggle for antpool & friends
Retropex Dec 17, 2025
0dba009
add precious block 2
Retropex Dec 22, 2025
9e38776
Add bip110 progress bar
Retropex Jan 27, 2026
8899ca2
made vertical padding between panels consistent
siulca Feb 13, 2026
1397b34
knots nodes chart tooltip background matches other chart and makes te…
siulca Feb 13, 2026
fbf5a0c
removed uncessary chart tooltip colour and aligned text left for cons…
siulca Feb 13, 2026
424c54c
changed local dev proxy target url to point to .guide
siulca Feb 13, 2026
f9d51cc
reverted imports auto organize
siulca Feb 13, 2026
bf9109d
fixed knots chart not animating on hover
siulca Feb 13, 2026
f5d3898
made charts label lines length consistent
siulca Feb 13, 2026
845d90d
BIP110
Retropex Feb 27, 2026
3571bbf
fix how block are fetched for bip signaling
Retropex Mar 2, 2026
f4cc916
fix standalone declaration
Retropex Mar 3, 2026
63fe085
Revert "Update default theme to wiz and rename classic to softsimon"
Retropex Mar 3, 2026
8738667
fix theme js generation
Retropex Mar 3, 2026
0baaf36
OCEAN x other pools
Retropex Nov 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions backend/src/api/audit.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { couldStartTrivia } from 'typescript';
import config from '../config';
import logger from '../logger';
import { MempoolTransactionExtended, MempoolBlockWithTransactions } from '../mempool.interfaces';
Expand Down Expand Up @@ -28,6 +29,10 @@ class Audit {
let matchedWeight = 0;
let projectedWeight = 0;

let countCb = 0;
let spamWeight = 0;
let blkWeight = 0;

const inBlock = {};
const inTemplate = {};

Expand Down Expand Up @@ -72,6 +77,22 @@ class Audit {
matchedWeight += transactions[0].weight;
}


for (const tx of transactions){
blkWeight += tx.weight;
}

for (const tx of transactions){
if (countCb !== 0){
if(tx.spam !== undefined){
if (tx.spam == true){
spamWeight += tx.weight;
}
}
}
countCb += 1;
}

// we can expect an honest miner to include 'displaced' transactions in place of recent arrivals and censored txs
// these displaced transactions should occupy the first N weight units of the next projected block
let displacedWeightRemaining = displacedWeight + 4000;
Expand Down Expand Up @@ -169,11 +190,8 @@ class Audit {
const numCensored = Object.keys(isCensored).length;
const numMatches = matches.length - 1; // adjust for coinbase tx
let score = 0;
if (numMatches <= 0 && numCensored <= 0) {
score = 1;
} else if (numMatches > 0) {
score = (numMatches / (numMatches + numCensored));
}

score = (Math.abs((spamWeight/blkWeight)-1));
const similarity = projectedWeight ? matchedWeight / projectedWeight : 1;

return {
Expand Down
245 changes: 245 additions & 0 deletions backend/src/api/bip110-deployment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
import logger from '../logger';
import blocks from './blocks';
import bitcoinApiFactory from './bitcoin/bitcoin-api-factory';
import { Common } from './common';

/**
* BIP-110 'reduced_data' deployment state tracker.
*
* Computes the deployment phase from the current chain tip and signaling data,
* using the constants defined in bip-0110.mediawiki:
*
* bit: 4
* starttime: 1764547200 (~December 1, 2025)
* threshold: 1109/2016 (55%)
* max_activation_height: 965664
* active_duration: 52416 blocks (~1 year)
* mandatory signaling: blocks 961632–963647
* mandatory lock-in: height 963648
*
* State machine: DEFINED → STARTED → LOCKED_IN → ACTIVE
*/

// ── BIP-110 deployment constants ──────────────────────────────────────────────
const RETARGET_PERIOD = 2016;
const THRESHOLD = 1109; // 55% of 2016
const STARTTIME = 1764547200; // MTP threshold for DEFINED→STARTED
const MANDATORY_SIGNALING_START = 961632; // first block of mandatory signaling period
const MANDATORY_LOCK_IN_HEIGHT = 963648; // forced LOCKED_IN if threshold not reached earlier
const MAX_ACTIVATION_HEIGHT = 965664; // ACTIVE starts here if locked in at mandatory
const ACTIVE_DURATION = 52416; // rules enforced for this many blocks after activation

export type Bip110State = 'defined' | 'started' | 'locked_in' | 'active';

export interface Bip110DeploymentInfo {
/** Current deployment state */
state: Bip110State;
/** Current chain tip height */
currentHeight: number;

// ── STARTED phase info ──────────────────────────────────────────────────
/** Signaling blocks in the current retarget period */
periodSignaling: number;
/** Total blocks mined so far in the current retarget period */
periodBlocks: number;
/** Height of the first block of the current retarget period */
periodStartHeight: number;
/** Threshold needed (1109) */
threshold: number;
/** Signaling percentage in current period (0–100) */
signalingPercent: number;
/** Whether threshold has been reached in the current period */
thresholdReached: boolean;

// ── Countdown / milestone info ──────────────────────────────────────────
/** Blocks remaining until mandatory signaling period begins */
blocksUntilMandatory: number;
/** Whether the current block is in the mandatory signaling window */
inMandatorySignaling: boolean;
/** Height at which the soft fork rules will activate (or did activate) */
activationHeight: number | null;
/** Height at which active_duration expires (rules stop being enforced) */
expiryHeight: number | null;
/** Blocks remaining in the active enforcement period (0 if not active or expired) */
blocksUntilExpiry: number;
/** Whether the rules have expired (active_duration elapsed) */
rulesExpired: boolean;
}

class Bip110DeploymentApi {
private cachedInfo: Bip110DeploymentInfo | null = null;
private lastHeight: number = -1;
/** Height at which LOCKED_IN was entered (if we know it) */
private lockedInHeight: number | null = null;

/**
* Get the current deployment info. Recomputes only when chain tip changes.
*/
public async getDeploymentInfo(): Promise<Bip110DeploymentInfo | null> {
const currentHeight = blocks.getCurrentBlockHeight();
if (currentHeight < 0) {
return null;
}
if (currentHeight !== this.lastHeight || !this.cachedInfo) {
this.cachedInfo = await this.computeDeploymentInfo(currentHeight);
this.lastHeight = currentHeight;
}
return this.cachedInfo;
}

/**
* Compute the full deployment state for a given chain tip height.
*/
private async computeDeploymentInfo(currentHeight: number): Promise<Bip110DeploymentInfo> {
const state = this.computeState(currentHeight);

// Current retarget period signaling stats
const periodStartHeight = currentHeight - (currentHeight % RETARGET_PERIOD);
const periodBlocks = (currentHeight % RETARGET_PERIOD) + 1;

// Count signaling blocks in the current retarget period from the in-memory block cache
const periodSignaling = await this.countSignalingInCurrentPeriod(periodStartHeight, currentHeight);
const signalingPercent = periodBlocks > 0 ? (periodSignaling / periodBlocks) * 100 : 0;
const thresholdReached = periodSignaling >= THRESHOLD;

// Milestone computations
const blocksUntilMandatory = Math.max(0, MANDATORY_SIGNALING_START - currentHeight);
const inMandatorySignaling = currentHeight >= MANDATORY_SIGNALING_START && currentHeight < MANDATORY_LOCK_IN_HEIGHT;

// Activation height: computed from when lock-in occurred
let activationHeight: number | null = null;
if (state === 'locked_in' || state === 'active') {
if (this.lockedInHeight != null) {
// Activation is at the start of the next retarget period after lock-in
const lockInPeriodStart = this.lockedInHeight - (this.lockedInHeight % RETARGET_PERIOD);
activationHeight = lockInPeriodStart + RETARGET_PERIOD;
} else {
// Fallback: use MAX_ACTIVATION_HEIGHT (mandatory lock-in case)
activationHeight = MAX_ACTIVATION_HEIGHT;
}
}

const expiryHeight = activationHeight != null ? activationHeight + ACTIVE_DURATION : null;
const blocksUntilExpiry = expiryHeight != null && state === 'active'
? Math.max(0, expiryHeight - currentHeight)
: 0;
const rulesExpired = state === 'active' && expiryHeight != null && currentHeight >= expiryHeight;

return {
state,
currentHeight,
periodSignaling,
periodBlocks,
periodStartHeight,
threshold: THRESHOLD,
signalingPercent,
thresholdReached,
blocksUntilMandatory,
inMandatorySignaling,
activationHeight,
expiryHeight,
blocksUntilExpiry,
rulesExpired,
};
}

/**
* Determine the deployment state based on height and known history.
*
* State transitions (BIP-110):
* DEFINED → block.MTP ≥ starttime → STARTED
* STARTED → threshold reached OR height ≥ 963648 → LOCKED_IN
* LOCKED_IN → next retarget boundary → ACTIVE
*
* Since we can't cheaply compute MTP, we use block timestamps from the cache
* as a reasonable approximation. The DEFINED→STARTED transition only matters
* for blocks near the starttime (~Dec 2025). After that, height-based logic
* dominates.
*/
private computeState(currentHeight: number): Bip110State {
// Check if we've already passed LOCKED_IN or ACTIVE thresholds
if (this.lockedInHeight != null) {
const lockInPeriodStart = this.lockedInHeight - (this.lockedInHeight % RETARGET_PERIOD);
const activationHeight = lockInPeriodStart + RETARGET_PERIOD;
if (currentHeight >= activationHeight) {
return 'active';
}
return 'locked_in';
}

// Height >= mandatory lock-in height means at least LOCKED_IN
if (currentHeight >= MANDATORY_LOCK_IN_HEIGHT) {
// Must be LOCKED_IN or ACTIVE
this.lockedInHeight = MANDATORY_LOCK_IN_HEIGHT;
const lockInPeriodStart = MANDATORY_LOCK_IN_HEIGHT - (MANDATORY_LOCK_IN_HEIGHT % RETARGET_PERIOD);
const activationHeight = lockInPeriodStart + RETARGET_PERIOD;
if (currentHeight >= activationHeight) {
return 'active';
}
return 'locked_in';
}

// Before starttime → DEFINED
// Use the latest block timestamp as a proxy for MTP
const latestBlocks = blocks.getBlocks();
const latestBlock = latestBlocks.length > 0 ? latestBlocks[latestBlocks.length - 1] : null;
if (latestBlock && latestBlock.timestamp < STARTTIME) {
return 'defined';
}

// If we can't determine (no blocks yet), assume DEFINED
if (!latestBlock) {
return 'defined';
}

// We're in STARTED state — check if threshold was reached at the end of
// the most recent completed retarget period
// (In practice, we'd need to scan historical retarget periods, but for
// a live dashboard, we check the current period's signaling progress.)
return 'started';
}

/**
* Called when a new block arrives. If we detect threshold reached at a
* retarget boundary, record the lock-in height.
*/
public async onNewBlock(height: number): Promise<void> {
if (this.lockedInHeight != null) {
return; // Already locked in
}

// Check if this block is the last block of a retarget period
const posInPeriod = height % RETARGET_PERIOD;
if (posInPeriod === RETARGET_PERIOD - 1) {
const periodStart = height - posInPeriod;
const signaling = await this.countSignalingInCurrentPeriod(periodStart, height);
if (signaling >= THRESHOLD) {
this.lockedInHeight = height + 1; // Lock-in happens at the next retarget boundary
logger.info(`BIP-110: Threshold reached at height ${height} (${signaling}/${RETARGET_PERIOD}). LOCKED_IN at ${this.lockedInHeight}.`);
}
}

// Clear cached info so it's recomputed
this.lastHeight = -1;
this.cachedInfo = null;
}

/**
* Count signaling blocks in the current retarget period using the in-memory
* block cache. Falls back to 0 if blocks aren't in memory.
*/
private async countSignalingInCurrentPeriod(periodStart: number, currentHeight: number): Promise<number> {
let count = 0;
let hash, blockdata;
for (let i = currentHeight; i >= periodStart; i--){
hash = await bitcoinApiFactory.$getBlockHash(i);
blockdata = await blocks.$getBlock(hash);
if (Common.isSignalingBIP110(blockdata.version)){
count++;
}
}
return count;
}
}

export default new Bip110DeploymentApi();
Loading