2023-05-30 19:35:39 -04:00
|
|
|
import config from '../../config';
|
2023-12-06 12:09:46 +00:00
|
|
|
import logger from '../../logger';
|
2024-07-23 14:00:23 +00:00
|
|
|
import { BlockExtended } from '../../mempool.interfaces';
|
2023-12-06 12:09:46 +00:00
|
|
|
import axios from 'axios';
|
2023-05-30 19:35:39 -04:00
|
|
|
|
2024-07-23 14:00:23 +00:00
|
|
|
type MyAccelerationStatus = 'requested' | 'accelerating' | 'done';
|
|
|
|
|
2023-05-30 19:35:39 -04:00
|
|
|
export interface Acceleration {
|
|
|
|
txid: string,
|
2024-04-04 09:42:49 +00:00
|
|
|
added: number,
|
|
|
|
effectiveVsize: number,
|
|
|
|
effectiveFee: number,
|
2023-05-30 19:35:39 -04:00
|
|
|
feeDelta: number,
|
2023-06-13 17:03:36 -04:00
|
|
|
pools: number[],
|
2024-02-02 02:25:49 +00:00
|
|
|
positions?: {
|
|
|
|
[pool: number]: {
|
|
|
|
block: number,
|
|
|
|
vbytes: number,
|
|
|
|
},
|
|
|
|
},
|
2024-03-11 21:19:03 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export interface AccelerationHistory {
|
|
|
|
txid: string,
|
|
|
|
status: string,
|
|
|
|
feePaid: number,
|
|
|
|
added: number,
|
|
|
|
lastUpdated: number,
|
|
|
|
baseFee: number,
|
|
|
|
vsizeFee: number,
|
|
|
|
effectiveFee: number,
|
|
|
|
effectiveVsize: number,
|
|
|
|
feeDelta: number,
|
|
|
|
blockHash: string,
|
|
|
|
blockHeight: number,
|
2024-06-20 12:22:54 +09:00
|
|
|
pools: number[];
|
2024-03-11 21:19:03 +00:00
|
|
|
};
|
2023-05-30 19:35:39 -04:00
|
|
|
|
|
|
|
class AccelerationApi {
|
2024-07-23 14:00:23 +00:00
|
|
|
private apiPath = config.MEMPOOL.OFFICIAL ? (config.MEMPOOL_SERVICES.API + '/accelerator/accelerations') : (config.EXTERNAL_DATA_SERVER.MEMPOOL_API + '/accelerations');
|
|
|
|
private _accelerations: Acceleration[] | null = null;
|
|
|
|
private lastPoll = 0;
|
|
|
|
private forcePoll = false;
|
|
|
|
private myAccelerations: Record<string, { status: MyAccelerationStatus, added: number, acceleration?: Acceleration }> = {};
|
|
|
|
|
|
|
|
public get accelerations(): Acceleration[] | null {
|
|
|
|
return this._accelerations;
|
|
|
|
}
|
|
|
|
|
|
|
|
public countMyAccelerationsWithStatus(filter: MyAccelerationStatus): number {
|
|
|
|
return Object.values(this.myAccelerations).reduce((count, {status}) => { return count + (status === filter ? 1 : 0); }, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
public accelerationRequested(txid: string): void {
|
|
|
|
this.myAccelerations[txid] = { status: 'requested', added: Date.now() };
|
|
|
|
}
|
|
|
|
|
|
|
|
public accelerationConfirmed(): void {
|
|
|
|
this.forcePoll = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private async $fetchAccelerations(): Promise<Acceleration[] | null> {
|
|
|
|
try {
|
|
|
|
const response = await axios.get(this.apiPath, { responseType: 'json', timeout: 10000 });
|
|
|
|
return response?.data || [];
|
|
|
|
} catch (e) {
|
|
|
|
logger.warn('Failed to fetch current accelerations from the mempool services backend: ' + (e instanceof Error ? e.message : e));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async $updateAccelerations(): Promise<Acceleration[] | null> {
|
2023-05-30 19:35:39 -04:00
|
|
|
if (config.MEMPOOL_SERVICES.ACCELERATIONS) {
|
2024-07-23 14:00:23 +00:00
|
|
|
const accelerations = await this.$fetchAccelerations();
|
|
|
|
if (accelerations) {
|
|
|
|
this._accelerations = accelerations;
|
|
|
|
return this._accelerations;
|
2023-12-06 12:09:46 +00:00
|
|
|
}
|
2023-05-30 19:35:39 -04:00
|
|
|
} else {
|
2024-07-23 14:00:23 +00:00
|
|
|
return this.$updateAccelerationsOnDemand();
|
2023-05-30 19:35:39 -04:00
|
|
|
}
|
2024-07-23 14:00:23 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private async $updateAccelerationsOnDemand(): Promise<Acceleration[] | null> {
|
|
|
|
const shouldUpdate = this.forcePoll
|
|
|
|
|| this.countMyAccelerationsWithStatus('requested') > 0
|
|
|
|
|| (this.countMyAccelerationsWithStatus('accelerating') > 0 && this.lastPoll < (Date.now() - (10 * 60 * 1000)));
|
|
|
|
|
|
|
|
// update accelerations if necessary
|
|
|
|
if (shouldUpdate) {
|
|
|
|
const accelerations = await this.$fetchAccelerations();
|
|
|
|
this.lastPoll = Date.now();
|
|
|
|
this.forcePoll = false;
|
|
|
|
if (accelerations) {
|
|
|
|
const latestAccelerations: Record<string, Acceleration> = {};
|
|
|
|
// set relevant accelerations to 'accelerating'
|
|
|
|
for (const acc of accelerations) {
|
|
|
|
if (this.myAccelerations[acc.txid]) {
|
|
|
|
latestAccelerations[acc.txid] = acc;
|
|
|
|
this.myAccelerations[acc.txid] = { status: 'accelerating', added: Date.now(), acceleration: acc };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// txs that are no longer accelerating are either confirmed or canceled, so mark for expiry
|
|
|
|
for (const [txid, { status, acceleration }] of Object.entries(this.myAccelerations)) {
|
|
|
|
if (status === 'accelerating' && !latestAccelerations[txid]) {
|
|
|
|
this.myAccelerations[txid] = { status: 'done', added: Date.now(), acceleration };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear expired accelerations (confirmed / failed / not accepted) after 10 minutes
|
|
|
|
for (const [txid, { status, added }] of Object.entries(this.myAccelerations)) {
|
|
|
|
if (['requested', 'done'].includes(status) && added < (Date.now() - (1000 * 60 * 10))) {
|
|
|
|
delete this.myAccelerations[txid];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this._accelerations = Object.values(this.myAccelerations).map(({ acceleration }) => acceleration).filter(acc => acc) as Acceleration[];
|
|
|
|
return this._accelerations;
|
2023-05-30 19:35:39 -04:00
|
|
|
}
|
2023-06-03 16:54:12 -04:00
|
|
|
|
2024-03-11 21:19:03 +00:00
|
|
|
public async $fetchAccelerationHistory(page?: number, status?: string): Promise<AccelerationHistory[] | null> {
|
|
|
|
if (config.MEMPOOL_SERVICES.ACCELERATIONS) {
|
|
|
|
try {
|
|
|
|
const response = await axios.get(`${config.MEMPOOL_SERVICES.API}/accelerator/accelerations/history`, {
|
|
|
|
responseType: 'json',
|
|
|
|
timeout: 10000,
|
|
|
|
params: {
|
|
|
|
page,
|
|
|
|
status,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return response.data as AccelerationHistory[];
|
|
|
|
} catch (e) {
|
|
|
|
logger.warn('Failed to fetch acceleration history from the mempool services backend: ' + (e instanceof Error ? e.message : e));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-13 17:03:36 -04:00
|
|
|
public isAcceleratedBlock(block: BlockExtended, accelerations: Acceleration[]): boolean {
|
|
|
|
let anyAccelerated = false;
|
|
|
|
for (let i = 0; i < accelerations.length && !anyAccelerated; i++) {
|
|
|
|
anyAccelerated = anyAccelerated || accelerations[i].pools?.includes(block.extras.pool.id);
|
2023-06-03 16:54:12 -04:00
|
|
|
}
|
2023-06-13 17:03:36 -04:00
|
|
|
return anyAccelerated;
|
2023-06-03 16:54:12 -04:00
|
|
|
}
|
2023-05-30 19:35:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export default new AccelerationApi();
|