2022-04-13 17:38:42 +04:00
import DB from '../database' ;
2022-01-19 18:50:52 +09:00
import logger from '../logger' ;
2022-01-20 22:59:10 +09:00
import config from '../config' ;
2023-01-02 13:25:40 +01:00
import PoolsRepository from '../repositories/PoolsRepository' ;
import { PoolTag } from '../mempool.interfaces' ;
2023-02-26 11:30:12 +09:00
import diskCache from './disk-cache' ;
2023-05-30 10:05:10 -07:00
import mining from './mining/mining' ;
2024-06-24 07:29:02 +00:00
import transactionUtils from './transaction-utils' ;
import BlocksRepository from '../repositories/BlocksRepository' ;
2022-01-19 18:50:52 +09:00
class PoolsParser {
2022-06-07 11:28:39 +02:00
miningPools : any [ ] = [ ] ;
unknownPool : any = {
2023-02-27 18:00:00 +09:00
'id' : 0 ,
2022-11-09 06:43:46 +01:00
'name' : 'Unknown' ,
'link' : 'https://learnmeabitcoin.com/technical/coinbase-transaction' ,
'regexes' : '[]' ,
'addresses' : '[]' ,
2022-06-07 11:28:39 +02:00
'slug' : 'unknown'
} ;
2023-02-24 21:35:13 +09:00
private uniqueLogs : string [ ] = [ ] ;
private uniqueLog ( loggerFunction : any , msg : string ) : void {
if ( this . uniqueLogs . includes ( msg ) ) {
return ;
}
this . uniqueLogs . push ( msg ) ;
loggerFunction ( msg ) ;
}
2023-01-02 13:25:40 +01:00
public setMiningPools ( pools ) : void {
2023-02-24 21:35:13 +09:00
for ( const pool of pools ) {
pool . regexes = pool . tags ;
2023-02-27 18:00:00 +09:00
pool . slug = pool . name . replace ( /[^a-z0-9]/gi , '' ) . toLowerCase ( ) ;
2023-02-24 21:35:13 +09:00
delete ( pool . tags ) ;
2023-02-16 09:09:14 +09:00
}
2023-02-24 21:35:13 +09:00
this . miningPools = pools ;
2023-01-02 13:25:40 +01:00
}
2022-03-25 12:31:09 +09:00
2022-01-19 18:50:52 +09:00
/ * *
2023-01-02 13:25:40 +01:00
* Populate our db with updated mining pool definition
2024-06-24 07:29:02 +00:00
* @param pools
2022-01-19 18:50:52 +09:00
* /
2023-02-24 21:35:13 +09:00
public async migratePoolsJson ( ) : Promise < void > {
2023-03-03 16:58:40 +09:00
// We also need to wipe the backend cache to make sure we don't serve blocks with
// the wrong mining pool (usually happen with unknown blocks)
2023-05-25 19:05:29 +04:00
diskCache . setIgnoreBlocksCache ( ) ;
2023-03-03 16:58:40 +09:00
2023-01-02 13:25:40 +01:00
await this . $insertUnknownPool ( ) ;
2022-01-19 18:50:52 +09:00
2024-06-24 07:29:02 +00:00
let reindexUnknown = false ;
2023-02-24 21:35:13 +09:00
for ( const pool of this . miningPools ) {
2023-01-02 13:25:40 +01:00
if ( ! pool . id ) {
logger . info ( ` Mining pool ${ pool . name } has no unique 'id' defined. Skipping. ` ) ;
continue ;
2022-01-19 18:50:52 +09:00
}
2022-01-20 22:59:10 +09:00
2024-05-23 11:30:30 +02:00
// One of the two fields 'addresses' or 'regexes' must be a non-empty array
if ( ! pool . addresses && ! pool . regexes ) {
logger . err ( ` Mining pool ${ pool . name } must have at least one of the fields 'addresses' or 'regexes'. Skipping. ` ) ;
continue ;
}
2024-06-24 07:29:02 +00:00
2024-05-23 11:30:30 +02:00
pool . addresses = pool . addresses || [ ] ;
pool . regexes = pool . regexes || [ ] ;
2024-06-24 07:29:02 +00:00
2024-05-23 11:30:30 +02:00
if ( pool . addresses . length === 0 && pool . regexes . length === 0 ) {
logger . err ( ` Mining pool ${ pool . name } has no 'addresses' nor 'regexes' defined. Skipping. ` ) ;
continue ;
}
2024-06-24 07:29:02 +00:00
2024-05-23 11:30:30 +02:00
if ( pool . addresses . length === 0 ) {
logger . warn ( ` Mining pool ${ pool . name } has no 'addresses' defined. ` ) ;
}
2024-06-24 07:29:02 +00:00
2024-05-23 11:30:30 +02:00
if ( pool . regexes . length === 0 ) {
logger . warn ( ` Mining pool ${ pool . name } has no 'regexes' defined. ` ) ;
2024-06-24 07:29:02 +00:00
}
2024-05-23 11:30:30 +02:00
2023-01-02 13:25:40 +01:00
const poolDB = await PoolsRepository . $getPoolByUniqueId ( pool . id , false ) ;
if ( ! poolDB ) {
// New mining pool
const slug = pool . name . replace ( /[^a-z0-9]/gi , '' ) . toLowerCase ( ) ;
logger . debug ( ` Inserting new mining pool ${ pool . name } ` ) ;
await PoolsRepository . $insertNewMiningPool ( pool , slug ) ;
2024-06-24 07:29:02 +00:00
reindexUnknown = true ;
2022-06-07 11:28:39 +02:00
} else {
2023-01-02 13:25:40 +01:00
if ( poolDB . name !== pool . name ) {
// Pool has been renamed
const newSlug = pool . name . replace ( /[^a-z0-9]/gi , '' ) . toLowerCase ( ) ;
logger . warn ( ` Renaming ${ poolDB . name } mining pool to ${ pool . name } . Slug has been updated. Maybe you want to make a redirection from 'https://mempool.space/mining/pool/ ${ poolDB . slug } ' to 'https://mempool.space/mining/pool/ ${ newSlug } ` ) ;
await PoolsRepository . $renameMiningPool ( poolDB . id , newSlug , pool . name ) ;
2022-03-25 12:31:09 +09:00
}
2023-01-02 13:25:40 +01:00
if ( poolDB . link !== pool . link ) {
// Pool link has changed
logger . debug ( ` Updating link for ${ pool . name } mining pool ` ) ;
await PoolsRepository . $updateMiningPoolLink ( poolDB . id , pool . link ) ;
2022-07-07 13:41:09 +02:00
}
2023-01-02 13:25:40 +01:00
if ( JSON . stringify ( pool . addresses ) !== poolDB . addresses ||
2023-02-24 21:35:13 +09:00
JSON . stringify ( pool . regexes ) !== poolDB . regexes ) {
2023-01-02 13:25:40 +01:00
// Pool addresses changed or coinbase tags changed
2023-05-30 10:05:10 -07:00
logger . notice ( ` Updating addresses and/or coinbase tags for ${ pool . name } mining pool. ` ) ;
2023-02-24 21:35:13 +09:00
await PoolsRepository . $updateMiningPoolTags ( poolDB . id , pool . addresses , pool . regexes ) ;
2024-06-24 07:29:02 +00:00
reindexUnknown = true ;
await this . $reindexBlocksForPool ( poolDB . id ) ;
}
}
}
if ( reindexUnknown ) {
logger . notice ( ` Updating addresses and/or coinbase tags for unknown mining pool. ` ) ;
let unknownPool ;
if ( config . DATABASE . ENABLED === true ) {
unknownPool = await PoolsRepository . $getUnknownPool ( ) ;
} else {
unknownPool = this . unknownPool ;
}
await this . $reindexBlocksForPool ( unknownPool . id ) ;
}
}
public matchBlockMiner ( scriptsig : string , addresses : string [ ] , pools : PoolTag [ ] ) : PoolTag | undefined {
const asciiScriptSig = transactionUtils . hex2ascii ( scriptsig ) ;
for ( let i = 0 ; i < pools . length ; ++ i ) {
if ( addresses . length ) {
const poolAddresses : string [ ] = typeof pools [ i ] . addresses === 'string' ?
JSON . parse ( pools [ i ] . addresses ) : pools [ i ] . addresses ;
for ( let y = 0 ; y < poolAddresses . length ; y ++ ) {
if ( addresses . indexOf ( poolAddresses [ y ] ) !== - 1 ) {
return pools [ i ] ;
}
}
}
const regexes : string [ ] = typeof pools [ i ] . regexes === 'string' ?
JSON . parse ( pools [ i ] . regexes ) : pools [ i ] . regexes ;
for ( let y = 0 ; y < regexes . length ; ++ y ) {
const regex = new RegExp ( regexes [ y ] , 'i' ) ;
const match = asciiScriptSig . match ( regex ) ;
if ( match !== null ) {
return pools [ i ] ;
2022-11-09 06:43:46 +01:00
}
2022-01-20 13:53:08 +09:00
}
2022-06-07 11:28:39 +02:00
}
2022-01-19 18:50:52 +09:00
}
2022-01-20 22:59:10 +09:00
/ * *
* Manually add the 'unknown pool'
* /
2023-01-02 13:25:40 +01:00
public async $insertUnknownPool ( ) : Promise < void > {
if ( ! config . DATABASE . ENABLED ) {
return ;
}
2022-01-20 22:59:10 +09:00
try {
2022-04-12 15:15:57 +09:00
const [ rows ] : any [ ] = await DB . query ( { sql : 'SELECT name from pools where name="Unknown"' , timeout : 120000 } ) ;
2022-01-20 22:59:10 +09:00
if ( rows . length === 0 ) {
2022-04-12 15:15:57 +09:00
await DB . query ( {
2023-01-02 13:25:40 +01:00
sql : ` INSERT INTO pools(name, link, regexes, addresses, slug, unique_id)
VALUES ( "${this.unknownPool.name}" , "${this.unknownPool.link}" , "[]" , "[]" , "${this.unknownPool.slug}" , 0 ) ;
2022-01-20 22:59:10 +09:00
` });
2022-03-24 19:44:22 +09:00
} else {
2022-04-12 15:15:57 +09:00
await DB . query ( ` UPDATE pools
2023-01-02 13:25:40 +01:00
SET name = '${this.unknownPool.name}' , link = '${this.unknownPool.link}' ,
2022-03-24 19:44:22 +09:00
regexes = '[]' , addresses = '[]' ,
2023-01-02 13:25:40 +01:00
slug = '${this.unknownPool.slug}' ,
unique_id = 0
WHERE slug = '${this.unknownPool.slug}'
2022-03-29 14:37:17 +09:00
` );
2022-01-20 22:59:10 +09:00
}
} catch ( e ) {
2023-01-02 13:25:40 +01:00
logger . err ( ` Unable to insert or update "Unknown" mining pool. Reason: ${ e instanceof Error ? e.message : e } ` ) ;
2022-01-20 22:59:10 +09:00
}
}
2022-07-07 13:41:09 +02:00
/ * *
2024-06-24 07:29:02 +00:00
* re - index pool assignment for blocks previously associated with pool
*
* @param pool local id of existing pool to reindex
2022-07-07 13:41:09 +02:00
* /
2024-06-24 07:29:02 +00:00
private async $reindexBlocksForPool ( poolId : number ) : Promise < void > {
2023-05-30 10:25:41 -07:00
let firstKnownBlockPool = 130635 ; // https://mempool.space/block/0000000000000a067d94ff753eec72830f1205ad3a4c216a08a80c832e551a52
if ( config . MEMPOOL . NETWORK === 'testnet' ) {
firstKnownBlockPool = 21106 ; // https://mempool.space/testnet/block/0000000070b701a5b6a1b965f6a38e0472e70b2bb31b973e4638dec400877581
} else if ( config . MEMPOOL . NETWORK === 'signet' ) {
firstKnownBlockPool = 0 ;
}
2024-06-24 07:29:02 +00:00
const [ blocks ] : any [ ] = await DB . query ( `
SELECT height , hash , coinbase_raw , coinbase_addresses
FROM blocks
WHERE pool_id = ?
AND height >= ?
ORDER BY height DESC
` , [poolId, firstKnownBlockPool]);
let pools : PoolTag [ ] = [ ] ;
if ( config . DATABASE . ENABLED === true ) {
pools = await PoolsRepository . $getPools ( ) ;
} else {
pools = this . miningPools ;
}
2023-02-12 22:44:04 +09:00
2024-06-24 07:29:02 +00:00
let changed = 0 ;
for ( const block of blocks ) {
const addresses = JSON . parse ( block . coinbase_addresses ) || [ ] ;
const newPool = this . matchBlockMiner ( block . coinbase_raw , addresses , pools ) ;
if ( newPool && newPool . id !== poolId ) {
changed ++ ;
await BlocksRepository . $savePool ( block . hash , newPool . id ) ;
}
2023-05-30 10:25:41 -07:00
}
2024-06-24 07:29:02 +00:00
logger . info ( ` ${ changed } blocks assigned to a new pool ` , logger . tags . mining ) ;
2023-05-30 10:05:10 -07:00
// Re-index hashrates and difficulty adjustments later
mining . reindexHashrateRequested = true ;
2023-02-12 22:44:04 +09:00
}
2022-01-19 18:50:52 +09:00
}
2022-01-20 16:34:14 +09:00
export default new PoolsParser ( ) ;