| 
									
										
										
										
											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'; | 
					
						
							| 
									
										
										
										
											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 { | 
					
						
							|  |  |  |   lastRun: number = 0; | 
					
						
							| 
									
										
										
										
											2022-08-09 15:52:24 +02:00
										 |  |  |   currentSha: string | undefined = undefined; | 
					
						
							|  |  |  |   poolsUrl: string = config.MEMPOOL.POOLS_JSON_URL; | 
					
						
							|  |  |  |   treeUrl: string = config.MEMPOOL.POOLS_JSON_TREE_URL; | 
					
						
							| 
									
										
										
										
											2022-04-07 14:37:16 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-09 15:52:24 +02:00
										 |  |  |   public async updatePoolsJson(): Promise<void> { | 
					
						
							| 
									
										
										
										
											2022-06-07 11:28:39 +02:00
										 |  |  |     if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) { | 
					
						
							| 
									
										
										
										
											2022-04-07 14:37:16 +09:00
										 |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 14:51:23 +09:00
										 |  |  |     const oneWeek = 604800; | 
					
						
							|  |  |  |     const oneDay = 86400; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 14:37:16 +09:00
										 |  |  |     const now = new Date().getTime() / 1000; | 
					
						
							| 
									
										
										
										
											2022-04-07 14:51:23 +09:00
										 |  |  |     if (now - this.lastRun < oneWeek) { // 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
 | 
					
						
							| 
									
										
										
										
											2022-04-07 14:37:16 +09:00
										 |  |  |       if (githubSha === undefined) { | 
					
						
							|  |  |  |         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
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-12 22:15:24 +09:00
										 |  |  |       logger.debug(`pools-v2.json sha | Current: ${this.currentSha} | Github: ${githubSha}`); | 
					
						
							| 
									
										
										
										
											2022-06-07 11:28:39 +02:00
										 |  |  |       if (this.currentSha !== undefined && 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
 | 
					
						
							|  |  |  |       if (this.currentSha !== undefined && // If we don't have any mining pool, download it at least once
 | 
					
						
							|  |  |  |         config.MEMPOOL.AUTOMATIC_BLOCK_REINDEXING !== true && // Automatic pools update is disabled
 | 
					
						
							|  |  |  |         !process.env.npm_config_update_pools // We're not manually updating mining pool
 | 
					
						
							|  |  |  |       ) { | 
					
						
							|  |  |  |         logger.warn(`Updated mining pools data is available (${githubSha}) but AUTOMATIC_BLOCK_REINDEXING is disabled`); | 
					
						
							|  |  |  |         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`); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const network = config.SOCKS5PROXY.ENABLED ? 'tor' : 'clearnet'; | 
					
						
							| 
									
										
										
										
											2022-06-13 10:12:27 +02:00
										 |  |  |       if (this.currentSha === undefined) { | 
					
						
							| 
									
										
										
										
											2023-02-26 14:19:10 +09:00
										 |  |  |         logger.info(`Downloading pools-v2.json for the first time from ${this.poolsUrl} over ${network}`, logger.tags.mining); | 
					
						
							| 
									
										
										
										
											2022-06-13 10:12:27 +02:00
										 |  |  |       } else { | 
					
						
							| 
									
										
										
										
											2023-02-26 14:19:10 +09:00
										 |  |  |         logger.warn(`pools-v2.json is outdated, fetch latest from ${this.poolsUrl} over ${network}`, logger.tags.mining); | 
					
						
							| 
									
										
										
										
											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
 | 
					
						
							| 
									
										
										
										
											2023-02-12 22:15:24 +09:00
										 |  |  |         logger.info('Mining pools-v2.json import completed (no database)'); | 
					
						
							| 
									
										
										
										
											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) { | 
					
						
							| 
									
										
										
										
											2023-02-25 17:12:50 +09:00
										 |  |  |         logger.err(`Could not migrate mining pools, rolling back. Exception: ${JSON.stringify(e)}`, logger.tags.mining); | 
					
						
							| 
									
										
										
										
											2023-01-02 13:25:40 +01:00
										 |  |  |         await DB.query('ROLLBACK;'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       logger.notice('PoolsUpdater completed'); | 
					
						
							| 
									
										
										
										
											2022-04-07 14:37:16 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							| 
									
										
										
										
											2022-04-07 16:14:43 +09:00
										 |  |  |       this.lastRun = now - (oneWeek - oneDay); // Try again in 24h instead of waiting next week
 | 
					
						
							| 
									
										
										
										
											2023-02-25 17:12:50 +09:00
										 |  |  |       logger.err(`PoolsUpdater failed. Will try again in 24h. Exception: ${JSON.stringify(e)}`, logger.tags.mining); | 
					
						
							| 
									
										
										
										
											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) { | 
					
						
							| 
									
										
										
										
											2023-02-12 22:15:24 +09:00
										 |  |  |         logger.err('Cannot save github pools-v2.json sha into the db. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining); | 
					
						
							| 
									
										
										
										
											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
										 |  |  |    */ | 
					
						
							|  |  |  |   private async getShaFromDb(): Promise<string | undefined> { | 
					
						
							|  |  |  |     try { | 
					
						
							| 
									
										
										
										
											2022-04-12 15:15:57 +09:00
										 |  |  |       const [rows]: any[] = await DB.query('SELECT string FROM state WHERE name="pools_json_sha"'); | 
					
						
							| 
									
										
										
										
											2022-04-07 14:37:16 +09:00
										 |  |  |       return (rows.length > 0 ? rows[0].string : undefined); | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							| 
									
										
										
										
											2023-02-12 22:15:24 +09:00
										 |  |  |       logger.err('Cannot fetch pools-v2.json sha from db. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining); | 
					
						
							| 
									
										
										
										
											2022-04-07 14:37:16 +09:00
										 |  |  |       return undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							| 
									
										
										
										
											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
										 |  |  |    */ | 
					
						
							|  |  |  |   private async fetchPoolsSha(): Promise<string | undefined> { | 
					
						
							| 
									
										
										
										
											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
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-12 22:15:24 +09:00
										 |  |  |     logger.err(`Cannot find "pools-v2.json" in git tree (${this.treeUrl})`, logger.tags.mining); | 
					
						
							| 
									
										
										
										
											2022-04-07 14:37:16 +09:00
										 |  |  |     return undefined; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * 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) { | 
					
						
							| 
									
										
										
										
											2022-06-23 15:42:42 +02:00
										 |  |  |         logger.err('Could not connect to Github. Reason: ' + (e instanceof Error ? e.message : e)); | 
					
						
							| 
									
										
										
										
											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(); |