Use minfee second node to supply alt rbf policy mempool txs
This commit is contained in:
parent
be27e3df52
commit
cdfee53cf5
@ -27,7 +27,8 @@
|
|||||||
"POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master",
|
"POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master",
|
||||||
"ADVANCED_GBT_AUDIT": false,
|
"ADVANCED_GBT_AUDIT": false,
|
||||||
"ADVANCED_GBT_MEMPOOL": false,
|
"ADVANCED_GBT_MEMPOOL": false,
|
||||||
"CPFP_INDEXING": false
|
"CPFP_INDEXING": false,
|
||||||
|
"RBF_DUAL_NODE": false
|
||||||
},
|
},
|
||||||
"CORE_RPC": {
|
"CORE_RPC": {
|
||||||
"HOST": "127.0.0.1",
|
"HOST": "127.0.0.1",
|
||||||
|
@ -26,9 +26,10 @@
|
|||||||
"INDEXING_BLOCKS_AMOUNT": 14,
|
"INDEXING_BLOCKS_AMOUNT": 14,
|
||||||
"POOLS_JSON_TREE_URL": "__POOLS_JSON_TREE_URL__",
|
"POOLS_JSON_TREE_URL": "__POOLS_JSON_TREE_URL__",
|
||||||
"POOLS_JSON_URL": "__POOLS_JSON_URL__",
|
"POOLS_JSON_URL": "__POOLS_JSON_URL__",
|
||||||
"ADVANCED_GBT_AUDIT": "__MEMPOOL_ADVANCED_GBT_AUDIT__",
|
"ADVANCED_GBT_AUDIT": "__ADVANCED_GBT_AUDIT__",
|
||||||
"ADVANCED_GBT_MEMPOOL": "__MEMPOOL_ADVANCED_GBT_MEMPOOL__",
|
"ADVANCED_GBT_MEMPOOL": "__ADVANCED_GBT_MEMPOOL__",
|
||||||
"CPFP_INDEXING": "__MEMPOOL_CPFP_INDEXING__"
|
"CPFP_INDEXING": "__CPFP_INDEXING__",
|
||||||
|
"RBF_DUAL_NODE": "__RBF_DUAL_NODE__"
|
||||||
},
|
},
|
||||||
"CORE_RPC": {
|
"CORE_RPC": {
|
||||||
"HOST": "__CORE_RPC_HOST__",
|
"HOST": "__CORE_RPC_HOST__",
|
||||||
|
@ -41,6 +41,7 @@ describe('Mempool Backend Config', () => {
|
|||||||
ADVANCED_GBT_AUDIT: false,
|
ADVANCED_GBT_AUDIT: false,
|
||||||
ADVANCED_GBT_MEMPOOL: false,
|
ADVANCED_GBT_MEMPOOL: false,
|
||||||
CPFP_INDEXING: false,
|
CPFP_INDEXING: false,
|
||||||
|
RBF_DUAL_NODE: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(config.ELECTRUM).toStrictEqual({ HOST: '127.0.0.1', PORT: 3306, TLS_ENABLED: true });
|
expect(config.ELECTRUM).toStrictEqual({ HOST: '127.0.0.1', PORT: 3306, TLS_ENABLED: true });
|
||||||
|
175
backend/src/api/alt-mempool.ts
Normal file
175
backend/src/api/alt-mempool.ts
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import config from '../config';
|
||||||
|
import { TransactionExtended } from '../mempool.interfaces';
|
||||||
|
import logger from '../logger';
|
||||||
|
import { Common } from './common';
|
||||||
|
import { IBitcoinApi } from './bitcoin/bitcoin-api.interface';
|
||||||
|
import BitcoinApi from './bitcoin/bitcoin-api';
|
||||||
|
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||||
|
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
|
||||||
|
import { IEsploraApi } from './bitcoin/esplora-api.interface';
|
||||||
|
import { Mempool } from './mempool';
|
||||||
|
|
||||||
|
class AltMempool extends Mempool {
|
||||||
|
private bitcoinSecondApi: BitcoinApi;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.bitcoinSecondApi = new BitcoinApi(bitcoinSecondClient, this, bitcoinApi);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected init(): void {
|
||||||
|
// override
|
||||||
|
}
|
||||||
|
|
||||||
|
public setOutOfSync(): void {
|
||||||
|
this.inSync = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setMempool(mempoolData: { [txId: string]: TransactionExtended }): void {
|
||||||
|
this.mempoolCache = mempoolData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFirstSeenForTransactions(txIds: string[]): number[] {
|
||||||
|
const txTimes: number[] = [];
|
||||||
|
txIds.forEach((txId: string) => {
|
||||||
|
const tx = this.mempoolCache[txId];
|
||||||
|
if (tx && tx.firstSeen) {
|
||||||
|
txTimes.push(tx.firstSeen);
|
||||||
|
} else {
|
||||||
|
txTimes.push(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return txTimes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $updateMempool(): Promise<void> {
|
||||||
|
logger.debug(`Updating alternative mempool...`);
|
||||||
|
const start = new Date().getTime();
|
||||||
|
const currentMempoolSize = Object.keys(this.mempoolCache).length;
|
||||||
|
const transactions = await this.bitcoinSecondApi.$getRawMempool();
|
||||||
|
const diff = transactions.length - currentMempoolSize;
|
||||||
|
|
||||||
|
this.mempoolCacheDelta = Math.abs(diff);
|
||||||
|
const loadingMempool = this.mempoolCacheDelta > 100;
|
||||||
|
|
||||||
|
for (const txid of transactions) {
|
||||||
|
if (!this.mempoolCache[txid]) {
|
||||||
|
try {
|
||||||
|
const transaction = await this.$fetchTransaction(txid);
|
||||||
|
this.mempoolCache[txid] = transaction;
|
||||||
|
if (loadingMempool && Object.keys(this.mempoolCache).length % 50 === 0) {
|
||||||
|
logger.info(`loaded ${Object.keys(this.mempoolCache).length}/${transactions.length} alternative mempool transactions`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug(`Error finding transaction '${txid}' in the alternative mempool: ` + (e instanceof Error ? e.message : e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((new Date().getTime()) - start > Mempool.WEBSOCKET_REFRESH_RATE_MS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (loadingMempool) {
|
||||||
|
logger.info(`loaded ${Object.keys(this.mempoolCache).length}/${transactions.length} alternative mempool transactions`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent mempool from clear on bitcoind restart by delaying the deletion
|
||||||
|
if (this.mempoolProtection === 0
|
||||||
|
&& currentMempoolSize > 20000
|
||||||
|
&& transactions.length / currentMempoolSize <= 0.80
|
||||||
|
) {
|
||||||
|
this.mempoolProtection = 1;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.mempoolProtection = 2;
|
||||||
|
}, 1000 * 60 * config.MEMPOOL.CLEAR_PROTECTION_MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedTransactions: string[] = [];
|
||||||
|
|
||||||
|
if (this.mempoolProtection !== 1) {
|
||||||
|
this.mempoolProtection = 0;
|
||||||
|
// Index object for faster search
|
||||||
|
const transactionsObject = {};
|
||||||
|
transactions.forEach((txId) => transactionsObject[txId] = true);
|
||||||
|
|
||||||
|
// Flag transactions for lazy deletion
|
||||||
|
for (const tx in this.mempoolCache) {
|
||||||
|
if (!transactionsObject[tx] && !this.mempoolCache[tx].deleteAfter) {
|
||||||
|
deletedTransactions.push(this.mempoolCache[tx].txid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const txid of deletedTransactions) {
|
||||||
|
delete this.mempoolCache[txid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mempoolCacheDelta = Math.abs(transactions.length - Object.keys(this.mempoolCache).length);
|
||||||
|
|
||||||
|
const end = new Date().getTime();
|
||||||
|
const time = end - start;
|
||||||
|
logger.debug(`Alt mempool updated in ${time / 1000} seconds. New size: ${Object.keys(this.mempoolCache).length} (${diff > 0 ? '+' + diff : diff})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTransaction(txid: string): TransactionExtended {
|
||||||
|
return this.mempoolCache[txid] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async $fetchTransaction(txid: string): Promise<TransactionExtended> {
|
||||||
|
const rawTx = await this.bitcoinSecondApi.$getRawTransaction(txid, false, true, false);
|
||||||
|
return this.extendTransaction(rawTx);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected extendTransaction(transaction: IEsploraApi.Transaction): TransactionExtended {
|
||||||
|
// @ts-ignore
|
||||||
|
if (transaction.vsize) {
|
||||||
|
// @ts-ignore
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
const feePerVbytes = Math.max(Common.isLiquid() ? 0.1 : 1,
|
||||||
|
(transaction.fee || 0) / (transaction.weight / 4));
|
||||||
|
const transactionExtended: TransactionExtended = Object.assign({
|
||||||
|
vsize: Math.round(transaction.weight / 4),
|
||||||
|
feePerVsize: feePerVbytes,
|
||||||
|
effectiveFeePerVsize: feePerVbytes,
|
||||||
|
}, transaction);
|
||||||
|
if (!transaction.status.confirmed) {
|
||||||
|
transactionExtended.firstSeen = Math.round((new Date().getTime() / 1000));
|
||||||
|
}
|
||||||
|
return transactionExtended;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $updateMemPoolInfo(): Promise<void> {
|
||||||
|
this.mempoolInfo = await this.$getMempoolInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMempoolInfo(): IBitcoinApi.MempoolInfo {
|
||||||
|
return this.mempoolInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTxPerSecond(): number {
|
||||||
|
return this.txPerSecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getVBytesPerSecond(): number {
|
||||||
|
return this.vBytesPerSecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleRbfTransactions(rbfTransactions: { [txid: string]: TransactionExtended; }): void {
|
||||||
|
for (const rbfTransaction in rbfTransactions) {
|
||||||
|
if (this.mempoolCache[rbfTransaction]) {
|
||||||
|
// Erase the replaced transactions from the local mempool
|
||||||
|
delete this.mempoolCache[rbfTransaction];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updateTxPerSecond(): void {}
|
||||||
|
|
||||||
|
protected deleteExpiredTransactions(): void {}
|
||||||
|
|
||||||
|
protected $getMempoolInfo(): any {
|
||||||
|
return bitcoinSecondClient.getMempoolInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new AltMempool();
|
@ -4,19 +4,20 @@ import EsploraApi from './esplora-api';
|
|||||||
import BitcoinApi from './bitcoin-api';
|
import BitcoinApi from './bitcoin-api';
|
||||||
import ElectrumApi from './electrum-api';
|
import ElectrumApi from './electrum-api';
|
||||||
import bitcoinClient from './bitcoin-client';
|
import bitcoinClient from './bitcoin-client';
|
||||||
|
import mempool from '../mempool';
|
||||||
|
|
||||||
function bitcoinApiFactory(): AbstractBitcoinApi {
|
function bitcoinApiFactory(): AbstractBitcoinApi {
|
||||||
switch (config.MEMPOOL.BACKEND) {
|
switch (config.MEMPOOL.BACKEND) {
|
||||||
case 'esplora':
|
case 'esplora':
|
||||||
return new EsploraApi();
|
return new EsploraApi();
|
||||||
case 'electrum':
|
case 'electrum':
|
||||||
return new ElectrumApi(bitcoinClient);
|
return new ElectrumApi(bitcoinClient, mempool);
|
||||||
case 'none':
|
case 'none':
|
||||||
default:
|
default:
|
||||||
return new BitcoinApi(bitcoinClient);
|
return new BitcoinApi(bitcoinClient, mempool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bitcoinCoreApi = new BitcoinApi(bitcoinClient);
|
export const bitcoinCoreApi = new BitcoinApi(bitcoinClient, mempool);
|
||||||
|
|
||||||
export default bitcoinApiFactory();
|
export default bitcoinApiFactory();
|
||||||
|
@ -3,15 +3,18 @@ import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
|||||||
import { IBitcoinApi } from './bitcoin-api.interface';
|
import { IBitcoinApi } from './bitcoin-api.interface';
|
||||||
import { IEsploraApi } from './esplora-api.interface';
|
import { IEsploraApi } from './esplora-api.interface';
|
||||||
import blocks from '../blocks';
|
import blocks from '../blocks';
|
||||||
import mempool from '../mempool';
|
import { MempoolBlock, TransactionExtended } from '../../mempool.interfaces';
|
||||||
import { TransactionExtended } from '../../mempool.interfaces';
|
|
||||||
|
|
||||||
class BitcoinApi implements AbstractBitcoinApi {
|
class BitcoinApi implements AbstractBitcoinApi {
|
||||||
|
private mempool: any = null;
|
||||||
private rawMempoolCache: IBitcoinApi.RawMempool | null = null;
|
private rawMempoolCache: IBitcoinApi.RawMempool | null = null;
|
||||||
protected bitcoindClient: any;
|
protected bitcoindClient: any;
|
||||||
|
protected backupBitcoinApi: BitcoinApi;
|
||||||
|
|
||||||
constructor(bitcoinClient: any) {
|
constructor(bitcoinClient: any, mempool: any, backupBitcoinApi: any = null) {
|
||||||
this.bitcoindClient = bitcoinClient;
|
this.bitcoindClient = bitcoinClient;
|
||||||
|
this.mempool = mempool;
|
||||||
|
this.backupBitcoinApi = backupBitcoinApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
static convertBlock(block: IBitcoinApi.Block): IEsploraApi.Block {
|
static convertBlock(block: IBitcoinApi.Block): IEsploraApi.Block {
|
||||||
@ -34,7 +37,7 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
|
|
||||||
$getRawTransaction(txId: string, skipConversion = false, addPrevout = false, lazyPrevouts = false): Promise<IEsploraApi.Transaction> {
|
$getRawTransaction(txId: string, skipConversion = false, addPrevout = false, lazyPrevouts = false): Promise<IEsploraApi.Transaction> {
|
||||||
// If the transaction is in the mempool we already converted and fetched the fee. Only prevouts are missing
|
// If the transaction is in the mempool we already converted and fetched the fee. Only prevouts are missing
|
||||||
const txInMempool = mempool.getMempool()[txId];
|
const txInMempool = this.mempool.getMempool()[txId];
|
||||||
if (txInMempool && addPrevout) {
|
if (txInMempool && addPrevout) {
|
||||||
return this.$addPrevouts(txInMempool);
|
return this.$addPrevouts(txInMempool);
|
||||||
}
|
}
|
||||||
@ -118,7 +121,7 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
|
|
||||||
$getAddressPrefix(prefix: string): string[] {
|
$getAddressPrefix(prefix: string): string[] {
|
||||||
const found: { [address: string]: string } = {};
|
const found: { [address: string]: string } = {};
|
||||||
const mp = mempool.getMempool();
|
const mp = this.mempool.getMempool();
|
||||||
for (const tx in mp) {
|
for (const tx in mp) {
|
||||||
for (const vout of mp[tx].vout) {
|
for (const vout of mp[tx].vout) {
|
||||||
if (vout.scriptpubkey_address.indexOf(prefix) === 0) {
|
if (vout.scriptpubkey_address.indexOf(prefix) === 0) {
|
||||||
@ -260,7 +263,7 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
let mempoolEntry: IBitcoinApi.MempoolEntry;
|
let mempoolEntry: IBitcoinApi.MempoolEntry;
|
||||||
if (!mempool.isInSync() && !this.rawMempoolCache) {
|
if (!this.mempool.isInSync() && !this.rawMempoolCache) {
|
||||||
this.rawMempoolCache = await this.$getRawMempoolVerbose();
|
this.rawMempoolCache = await this.$getRawMempoolVerbose();
|
||||||
}
|
}
|
||||||
if (this.rawMempoolCache && this.rawMempoolCache[transaction.txid]) {
|
if (this.rawMempoolCache && this.rawMempoolCache[transaction.txid]) {
|
||||||
@ -316,10 +319,23 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
transaction.vin[i].lazy = true;
|
transaction.vin[i].lazy = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const innerTx = await this.$getRawTransaction(transaction.vin[i].txid, false, false);
|
let innerTx;
|
||||||
transaction.vin[i].prevout = innerTx.vout[transaction.vin[i].vout];
|
try {
|
||||||
this.addInnerScriptsToVin(transaction.vin[i]);
|
innerTx = await this.$getRawTransaction(transaction.vin[i].txid, false, false);
|
||||||
totalIn += innerTx.vout[transaction.vin[i].vout].value;
|
} catch (e) {
|
||||||
|
if (this.backupBitcoinApi) {
|
||||||
|
// input tx is confirmed, but the preferred client has txindex=0, so fetch from the backup client instead.
|
||||||
|
const backupTx = await this.backupBitcoinApi.$getRawTransaction(transaction.vin[i].txid);
|
||||||
|
innerTx = JSON.parse(JSON.stringify(backupTx));
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (innerTx) {
|
||||||
|
transaction.vin[i].prevout = innerTx.vout[transaction.vin[i].vout];
|
||||||
|
this.addInnerScriptsToVin(transaction.vin[i]);
|
||||||
|
totalIn += innerTx.vout[transaction.vin[i].vout].value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (lazyPrevouts && transaction.vin.length > 12) {
|
if (lazyPrevouts && transaction.vin.length > 12) {
|
||||||
transaction.fee = -1;
|
transaction.fee = -1;
|
||||||
|
@ -19,6 +19,7 @@ import bitcoinClient from './bitcoin-client';
|
|||||||
import difficultyAdjustment from '../difficulty-adjustment';
|
import difficultyAdjustment from '../difficulty-adjustment';
|
||||||
import transactionRepository from '../../repositories/TransactionRepository';
|
import transactionRepository from '../../repositories/TransactionRepository';
|
||||||
import rbfCache from '../rbf-cache';
|
import rbfCache from '../rbf-cache';
|
||||||
|
import altMempool from '../alt-mempool';
|
||||||
|
|
||||||
class BitcoinRoutes {
|
class BitcoinRoutes {
|
||||||
public initRoutes(app: Application) {
|
public initRoutes(app: Application) {
|
||||||
@ -32,6 +33,7 @@ class BitcoinRoutes {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'backend-info', this.getBackendInfo)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'backend-info', this.getBackendInfo)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'init-data', this.getInitData)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'init-data', this.getInitData)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'validate-address/:address', this.validateAddress)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'validate-address/:address', this.validateAddress)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/alt/:txId', this.getAltTransaction)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/replaces', this.getRbfHistory)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/replaces', this.getRbfHistory)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/cached', this.getCachedTx)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/cached', this.getCachedTx)
|
||||||
.post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', this.$postTransactionForm)
|
.post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', this.$postTransactionForm)
|
||||||
@ -241,6 +243,23 @@ class BitcoinRoutes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getAltTransaction(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const transaction = altMempool.getTransaction(req.params.txId);
|
||||||
|
if (transaction) {
|
||||||
|
res.json(transaction);
|
||||||
|
} else {
|
||||||
|
res.status(404).send('No such transaction in the alternate mempool');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
let statusCode = 500;
|
||||||
|
if (e instanceof Error && e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
|
||||||
|
statusCode = 404;
|
||||||
|
}
|
||||||
|
res.status(statusCode).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async getRawTransaction(req: Request, res: Response) {
|
private async getRawTransaction(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const transaction: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(req.params.txId, true);
|
const transaction: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(req.params.txId, true);
|
||||||
|
@ -12,8 +12,8 @@ import memoryCache from '../memory-cache';
|
|||||||
class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
|
class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
|
||||||
private electrumClient: any;
|
private electrumClient: any;
|
||||||
|
|
||||||
constructor(bitcoinClient: any) {
|
constructor(bitcoinClient: any, mempool: any) {
|
||||||
super(bitcoinClient);
|
super(bitcoinClient, mempool);
|
||||||
|
|
||||||
const electrumConfig = { client: 'mempool-v2', version: '1.4' };
|
const electrumConfig = { client: 'mempool-v2', version: '1.4' };
|
||||||
const electrumPersistencePolicy = { retryPeriod: 10000, maxRetry: 1000, callback: null };
|
const electrumPersistencePolicy = { retryPeriod: 10000, maxRetry: 1000, callback: null };
|
||||||
|
@ -249,7 +249,7 @@ class MempoolBlocks {
|
|||||||
cluster.forEach(txid => {
|
cluster.forEach(txid => {
|
||||||
if (txid === tx.txid) {
|
if (txid === tx.txid) {
|
||||||
matched = true;
|
matched = true;
|
||||||
} else {
|
} else if (mempool[txid]) {
|
||||||
const relative = {
|
const relative = {
|
||||||
txid: txid,
|
txid: txid,
|
||||||
fee: mempool[txid].fee,
|
fee: mempool[txid].fee,
|
||||||
|
@ -10,28 +10,32 @@ import bitcoinClient from './bitcoin/bitcoin-client';
|
|||||||
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
|
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
|
||||||
import rbfCache from './rbf-cache';
|
import rbfCache from './rbf-cache';
|
||||||
|
|
||||||
class Mempool {
|
export class Mempool {
|
||||||
private static WEBSOCKET_REFRESH_RATE_MS = 10000;
|
protected static WEBSOCKET_REFRESH_RATE_MS = 10000;
|
||||||
private static LAZY_DELETE_AFTER_SECONDS = 30;
|
protected static LAZY_DELETE_AFTER_SECONDS = 30;
|
||||||
private inSync: boolean = false;
|
protected inSync: boolean = false;
|
||||||
private mempoolCacheDelta: number = -1;
|
protected mempoolCacheDelta: number = -1;
|
||||||
private mempoolCache: { [txId: string]: TransactionExtended } = {};
|
protected mempoolCache: { [txId: string]: TransactionExtended } = {};
|
||||||
private mempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0,
|
protected mempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0,
|
||||||
maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
|
maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
|
||||||
private mempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
protected mempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
||||||
deletedTransactions: TransactionExtended[]) => void) | undefined;
|
deletedTransactions: TransactionExtended[]) => void) | undefined;
|
||||||
private asyncMempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
protected asyncMempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
||||||
deletedTransactions: TransactionExtended[]) => Promise<void>) | undefined;
|
deletedTransactions: TransactionExtended[]) => Promise<void>) | undefined;
|
||||||
|
|
||||||
private txPerSecondArray: number[] = [];
|
protected txPerSecondArray: number[] = [];
|
||||||
private txPerSecond: number = 0;
|
protected txPerSecond: number = 0;
|
||||||
|
|
||||||
private vBytesPerSecondArray: VbytesPerSecond[] = [];
|
protected vBytesPerSecondArray: VbytesPerSecond[] = [];
|
||||||
private vBytesPerSecond: number = 0;
|
protected vBytesPerSecond: number = 0;
|
||||||
private mempoolProtection = 0;
|
protected mempoolProtection = 0;
|
||||||
private latestTransactions: any[] = [];
|
protected latestTransactions: any[] = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected init(): void {
|
||||||
setInterval(this.updateTxPerSecond.bind(this), 1000);
|
setInterval(this.updateTxPerSecond.bind(this), 1000);
|
||||||
setInterval(this.deleteExpiredTransactions.bind(this), 20000);
|
setInterval(this.deleteExpiredTransactions.bind(this), 20000);
|
||||||
}
|
}
|
||||||
@ -217,7 +221,7 @@ class Mempool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateTxPerSecond() {
|
protected updateTxPerSecond() {
|
||||||
const nowMinusTimeSpan = new Date().getTime() - (1000 * config.STATISTICS.TX_PER_SECOND_SAMPLE_PERIOD);
|
const nowMinusTimeSpan = new Date().getTime() - (1000 * config.STATISTICS.TX_PER_SECOND_SAMPLE_PERIOD);
|
||||||
this.txPerSecondArray = this.txPerSecondArray.filter((unixTime) => unixTime > nowMinusTimeSpan);
|
this.txPerSecondArray = this.txPerSecondArray.filter((unixTime) => unixTime > nowMinusTimeSpan);
|
||||||
this.txPerSecond = this.txPerSecondArray.length / config.STATISTICS.TX_PER_SECOND_SAMPLE_PERIOD || 0;
|
this.txPerSecond = this.txPerSecondArray.length / config.STATISTICS.TX_PER_SECOND_SAMPLE_PERIOD || 0;
|
||||||
@ -230,7 +234,7 @@ class Mempool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private deleteExpiredTransactions() {
|
protected deleteExpiredTransactions() {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
for (const tx in this.mempoolCache) {
|
for (const tx in this.mempoolCache) {
|
||||||
const lazyDeleteAt = this.mempoolCache[tx].deleteAfter;
|
const lazyDeleteAt = this.mempoolCache[tx].deleteAfter;
|
||||||
@ -241,7 +245,7 @@ class Mempool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private $getMempoolInfo() {
|
protected $getMempoolInfo() {
|
||||||
if (config.MEMPOOL.USE_SECOND_NODE_FOR_MINFEE) {
|
if (config.MEMPOOL.USE_SECOND_NODE_FOR_MINFEE) {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
bitcoinClient.getMempoolInfo(),
|
bitcoinClient.getMempoolInfo(),
|
||||||
|
@ -32,6 +32,7 @@ interface IConfig {
|
|||||||
ADVANCED_GBT_AUDIT: boolean;
|
ADVANCED_GBT_AUDIT: boolean;
|
||||||
ADVANCED_GBT_MEMPOOL: boolean;
|
ADVANCED_GBT_MEMPOOL: boolean;
|
||||||
CPFP_INDEXING: boolean;
|
CPFP_INDEXING: boolean;
|
||||||
|
RBF_DUAL_NODE: boolean;
|
||||||
};
|
};
|
||||||
ESPLORA: {
|
ESPLORA: {
|
||||||
REST_API_URL: string;
|
REST_API_URL: string;
|
||||||
@ -153,6 +154,7 @@ const defaults: IConfig = {
|
|||||||
'ADVANCED_GBT_AUDIT': false,
|
'ADVANCED_GBT_AUDIT': false,
|
||||||
'ADVANCED_GBT_MEMPOOL': false,
|
'ADVANCED_GBT_MEMPOOL': false,
|
||||||
'CPFP_INDEXING': false,
|
'CPFP_INDEXING': false,
|
||||||
|
'RBF_DUAL_NODE': false,
|
||||||
},
|
},
|
||||||
'ESPLORA': {
|
'ESPLORA': {
|
||||||
'REST_API_URL': 'http://127.0.0.1:3000',
|
'REST_API_URL': 'http://127.0.0.1:3000',
|
||||||
|
@ -17,6 +17,7 @@ import logger from './logger';
|
|||||||
import backendInfo from './api/backend-info';
|
import backendInfo from './api/backend-info';
|
||||||
import loadingIndicators from './api/loading-indicators';
|
import loadingIndicators from './api/loading-indicators';
|
||||||
import mempool from './api/mempool';
|
import mempool from './api/mempool';
|
||||||
|
import altMempool from './api/alt-mempool';
|
||||||
import elementsParser from './api/liquid/elements-parser';
|
import elementsParser from './api/liquid/elements-parser';
|
||||||
import databaseMigration from './api/database-migration';
|
import databaseMigration from './api/database-migration';
|
||||||
import syncAssets from './sync-assets';
|
import syncAssets from './sync-assets';
|
||||||
@ -170,6 +171,9 @@ class Server {
|
|||||||
await poolsUpdater.updatePoolsJson();
|
await poolsUpdater.updatePoolsJson();
|
||||||
await blocks.$updateBlocks();
|
await blocks.$updateBlocks();
|
||||||
await memPool.$updateMempool();
|
await memPool.$updateMempool();
|
||||||
|
if (config.MEMPOOL.RBF_DUAL_NODE) {
|
||||||
|
await altMempool.$updateMempool();
|
||||||
|
}
|
||||||
indexer.$run();
|
indexer.$run();
|
||||||
|
|
||||||
setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS);
|
setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS);
|
||||||
|
@ -25,7 +25,8 @@
|
|||||||
"AUTOMATIC_BLOCK_REINDEXING": __MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__,
|
"AUTOMATIC_BLOCK_REINDEXING": __MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__,
|
||||||
"ADVANCED_GBT_AUDIT": __MEMPOOL_ADVANCED_GBT_AUDIT__,
|
"ADVANCED_GBT_AUDIT": __MEMPOOL_ADVANCED_GBT_AUDIT__,
|
||||||
"ADVANCED_GBT_MEMPOOL": __MEMPOOL_ADVANCED_GBT_MEMPOOL__,
|
"ADVANCED_GBT_MEMPOOL": __MEMPOOL_ADVANCED_GBT_MEMPOOL__,
|
||||||
"CPFP_INDEXING": __MEMPOOL_CPFP_INDEXING__
|
"CPFP_INDEXING": __MEMPOOL_CPFP_INDEXING__,
|
||||||
|
"RBF_DUAL_NODE": __RBF_DUAL_NODE__
|
||||||
},
|
},
|
||||||
"CORE_RPC": {
|
"CORE_RPC": {
|
||||||
"HOST": "__CORE_RPC_HOST__",
|
"HOST": "__CORE_RPC_HOST__",
|
||||||
|
@ -30,6 +30,8 @@ __MEMPOOL_POOLS_JSON_TREE_URL__=${MEMPOOL_POOLS_JSON_TREE_URL:=https://api.githu
|
|||||||
__MEMPOOL_ADVANCED_GBT_AUDIT__=${MEMPOOL_ADVANCED_GBT_AUDIT:=false}
|
__MEMPOOL_ADVANCED_GBT_AUDIT__=${MEMPOOL_ADVANCED_GBT_AUDIT:=false}
|
||||||
__MEMPOOL_ADVANCED_GBT_MEMPOOL__=${MEMPOOL_ADVANCED_GBT_MEMPOOL:=false}
|
__MEMPOOL_ADVANCED_GBT_MEMPOOL__=${MEMPOOL_ADVANCED_GBT_MEMPOOL:=false}
|
||||||
__MEMPOOL_CPFP_INDEXING__=${MEMPOOL_CPFP_INDEXING:=false}
|
__MEMPOOL_CPFP_INDEXING__=${MEMPOOL_CPFP_INDEXING:=false}
|
||||||
|
__MEMPOOL_RBF_DUAL_NODE__=${MEMPOOL_RBF_DUAL_NODE:=false}
|
||||||
|
|
||||||
|
|
||||||
# CORE_RPC
|
# CORE_RPC
|
||||||
__CORE_RPC_HOST__=${CORE_RPC_HOST:=127.0.0.1}
|
__CORE_RPC_HOST__=${CORE_RPC_HOST:=127.0.0.1}
|
||||||
@ -142,6 +144,7 @@ sed -i "s!__MEMPOOL_POOLS_JSON_TREE_URL__!${__MEMPOOL_POOLS_JSON_TREE_URL__}!g"
|
|||||||
sed -i "s!__MEMPOOL_ADVANCED_GBT_MEMPOOL__!${__MEMPOOL_ADVANCED_GBT_MEMPOOL__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_ADVANCED_GBT_MEMPOOL__!${__MEMPOOL_ADVANCED_GBT_MEMPOOL__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_ADVANCED_GBT_AUDIT__!${__MEMPOOL_ADVANCED_GBT_AUDIT__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_ADVANCED_GBT_AUDIT__!${__MEMPOOL_ADVANCED_GBT_AUDIT__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_CPFP_INDEXING__!${__MEMPOOL_CPFP_INDEXING__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_CPFP_INDEXING__!${__MEMPOOL_CPFP_INDEXING__}!g" mempool-config.json
|
||||||
|
sed -i "s/__MEMPOOL_RBF_DUAL_NODE__/${__MEMPOOL_RBF_DUAL_NODE__}/g" mempool-config.json
|
||||||
|
|
||||||
sed -i "s/__CORE_RPC_HOST__/${__CORE_RPC_HOST__}/g" mempool-config.json
|
sed -i "s/__CORE_RPC_HOST__/${__CORE_RPC_HOST__}/g" mempool-config.json
|
||||||
sed -i "s/__CORE_RPC_PORT__/${__CORE_RPC_PORT__}/g" mempool-config.json
|
sed -i "s/__CORE_RPC_PORT__/${__CORE_RPC_PORT__}/g" mempool-config.json
|
||||||
|
@ -22,5 +22,5 @@
|
|||||||
"SIGNET_BLOCK_AUDIT_START_HEIGHT": 0,
|
"SIGNET_BLOCK_AUDIT_START_HEIGHT": 0,
|
||||||
"LIGHTNING": false,
|
"LIGHTNING": false,
|
||||||
"FULL_RBF_ENABLED": false,
|
"FULL_RBF_ENABLED": false,
|
||||||
"ALT_BACKEND_URL": "https://rbf.mempool.space"
|
"ALT_BACKEND_ENABLED": false
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Component, OnInit, AfterViewInit, OnDestroy, HostListener, ViewChild, ElementRef } from '@angular/core';
|
import { Component, OnInit, AfterViewInit, OnDestroy, HostListener, ViewChild, ElementRef } from '@angular/core';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
import { AltElectrsApiService } from '../../services/alt-electrs-api.service';
|
|
||||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||||
import {
|
import {
|
||||||
switchMap,
|
switchMap,
|
||||||
@ -44,7 +43,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
fetchRbfSubscription: Subscription;
|
fetchRbfSubscription: Subscription;
|
||||||
fetchCachedTxSubscription: Subscription;
|
fetchCachedTxSubscription: Subscription;
|
||||||
txReplacedSubscription: Subscription;
|
txReplacedSubscription: Subscription;
|
||||||
altBackendTxSubscription: Subscription;
|
altTxSubscription: Subscription;
|
||||||
blocksSubscription: Subscription;
|
blocksSubscription: Subscription;
|
||||||
queryParamsSubscription: Subscription;
|
queryParamsSubscription: Subscription;
|
||||||
urlFragmentSubscription: Subscription;
|
urlFragmentSubscription: Subscription;
|
||||||
@ -87,7 +86,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private relativeUrlPipe: RelativeUrlPipe,
|
private relativeUrlPipe: RelativeUrlPipe,
|
||||||
private electrsApiService: ElectrsApiService,
|
private electrsApiService: ElectrsApiService,
|
||||||
private altElectrsApiService: AltElectrsApiService,
|
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
@ -96,7 +94,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
private seoService: SeoService
|
private seoService: SeoService
|
||||||
) {
|
) {
|
||||||
this.fullRBF = stateService.env.FULL_RBF_ENABLED;
|
this.fullRBF = stateService.env.FULL_RBF_ENABLED;
|
||||||
this.altBackend = stateService.env.ALT_BACKEND_URL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -203,27 +200,29 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tx = tx;
|
if (!this.altTx && !this.tx) {
|
||||||
if (tx.fee === undefined) {
|
this.tx = tx;
|
||||||
this.tx.fee = 0;
|
if (tx.fee === undefined) {
|
||||||
}
|
this.tx.fee = 0;
|
||||||
this.tx.feePerVsize = tx.fee / (tx.weight / 4);
|
}
|
||||||
this.isLoadingTx = false;
|
this.tx.feePerVsize = tx.fee / (tx.weight / 4);
|
||||||
this.error = undefined;
|
this.isLoadingTx = false;
|
||||||
this.waitingForTransaction = false;
|
this.error = undefined;
|
||||||
this.graphExpanded = false;
|
this.waitingForTransaction = false;
|
||||||
this.setupGraph();
|
this.graphExpanded = false;
|
||||||
|
this.setupGraph();
|
||||||
|
|
||||||
if (!this.tx?.status?.confirmed) {
|
if (!this.tx?.status?.confirmed) {
|
||||||
this.fetchRbfHistory$.next(this.tx.txid);
|
this.fetchRbfHistory$.next(this.tx.txid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.altBackendTxSubscription = this.checkAltBackend$
|
this.altTxSubscription = this.checkAltBackend$
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((txId) =>
|
switchMap((txId) =>
|
||||||
this.altElectrsApiService
|
this.apiService
|
||||||
.getTransaction$(txId)
|
.getAltTransaction$(txId)
|
||||||
.pipe(
|
.pipe(
|
||||||
catchError((e) => {
|
catchError((e) => {
|
||||||
return of(null);
|
return of(null);
|
||||||
@ -326,7 +325,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
.subscribe((tx: Transaction) => {
|
.subscribe((tx: Transaction) => {
|
||||||
if (!tx) {
|
if (!tx) {
|
||||||
this.notFound = true;
|
this.notFound = true;
|
||||||
if (this.stateService.env.ALT_BACKEND_URL) {
|
if (this.stateService.env.ALT_BACKEND_ENABLED) {
|
||||||
this.checkAltBackend$.next(this.txId);
|
this.checkAltBackend$.next(this.txId);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -370,9 +369,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.fetchCpfp$.next(this.tx.txid);
|
this.fetchCpfp$.next(this.tx.txid);
|
||||||
}
|
}
|
||||||
this.fetchRbfHistory$.next(this.tx.txid);
|
this.fetchRbfHistory$.next(this.tx.txid);
|
||||||
if (this.stateService.env.ALT_BACKEND_URL) {
|
|
||||||
this.checkAltBackend$.next(this.txId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setTimeout(() => { this.applyFragment(); }, 0);
|
setTimeout(() => { this.applyFragment(); }, 0);
|
||||||
},
|
},
|
||||||
@ -546,7 +542,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.fetchCpfpSubscription.unsubscribe();
|
this.fetchCpfpSubscription.unsubscribe();
|
||||||
this.fetchRbfSubscription.unsubscribe();
|
this.fetchRbfSubscription.unsubscribe();
|
||||||
this.fetchCachedTxSubscription.unsubscribe();
|
this.fetchCachedTxSubscription.unsubscribe();
|
||||||
this.altBackendTxSubscription?.unsubscribe();
|
this.altTxSubscription?.unsubscribe();
|
||||||
this.txReplacedSubscription.unsubscribe();
|
this.txReplacedSubscription.unsubscribe();
|
||||||
this.blocksSubscription.unsubscribe();
|
this.blocksSubscription.unsubscribe();
|
||||||
this.queryParamsSubscription.unsubscribe();
|
this.queryParamsSubscription.unsubscribe();
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { Transaction, Address, Outspend, Recent, Asset } from '../interfaces/electrs.interface';
|
|
||||||
import { StateService } from './state.service';
|
|
||||||
import { BlockExtended } from '../interfaces/node-api.interface';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class AltElectrsApiService {
|
|
||||||
private apiBaseUrl: string; // base URL is protocol, hostname, and port
|
|
||||||
private apiBasePath: string; // network path is /testnet, etc. or '' for mainnet
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private httpClient: HttpClient,
|
|
||||||
private stateService: StateService,
|
|
||||||
) {
|
|
||||||
this.apiBaseUrl = stateService.env.ALT_BACKEND_URL || '';
|
|
||||||
this.apiBasePath = ''; // assume mainnet by default
|
|
||||||
this.stateService.networkChanged$.subscribe((network) => {
|
|
||||||
if (network === 'bisq') {
|
|
||||||
network = '';
|
|
||||||
}
|
|
||||||
this.apiBasePath = network ? '/' + network : '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getBlock$(hash: string): Observable<BlockExtended> {
|
|
||||||
return this.httpClient.get<BlockExtended>(this.apiBaseUrl + this.apiBasePath + '/api/block/' + hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
listBlocks$(height?: number): Observable<BlockExtended[]> {
|
|
||||||
return this.httpClient.get<BlockExtended[]>(this.apiBaseUrl + this.apiBasePath + '/api/blocks/' + (height || ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
getTransaction$(txId: string): Observable<Transaction> {
|
|
||||||
return this.httpClient.get<Transaction>(this.apiBaseUrl + this.apiBasePath + '/api/tx/' + txId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRecentTransaction$(): Observable<Recent[]> {
|
|
||||||
return this.httpClient.get<Recent[]>(this.apiBaseUrl + this.apiBasePath + '/api/mempool/recent');
|
|
||||||
}
|
|
||||||
|
|
||||||
getOutspend$(hash: string, vout: number): Observable<Outspend> {
|
|
||||||
return this.httpClient.get<Outspend>(this.apiBaseUrl + this.apiBasePath + '/api/tx/' + hash + '/outspend/' + vout);
|
|
||||||
}
|
|
||||||
|
|
||||||
getOutspends$(hash: string): Observable<Outspend[]> {
|
|
||||||
return this.httpClient.get<Outspend[]>(this.apiBaseUrl + this.apiBasePath + '/api/tx/' + hash + '/outspends');
|
|
||||||
}
|
|
||||||
|
|
||||||
getBlockTransactions$(hash: string, index: number = 0): Observable<Transaction[]> {
|
|
||||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + '/api/block/' + hash + '/txs/' + index);
|
|
||||||
}
|
|
||||||
|
|
||||||
getBlockHashFromHeight$(height: number): Observable<string> {
|
|
||||||
return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/block-height/' + height, {responseType: 'text'});
|
|
||||||
}
|
|
||||||
|
|
||||||
getAddress$(address: string): Observable<Address> {
|
|
||||||
return this.httpClient.get<Address>(this.apiBaseUrl + this.apiBasePath + '/api/address/' + address);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAddressTransactions$(address: string): Observable<Transaction[]> {
|
|
||||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + '/api/address/' + address + '/txs');
|
|
||||||
}
|
|
||||||
|
|
||||||
getAddressTransactionsFromHash$(address: string, txid: string): Observable<Transaction[]> {
|
|
||||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + '/api/address/' + address + '/txs/chain/' + txid);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAsset$(assetId: string): Observable<Asset> {
|
|
||||||
return this.httpClient.get<Asset>(this.apiBaseUrl + this.apiBasePath + '/api/asset/' + assetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAssetTransactions$(assetId: string): Observable<Transaction[]> {
|
|
||||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + '/api/asset/' + assetId + '/txs');
|
|
||||||
}
|
|
||||||
|
|
||||||
getAssetTransactionsFromHash$(assetId: string, txid: string): Observable<Transaction[]> {
|
|
||||||
return this.httpClient.get<Transaction[]>(this.apiBaseUrl + this.apiBasePath + '/api/asset/' + assetId + '/txs/chain/' + txid);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAddressesByPrefix$(prefix: string): Observable<string[]> {
|
|
||||||
if (prefix.toLowerCase().indexOf('bc1') === 0) {
|
|
||||||
prefix = prefix.toLowerCase();
|
|
||||||
}
|
|
||||||
return this.httpClient.get<string[]>(this.apiBaseUrl + this.apiBasePath + '/api/address-prefix/' + prefix);
|
|
||||||
}
|
|
||||||
}
|
|
@ -143,6 +143,10 @@ export class ApiService {
|
|||||||
return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
|
return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAltTransaction$(txId: string): Observable<Transaction> {
|
||||||
|
return this.httpClient.get<Transaction>(this.apiBaseUrl + this.apiBasePath + '/api/v1/tx/alt/' + txId);
|
||||||
|
}
|
||||||
|
|
||||||
listPools$(interval: string | undefined) : Observable<any> {
|
listPools$(interval: string | undefined) : Observable<any> {
|
||||||
return this.httpClient.get<any>(
|
return this.httpClient.get<any>(
|
||||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pools` +
|
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pools` +
|
||||||
|
@ -43,7 +43,7 @@ export interface Env {
|
|||||||
TESTNET_BLOCK_AUDIT_START_HEIGHT: number;
|
TESTNET_BLOCK_AUDIT_START_HEIGHT: number;
|
||||||
SIGNET_BLOCK_AUDIT_START_HEIGHT: number;
|
SIGNET_BLOCK_AUDIT_START_HEIGHT: number;
|
||||||
FULL_RBF_ENABLED: boolean;
|
FULL_RBF_ENABLED: boolean;
|
||||||
ALT_BACKEND_URL: string;
|
ALT_BACKEND_ENABLED: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultEnv: Env = {
|
const defaultEnv: Env = {
|
||||||
@ -73,7 +73,7 @@ const defaultEnv: Env = {
|
|||||||
'TESTNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
'TESTNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
||||||
'SIGNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
'SIGNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
||||||
'FULL_RBF_ENABLED': false,
|
'FULL_RBF_ENABLED': false,
|
||||||
'ALT_BACKEND_URL': '',
|
'ALT_BACKEND_ENABLED': false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user