mirror of
https://github.com/mempool/mempool.git
synced 2026-08-13 12:33:11 +02:00
feat: coinbase bip 54 badge in block details
This commit is contained in:
parent
29afce710d
commit
04aad09305
7 changed files with 126 additions and 5 deletions
|
|
@ -51,6 +51,7 @@ class Blocks {
|
|||
private quarterEpochBlockTime: number | null = null;
|
||||
private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = [];
|
||||
private classifyingBlocks: boolean = false;
|
||||
private updatingBlocksMissingCoinbaseBip54: boolean = false;
|
||||
private oldestCoreLogTimestamp: number | undefined | null = undefined;
|
||||
|
||||
private mainLoopTimeout: number = 120000;
|
||||
|
|
@ -319,6 +320,12 @@ class Blocks {
|
|||
extras.coinbaseSignatureAscii = null;
|
||||
}
|
||||
|
||||
const seq = transactions[0].vin[0].sequence;
|
||||
|
||||
if (block.timestamp >= 1771507776) { // First block to include coinbase bip54 on mainnet
|
||||
extras.coinbaseBip54 = (block.height > 0 && transactions[0].locktime === block.height - 1 && typeof seq === 'number' && seq !== 0xffffffff);
|
||||
}
|
||||
|
||||
const header = await bitcoinClient.getBlockHeader(block.id, false);
|
||||
extras.header = header;
|
||||
|
||||
|
|
@ -931,6 +938,60 @@ class Blocks {
|
|||
this.classifyingBlocks = false;
|
||||
}
|
||||
|
||||
/** @asyncSafe */
|
||||
public async $updateBlocksMissingCoinbaseBip54(): Promise<void> {
|
||||
if (this.updatingBlocksMissingCoinbaseBip54) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updatingBlocksMissingCoinbaseBip54 = true;
|
||||
|
||||
if (!Common.indexingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const blocksMissingCoinbaseBip54 = await BlocksRepository.$getBlocksMissingCoinbaseBip54();
|
||||
|
||||
if (!blocksMissingCoinbaseBip54.length) {
|
||||
this.updatingBlocksMissingCoinbaseBip54 = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let timer = Date.now();
|
||||
let updatedThisRun = 0;
|
||||
let updatedInTotal = 0;
|
||||
const numToUpdate = blocksMissingCoinbaseBip54.length;
|
||||
|
||||
logger.debug(`Updating blocks missing coinbase bip54 from #${blocksMissingCoinbaseBip54[0].height} to #${blocksMissingCoinbaseBip54[blocksMissingCoinbaseBip54.length - 1].height}`, logger.tags.mining);
|
||||
for (const block of blocksMissingCoinbaseBip54) {
|
||||
try {
|
||||
const coinbaseTx = await bitcoinApi.$getCoinbaseTx(block.id);
|
||||
const seq = coinbaseTx.vin[0].sequence;
|
||||
const coinbaseBip54 = block.height > 0 && coinbaseTx.locktime === block.height - 1 && typeof seq === 'number' && seq !== 0xffffffff;
|
||||
await BlocksRepository.$updateCoinbaseBip54(coinbaseBip54, block.id);
|
||||
updatedInTotal++;
|
||||
updatedThisRun++;
|
||||
|
||||
} catch (e) {
|
||||
logger.warn(`Failed to update bip54 field in block #${block.height}`, logger.tags.mining);
|
||||
}
|
||||
|
||||
const elapsedSeconds = (Date.now() - timer) / 1000;
|
||||
if (elapsedSeconds > 5) {
|
||||
const perSecond = updatedThisRun / elapsedSeconds;
|
||||
const progress = (updatedInTotal / numToUpdate) * 100;
|
||||
logger.debug(`Updated #${block.height}: ${updatedInTotal} / ${numToUpdate} (${progress.toFixed(2)}%) | ~${perSecond.toFixed(1)} blocks/s`, logger.tags.mining);
|
||||
timer = Date.now();
|
||||
updatedThisRun = 0;
|
||||
}
|
||||
|
||||
await Common.sleep$(250); //Don't DoS the DB
|
||||
}
|
||||
logger.debug(`Update of blocks missing coinbase bip54 completed`, logger.tags.mining);
|
||||
|
||||
this.updatingBlocksMissingCoinbaseBip54 = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* [INDEXING] Index missing coinbase addresses for all blocks
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
|||
import { RowDataPacket } from 'mysql2';
|
||||
|
||||
class DatabaseMigration {
|
||||
private static currentVersion = 111;
|
||||
private static currentVersion = 112;
|
||||
private queryTimeout = 3600_000;
|
||||
private statisticsAddedIndexed = false;
|
||||
private uniqueLogs: string[] = [];
|
||||
|
|
@ -1255,6 +1255,11 @@ class DatabaseMigration {
|
|||
await this.$executeQuery('ALTER TABLE `compact_cpfp_clusters` ADD template_algo TINYINT UNSIGNED NOT NULL DEFAULT 0');
|
||||
await this.updateToSchemaVersion(111);
|
||||
}
|
||||
|
||||
if (databaseSchemaVersion < 112) {
|
||||
await this.$executeQuery('ALTER TABLE `blocks` ADD coinbase_bip_54 TINYINT(1) NULL DEFAULT NULL');
|
||||
await this.updateToSchemaVersion(112);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -228,6 +228,7 @@ class Indexer {
|
|||
await BlocksRepository.$migrateBlocks();
|
||||
// do not wait for classify blocks to finish
|
||||
void blocks.$classifyBlocks();
|
||||
void blocks.$updateBlocksMissingCoinbaseBip54();
|
||||
runSuccessful = true;
|
||||
} catch (e) {
|
||||
nextRunDelay = retryDelay;
|
||||
|
|
|
|||
|
|
@ -314,6 +314,7 @@ export interface BlockExtension {
|
|||
avgFee: number;
|
||||
avgFeeRate: number;
|
||||
coinbaseRaw: string;
|
||||
coinbaseBip54?: boolean;
|
||||
orphans: OrphanedBlock[] | null;
|
||||
coinbaseAddress: string | null;
|
||||
coinbaseAddresses: string[] | null;
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ interface DatabaseBlock {
|
|||
totalInputAmt: number;
|
||||
firstSeen: string; // UNIX_TIMESTAMP() returns a string when applied to datetime(6)
|
||||
stale: boolean;
|
||||
coinbaseBip54: number | null;
|
||||
}
|
||||
|
||||
const BLOCK_DB_FIELDS = `
|
||||
|
|
@ -106,7 +107,8 @@ const BLOCK_DB_FIELDS = `
|
|||
blocks.utxoset_size AS utxoSetSize,
|
||||
blocks.total_input_amt AS totalInputAmt,
|
||||
UNIX_TIMESTAMP(blocks.first_seen) AS firstSeen,
|
||||
blocks.stale
|
||||
blocks.stale,
|
||||
blocks.coinbase_bip_54 AS coinbaseBip54
|
||||
`;
|
||||
|
||||
class BlocksRepository {
|
||||
|
|
@ -132,7 +134,7 @@ class BlocksRepository {
|
|||
total_inputs, total_outputs, total_input_amt, total_output_amt,
|
||||
fee_percentiles, segwit_total_txs, segwit_total_size, segwit_total_weight,
|
||||
median_fee_amt, coinbase_signature_ascii, definition_hash, index_version,
|
||||
stale, first_seen
|
||||
stale, first_seen, coinbase_bip_54
|
||||
) VALUE (
|
||||
?, ?, FROM_UNIXTIME(?), ?,
|
||||
?, ?, ?, ?,
|
||||
|
|
@ -144,7 +146,7 @@ class BlocksRepository {
|
|||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, FROM_UNIXTIME(?)
|
||||
?, FROM_UNIXTIME(?), ?
|
||||
)`;
|
||||
|
||||
const poolDbId = await PoolsRepository.$getPoolByUniqueId(block.extras.pool.id);
|
||||
|
|
@ -194,7 +196,8 @@ class BlocksRepository {
|
|||
poolsUpdater.currentSha,
|
||||
BlocksRepository.version,
|
||||
(block.stale ? 1 : 0),
|
||||
block.extras.firstSeen === null ? 1 : block.extras.firstSeen // Sentinel value 1 indicates that we could not find first seen time
|
||||
block.extras.firstSeen === null ? 1 : block.extras.firstSeen, // Sentinel value 1 indicates that we could not find first seen time
|
||||
block.extras.coinbaseBip54 ?? null
|
||||
];
|
||||
|
||||
await DB.query(query, params);
|
||||
|
|
@ -1294,6 +1297,7 @@ class BlocksRepository {
|
|||
extras.utxoSetSize = dbBlk.utxoSetSize;
|
||||
extras.totalInputAmt = dbBlk.totalInputAmt;
|
||||
extras.virtualSize = dbBlk.weight / 4.0;
|
||||
extras.coinbaseBip54 = dbBlk.coinbaseBip54 === null ? undefined : !!dbBlk.coinbaseBip54;
|
||||
|
||||
extras.firstSeen = null;
|
||||
if (config.CORE_RPC.DEBUG_LOG_PATH) {
|
||||
|
|
@ -1426,6 +1430,44 @@ class BlocksRepository {
|
|||
}
|
||||
return blocksMigrated;
|
||||
}
|
||||
|
||||
/** @asyncSafe */
|
||||
public async $getBlocksMissingCoinbaseBip54(): Promise<BlockExtended[]> {
|
||||
const query = `SELECT ${BLOCK_DB_FIELDS} FROM blocks
|
||||
JOIN pools ON blocks.pool_id = pools.id
|
||||
where blocks.coinbase_bip_54 IS NULL AND
|
||||
blocks.height > 0 AND
|
||||
blocks.blockTimestamp >= FROM_UNIXTIME(1771507776) AND
|
||||
blocks.stale = 0
|
||||
ORDER BY blocks.height DESC`;
|
||||
|
||||
try {
|
||||
const [rows]: any[] = await DB.query(query);
|
||||
|
||||
const blocks: BlockExtended[] = [];
|
||||
for (const dbBlock of rows) {
|
||||
blocks.push(await this.formatDbBlockIntoExtendedBlock(dbBlock as DatabaseBlock));
|
||||
}
|
||||
|
||||
return blocks;
|
||||
} catch (e) {
|
||||
logger.err(`Cannot get blocks with missing bip54 flag. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public async $updateCoinbaseBip54(result: boolean, hash: string): Promise<void> {
|
||||
const query = `UPDATE blocks SET coinbase_bip_54 = ? WHERE hash = ?`;
|
||||
const params = [result, hash];
|
||||
|
||||
try {
|
||||
await DB.query(query, params);
|
||||
} catch (e) {
|
||||
logger.err(`Couldn't update coinbaseBip54 field for block ${hash}. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new BlocksRepository();
|
||||
|
|
|
|||
|
|
@ -212,6 +212,16 @@
|
|||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@if (network !== 'liquid' && network !== 'liquidtestnet' && block.extras.coinbaseBip54) {
|
||||
<tr>
|
||||
<td i18n="transaction.features|Transaction features">Features</td>
|
||||
<td>
|
||||
<span class="badge bg-success" i18n="block.bip54" i18n-ngbTooltip="bip54-tooltip" ngbTooltip="Coinbase is forward-compatible with BIP-54 (nLockTime = height − 1, nSequence ≠ 0xffffffff)." placement="bottom">
|
||||
BIP-54 coinbase
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</ng-container>
|
||||
<ng-template #loadingRest>
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ export interface BlockExtension {
|
|||
feeRange?: number[];
|
||||
reward?: number;
|
||||
coinbaseRaw?: string;
|
||||
coinbaseBip54?: boolean;
|
||||
matchRate?: number;
|
||||
expectedFees?: number;
|
||||
expectedWeight?: number;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue