2022-05-20 12:55:28 +09:00
import axios , { AxiosResponse } from 'axios' ;
2022-04-12 15:15:57 +09:00
import poolsParser from '../api/pools-parser' ;
import config from '../config' ;
2022-04-13 17:38:42 +04:00
import DB from '../database' ;
2022-05-20 23:38:16 +09:00
import backendInfo from '../api/backend-info' ;
2022-04-12 15:15:57 +09:00
import logger from '../logger' ;
2022-05-15 20:53:04 +09:00
import { SocksProxyAgent } from 'socks-proxy-agent' ;
import * as https from 'https' ;
2024-08-20 12:07:20 +02:00
import { Common } from '../api/common' ;
2022-04-07 14:37:16 +09:00
/ * *
2023-02-12 22:15:24 +09:00
* Maintain the most recent version of pools - v2 . json
2022-04-07 14:37:16 +09:00
* /
class PoolsUpdater {
2024-08-20 12:07:20 +02:00
tag = 'PoolsUpdater' ;
2022-04-07 14:37:16 +09:00
lastRun : number = 0 ;
2023-03-03 17:49:48 +09:00
currentSha : string | null = null ;
2022-08-09 15:52:24 +02:00
poolsUrl : string = config . MEMPOOL . POOLS_JSON_URL ;
treeUrl : string = config . MEMPOOL . POOLS_JSON_TREE_URL ;
2022-04-07 14:37:16 +09:00
2024-08-20 12:07:20 +02:00
public async $startService ( ) : Promise < void > {
while ( 'Bitcoin is still alive' ) {
try {
await this . updatePoolsJson ( ) ;
} catch ( e : any ) {
logger . info ( ` Exception ${ e } in PoolsUpdater:: $ startService. Code: ${ e . code } . Message: ${ e . message } ` , this . tag ) ;
}
await Common . sleep $ ( 10000 ) ;
}
}
2022-08-09 15:52:24 +02:00
public async updatePoolsJson ( ) : Promise < void > {
2023-03-13 17:24:23 +09:00
if ( [ 'mainnet' , 'testnet' , 'signet' ] . includes ( config . MEMPOOL . NETWORK ) === false ||
config . MEMPOOL . ENABLED === false
) {
2022-04-07 14:37:16 +09:00
return ;
}
const now = new Date ( ) . getTime ( ) / 1000 ;
2024-08-20 11:53:48 +02:00
if ( now - this . lastRun < config . MEMPOOL . POOLS_UPDATE_DELAY ) { // Execute the PoolsUpdate only once a week, or upon restart
2022-04-07 14:37:16 +09:00
return ;
}
this . lastRun = now ;
try {
2023-02-12 22:15:24 +09:00
const githubSha = await this . fetchPoolsSha ( ) ; // Fetch pools-v2.json sha from github
2023-03-03 17:49:48 +09:00
if ( githubSha === null ) {
2022-04-07 14:37:16 +09:00
return ;
}
2022-06-07 11:28:39 +02:00
if ( config . DATABASE . ENABLED === true ) {
2022-06-23 15:42:42 +02:00
this . currentSha = await this . getShaFromDb ( ) ;
2022-06-07 11:28:39 +02:00
}
2024-08-20 12:07:20 +02:00
logger . debug ( ` pools-v2.json sha | Current: ${ this . currentSha } | Github: ${ githubSha } ` , this . tag ) ;
2023-03-03 17:49:48 +09:00
if ( this . currentSha !== null && this . currentSha === githubSha ) {
2022-04-07 14:37:16 +09:00
return ;
}
2023-02-26 14:19:10 +09:00
// See backend README for more details about the mining pools update process
2023-03-03 17:49:48 +09:00
if ( this . currentSha !== null && // If we don't have any mining pool, download it at least once
2024-07-05 05:58:14 +00:00
config . MEMPOOL . AUTOMATIC_POOLS_UPDATE !== true && // Automatic pools update is disabled
2023-02-26 14:19:10 +09:00
! process . env . npm_config_update_pools // We're not manually updating mining pool
) {
2024-08-20 12:07:20 +02:00
logger . warn ( ` Updated mining pools data is available ( ${ githubSha } ) but AUTOMATIC_POOLS_UPDATE is disabled ` , this . tag ) ;
logger . info ( ` You can update your mining pools using the --update-pools command flag. You may want to clear your nginx cache as well if applicable ` , this . tag ) ;
2023-02-26 14:19:10 +09:00
return ;
}
const network = config . SOCKS5PROXY . ENABLED ? 'tor' : 'clearnet' ;
2023-03-03 17:49:48 +09:00
if ( this . currentSha === null ) {
2024-08-20 12:07:20 +02:00
logger . info ( ` Downloading pools-v2.json for the first time from ${ this . poolsUrl } over ${ network } ` , this . tag ) ;
2022-06-13 10:12:27 +02:00
} else {
2024-08-20 12:07:20 +02:00
logger . warn ( ` pools-v2.json is outdated, fetching latest from ${ this . poolsUrl } over ${ network } ` , this . tag ) ;
2022-06-13 10:12:27 +02:00
}
const poolsJson = await this . query ( this . poolsUrl ) ;
2022-05-15 20:53:04 +09:00
if ( poolsJson === undefined ) {
return ;
}
2023-01-02 13:25:40 +01:00
poolsParser . setMiningPools ( poolsJson ) ;
if ( config . DATABASE . ENABLED === false ) { // Don't run db operations
2024-08-20 12:07:20 +02:00
logger . info ( ` Mining pools-v2.json ( ${ githubSha } ) import completed (no database) ` , this . tag ) ;
2023-01-02 13:25:40 +01:00
return ;
}
try {
await DB . query ( 'START TRANSACTION;' ) ;
2023-02-24 21:35:13 +09:00
await poolsParser . migratePoolsJson ( ) ;
2023-01-02 13:25:40 +01:00
await this . updateDBSha ( githubSha ) ;
await DB . query ( 'COMMIT;' ) ;
} catch ( e ) {
2024-08-20 12:07:20 +02:00
logger . err ( ` Could not migrate mining pools, rolling back. Exception: ${ JSON . stringify ( e ) } ` , this . tag ) ;
2023-01-02 13:25:40 +01:00
await DB . query ( 'ROLLBACK;' ) ;
}
2024-08-20 12:07:20 +02:00
logger . info ( ` Mining pools-v2.json ( ${ githubSha } ) import completed ` , this . tag ) ;
2022-04-07 14:37:16 +09:00
} catch ( e ) {
2024-08-20 11:53:48 +02:00
this . lastRun = now - 600 ; // Try again in 10 minutes
2024-08-20 12:07:20 +02:00
logger . err ( ` PoolsUpdater failed. Will try again in 10 minutes. Exception: ${ JSON . stringify ( e ) } ` , this . tag ) ;
2022-04-07 14:37:16 +09:00
}
}
/ * *
2023-02-12 22:15:24 +09:00
* Fetch our latest pools - v2 . json sha from the db
2022-04-07 14:37:16 +09:00
* /
2022-08-09 15:52:24 +02:00
private async updateDBSha ( githubSha : string ) : Promise < void > {
2022-06-07 11:28:39 +02:00
this . currentSha = githubSha ;
if ( config . DATABASE . ENABLED === true ) {
try {
await DB . query ( 'DELETE FROM state where name="pools_json_sha"' ) ;
await DB . query ( ` INSERT INTO state VALUES('pools_json_sha', NULL, ' ${ githubSha } ') ` ) ;
} catch ( e ) {
2024-08-20 12:07:20 +02:00
logger . err ( 'Cannot save github pools-v2.json sha into the db. Reason: ' + ( e instanceof Error ? e.message : e ) , this . tag ) ;
2022-06-07 11:28:39 +02:00
}
2022-04-07 14:37:16 +09:00
}
}
/ * *
2023-02-12 22:15:24 +09:00
* Fetch our latest pools - v2 . json sha from the db
2022-04-07 14:37:16 +09:00
* /
2023-03-03 17:49:48 +09:00
private async getShaFromDb ( ) : Promise < string | null > {
2022-04-07 14:37:16 +09:00
try {
2022-04-12 15:15:57 +09:00
const [ rows ] : any [ ] = await DB . query ( 'SELECT string FROM state WHERE name="pools_json_sha"' ) ;
2023-03-03 17:49:48 +09:00
return ( rows . length > 0 ? rows [ 0 ] . string : null ) ;
2022-04-07 14:37:16 +09:00
} catch ( e ) {
2024-08-20 12:07:20 +02:00
logger . err ( 'Cannot fetch pools-v2.json sha from db. Reason: ' + ( e instanceof Error ? e.message : e ) , this . tag ) ;
2023-03-03 17:49:48 +09:00
return null ;
2022-04-07 14:37:16 +09:00
}
}
/ * *
2023-02-12 22:15:24 +09:00
* Fetch our latest pools - v2 . json sha from github
2022-04-07 14:37:16 +09:00
* /
2023-03-03 17:49:48 +09:00
private async fetchPoolsSha ( ) : Promise < string | null > {
2022-06-13 10:12:27 +02:00
const response = await this . query ( this . treeUrl ) ;
2022-04-07 14:37:16 +09:00
2022-05-15 20:53:04 +09:00
if ( response !== undefined ) {
2023-02-12 22:15:24 +09:00
for ( const file of response [ 'tree' ] ) {
if ( file [ 'path' ] === 'pools-v2.json' ) {
2022-05-15 20:53:04 +09:00
return file [ 'sha' ] ;
}
2022-04-07 14:37:16 +09:00
}
}
2024-08-20 12:07:20 +02:00
logger . err ( ` Cannot find "pools-v2.json" in git tree ( ${ this . treeUrl } ) ` , this . tag ) ;
2023-03-03 17:49:48 +09:00
return null ;
2022-04-07 14:37:16 +09:00
}
/ * *
* Http request wrapper
* /
2023-01-02 13:25:40 +01:00
private async query ( path ) : Promise < any [ ] | undefined > {
2022-05-15 20:53:04 +09:00
type axiosOptions = {
2022-05-20 23:38:16 +09:00
headers : {
'User-Agent' : string
} ;
timeout : number ;
2022-05-15 20:53:04 +09:00
httpsAgent? : https.Agent ;
2022-06-23 15:42:42 +02:00
} ;
2022-05-15 20:53:04 +09:00
const setDelay = ( secs : number = 1 ) : Promise < void > = > new Promise ( resolve = > setTimeout ( ( ) = > resolve ( ) , secs * 1000 ) ) ;
2022-05-20 23:38:16 +09:00
const axiosOptions : axiosOptions = {
headers : {
'User-Agent' : ( config . MEMPOOL . USER_AGENT === 'mempool' ) ? ` mempool/v ${ backendInfo . getBackendInfo ( ) . version } ` : ` ${ config . MEMPOOL . USER_AGENT } `
} ,
timeout : config.SOCKS5PROXY.ENABLED ? 30000 : 10000
} ;
2022-05-15 20:53:04 +09:00
let retry = 0 ;
2022-06-23 15:42:42 +02:00
while ( retry < config . MEMPOOL . EXTERNAL_MAX_RETRY ) {
2022-05-15 20:53:04 +09:00
try {
2022-05-23 20:05:32 +09:00
if ( config . SOCKS5PROXY . ENABLED ) {
const socksOptions : any = {
agentOptions : {
keepAlive : true ,
} ,
hostname : config.SOCKS5PROXY.HOST ,
port : config.SOCKS5PROXY.PORT
} ;
if ( config . SOCKS5PROXY . USERNAME && config . SOCKS5PROXY . PASSWORD ) {
socksOptions . username = config . SOCKS5PROXY . USERNAME ;
socksOptions . password = config . SOCKS5PROXY . PASSWORD ;
2022-05-24 15:46:15 +09:00
} else {
// Retry with different tor circuits https://stackoverflow.com/a/64960234
socksOptions . username = ` circuit ${ retry } ` ;
2022-05-23 20:05:32 +09:00
}
axiosOptions . httpsAgent = new SocksProxyAgent ( socksOptions ) ;
}
2022-06-23 15:42:42 +02:00
2022-05-20 12:55:28 +09:00
const data : AxiosResponse = await axios . get ( path , axiosOptions ) ;
if ( data . statusText === 'error' || ! data . data ) {
2022-06-13 10:12:27 +02:00
throw new Error ( ` Could not fetch data from ${ path } , Error: ${ data . status } ` ) ;
2022-05-15 20:53:04 +09:00
}
return data . data ;
} catch ( e ) {
2024-08-20 12:07:20 +02:00
logger . err ( 'Could not connect to Github. Reason: ' + ( e instanceof Error ? e.message : e ) , this . tag ) ;
2022-05-15 20:53:04 +09:00
retry ++ ;
}
2022-05-20 12:55:28 +09:00
await setDelay ( config . MEMPOOL . EXTERNAL_RETRY_INTERVAL ) ;
2022-05-15 20:53:04 +09:00
}
return undefined ;
2022-04-07 14:37:16 +09:00
}
}
export default new PoolsUpdater ( ) ;