mirror of
https://github.com/mempool/mempool.git
synced 2025-03-26 17:51:45 +01:00
Add coinbase_addresses to extended blocks & table
This commit is contained in:
parent
868dac91c7
commit
f9d03b1bb4
@ -28,6 +28,7 @@ export interface AbstractBitcoinApi {
|
||||
$getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]>;
|
||||
$getBatchedOutspendsInternal(txId: string[]): Promise<IEsploraApi.Outspend[][]>;
|
||||
$getOutSpendsByOutpoint(outpoints: { txid: string, vout: number }[]): Promise<IEsploraApi.Outspend[]>;
|
||||
$getCoinbaseTx(blockhash: string): Promise<IEsploraApi.Transaction>;
|
||||
|
||||
startHealthChecks(): void;
|
||||
getHealthStatus(): HealthCheckHost[];
|
||||
|
@ -238,6 +238,11 @@ class BitcoinApi implements AbstractBitcoinApi {
|
||||
return outspends;
|
||||
}
|
||||
|
||||
async $getCoinbaseTx(blockhash: string): Promise<IEsploraApi.Transaction> {
|
||||
const txids = await this.$getTxIdsForBlock(blockhash);
|
||||
return this.$getRawTransaction(txids[0]);
|
||||
}
|
||||
|
||||
$getEstimatedHashrate(blockHeight: number): Promise<number> {
|
||||
// 120 is the default block span in Core
|
||||
return this.bitcoindClient.getNetworkHashPs(120, blockHeight);
|
||||
|
@ -352,6 +352,11 @@ class ElectrsApi implements AbstractBitcoinApi {
|
||||
return this.failoverRouter.$post<IEsploraApi.Outspend[]>('/internal/txs/outspends/by-outpoint', outpoints.map(out => `${out.txid}:${out.vout}`), 'json');
|
||||
}
|
||||
|
||||
async $getCoinbaseTx(blockhash: string): Promise<IEsploraApi.Transaction> {
|
||||
const txid = await this.failoverRouter.$get<string>(`/block/${blockhash}/txid/0`);
|
||||
return this.failoverRouter.$get<IEsploraApi.Transaction>('/tx/' + txid);
|
||||
}
|
||||
|
||||
public startHealthChecks(): void {
|
||||
this.failoverRouter.startHealthChecks();
|
||||
}
|
||||
|
@ -295,10 +295,12 @@ class Blocks {
|
||||
extras.virtualSize = block.weight / 4.0;
|
||||
if (coinbaseTx?.vout.length > 0) {
|
||||
extras.coinbaseAddress = coinbaseTx.vout[0].scriptpubkey_address ?? null;
|
||||
extras.coinbaseAddresses = [...new Set<string>(...coinbaseTx.vout.map(v => v.scriptpubkey_address).filter(a => a) as string[])];
|
||||
extras.coinbaseSignature = coinbaseTx.vout[0].scriptpubkey_asm ?? null;
|
||||
extras.coinbaseSignatureAscii = transactionUtils.hex2ascii(coinbaseTx.vin[0].scriptsig) ?? null;
|
||||
} else {
|
||||
extras.coinbaseAddress = null;
|
||||
extras.coinbaseAddresses = null;
|
||||
extras.coinbaseSignature = null;
|
||||
extras.coinbaseSignatureAscii = null;
|
||||
}
|
||||
@ -690,6 +692,52 @@ class Blocks {
|
||||
this.classifyingBlocks = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* [INDEXING] Index missing coinbase addresses for all blocks
|
||||
*/
|
||||
public async $indexCoinbaseAddresses(): Promise<void> {
|
||||
try {
|
||||
// Get all indexed block hash
|
||||
const unindexedBlocks = await blocksRepository.$getBlocksWithoutCoinbaseAddresses();
|
||||
|
||||
if (!unindexedBlocks?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`Indexing missing coinbase addresses for ${unindexedBlocks.length} blocks`);
|
||||
|
||||
// Logging
|
||||
let count = 0;
|
||||
let countThisRun = 0;
|
||||
let timer = Date.now() / 1000;
|
||||
const startedAt = Date.now() / 1000;
|
||||
for (const { height, hash } of unindexedBlocks) {
|
||||
// Logging
|
||||
const elapsedSeconds = (Date.now() / 1000) - timer;
|
||||
if (elapsedSeconds > 5) {
|
||||
const runningFor = (Date.now() / 1000) - startedAt;
|
||||
const blockPerSeconds = countThisRun / elapsedSeconds;
|
||||
const progress = Math.round(count / unindexedBlocks.length * 10000) / 100;
|
||||
logger.debug(`Indexing coinbase addresses for #${height} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${count}/${unindexedBlocks.length} (${progress}%) | elapsed: ${runningFor.toFixed(2)} seconds`);
|
||||
timer = Date.now() / 1000;
|
||||
countThisRun = 0;
|
||||
}
|
||||
|
||||
const coinbaseTx = await bitcoinApi.$getCoinbaseTx(hash);
|
||||
const addresses = new Set<string>(coinbaseTx.vout.map(v => v.scriptpubkey_address).filter(a => a));
|
||||
await blocksRepository.$saveCoinbaseAddresses(hash, [...addresses]);
|
||||
|
||||
// Logging
|
||||
count++;
|
||||
countThisRun++;
|
||||
}
|
||||
logger.notice(`coinbase addresses indexing completed: indexed ${count} blocks`);
|
||||
} catch (e) {
|
||||
logger.err(`coinbase addresses indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [INDEXING] Index all blocks metadata for the mining dashboard
|
||||
*/
|
||||
@ -1259,6 +1307,7 @@ class Blocks {
|
||||
utxoset_size: block.extras.utxoSetSize ?? null,
|
||||
coinbase_raw: block.extras.coinbaseRaw ?? null,
|
||||
coinbase_address: block.extras.coinbaseAddress ?? null,
|
||||
coinbase_addresses: block.extras.coinbaseAddresses ?? null,
|
||||
coinbase_signature: block.extras.coinbaseSignature ?? null,
|
||||
coinbase_signature_ascii: block.extras.coinbaseSignatureAscii ?? null,
|
||||
pool_slug: block.extras.pool.slug ?? null,
|
||||
|
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
||||
import { RowDataPacket } from 'mysql2';
|
||||
|
||||
class DatabaseMigration {
|
||||
private static currentVersion = 79;
|
||||
private static currentVersion = 80;
|
||||
private queryTimeout = 3600_000;
|
||||
private statisticsAddedIndexed = false;
|
||||
private uniqueLogs: string[] = [];
|
||||
@ -686,6 +686,11 @@ class DatabaseMigration {
|
||||
`);
|
||||
await this.updateToSchemaVersion(79);
|
||||
}
|
||||
|
||||
if (databaseSchemaVersion < 80) {
|
||||
await this.$executeQuery('ALTER TABLE `blocks` ADD coinbase_addresses JSON DEFAULT NULL');
|
||||
await this.updateToSchemaVersion(80);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -182,6 +182,7 @@ class Indexer {
|
||||
}
|
||||
|
||||
this.runSingleTask('blocksPrices');
|
||||
await blocks.$indexCoinbaseAddresses();
|
||||
await mining.$indexDifficultyAdjustments();
|
||||
await mining.$generateNetworkHashrateHistory();
|
||||
await mining.$generatePoolHashrateHistory();
|
||||
|
@ -287,6 +287,7 @@ export interface BlockExtension {
|
||||
coinbaseRaw: string;
|
||||
orphans: OrphanedBlock[] | null;
|
||||
coinbaseAddress: string | null;
|
||||
coinbaseAddresses: string[] | null;
|
||||
coinbaseSignature: string | null;
|
||||
coinbaseSignatureAscii: string | null;
|
||||
virtualSize: number;
|
||||
|
@ -40,6 +40,7 @@ interface DatabaseBlock {
|
||||
avgFeeRate: number;
|
||||
coinbaseRaw: string;
|
||||
coinbaseAddress: string;
|
||||
coinbaseAddresses: string[];
|
||||
coinbaseSignature: string;
|
||||
coinbaseSignatureAscii: string;
|
||||
avgTxSize: number;
|
||||
@ -82,6 +83,7 @@ const BLOCK_DB_FIELDS = `
|
||||
blocks.avg_fee_rate AS avgFeeRate,
|
||||
blocks.coinbase_raw AS coinbaseRaw,
|
||||
blocks.coinbase_address AS coinbaseAddress,
|
||||
blocks.coinbase_addresses AS coinbaseAddresses,
|
||||
blocks.coinbase_signature AS coinbaseSignature,
|
||||
blocks.coinbase_signature_ascii AS coinbaseSignatureAscii,
|
||||
blocks.avg_tx_size AS avgTxSize,
|
||||
@ -114,7 +116,7 @@ class BlocksRepository {
|
||||
pool_id, fees, fee_span, median_fee,
|
||||
reward, version, bits, nonce,
|
||||
merkle_root, previous_block_hash, avg_fee, avg_fee_rate,
|
||||
median_timestamp, header, coinbase_address,
|
||||
median_timestamp, header, coinbase_address, coinbase_addresses,
|
||||
coinbase_signature, utxoset_size, utxoset_change, avg_tx_size,
|
||||
total_inputs, total_outputs, total_input_amt, total_output_amt,
|
||||
fee_percentiles, segwit_total_txs, segwit_total_size, segwit_total_weight,
|
||||
@ -125,7 +127,7 @@ class BlocksRepository {
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
FROM_UNIXTIME(?), ?, ?,
|
||||
FROM_UNIXTIME(?), ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
@ -161,6 +163,7 @@ class BlocksRepository {
|
||||
block.mediantime,
|
||||
block.extras.header,
|
||||
block.extras.coinbaseAddress,
|
||||
block.extras.coinbaseAddresses,
|
||||
truncatedCoinbaseSignature,
|
||||
block.extras.utxoSetSize,
|
||||
block.extras.utxoSetChange,
|
||||
@ -922,6 +925,25 @@ class BlocksRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all indexed blocks with missing coinbase addresses
|
||||
*/
|
||||
public async $getBlocksWithoutCoinbaseAddresses(): Promise<any> {
|
||||
try {
|
||||
const [blocks] = await DB.query(`
|
||||
SELECT height, hash, coinbase_addresses
|
||||
FROM blocks
|
||||
WHERE coinbase_addresses IS NULL AND
|
||||
coinbase_address IS NOT NULL
|
||||
ORDER BY height DESC
|
||||
`);
|
||||
return blocks;
|
||||
} catch (e) {
|
||||
logger.err(`Cannot get blocks with missing coinbase addresses. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save indexed median fee to avoid recomputing it later
|
||||
*
|
||||
@ -960,6 +982,25 @@ class BlocksRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save coinbase addresses
|
||||
*
|
||||
* @param id
|
||||
* @param addresses
|
||||
*/
|
||||
public async $saveCoinbaseAddresses(id: string, addresses: string[]): Promise<void> {
|
||||
try {
|
||||
await DB.query(`
|
||||
UPDATE blocks SET coinbase_addresses = ?
|
||||
WHERE hash = ?`,
|
||||
[JSON.stringify(addresses), id]
|
||||
);
|
||||
} catch (e) {
|
||||
logger.err(`Cannot update block coinbase addresses. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a mysql row block into a BlockExtended. Note that you
|
||||
* must provide the correct field into dbBlk object param
|
||||
@ -999,6 +1040,7 @@ class BlocksRepository {
|
||||
extras.avgFeeRate = dbBlk.avgFeeRate;
|
||||
extras.coinbaseRaw = dbBlk.coinbaseRaw;
|
||||
extras.coinbaseAddress = dbBlk.coinbaseAddress;
|
||||
extras.coinbaseAddresses = dbBlk.coinbaseAddresses;
|
||||
extras.coinbaseSignature = dbBlk.coinbaseSignature;
|
||||
extras.coinbaseSignatureAscii = dbBlk.coinbaseSignatureAscii;
|
||||
extras.avgTxSize = dbBlk.avgTxSize;
|
||||
|
Loading…
x
Reference in New Issue
Block a user