Skip to content

Commit

Permalink
Merge pull request #231 from utxostack/221-mempoolspace-api-returns-a…
Browse files Browse the repository at this point in the history
…n-incorrectdifferent-valueorder

feat(bitcoin): add configurable default data provider per method when use mempool
  • Loading branch information
ahonn authored Jan 17, 2025
2 parents 3ef9b41 + 56b08e6 commit 7ae028b
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 5 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "btc-assets-api",
"version": "2.5.6",
"version": "2.5.7",
"title": "Bitcoin/RGB++ Assets API",
"description": "",
"main": "index.js",
Expand Down
9 changes: 9 additions & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,15 @@ const envSchema = z
* used for fallback when the mempool.space API is not available.
*/
BITCOIN_ELECTRS_API_URL: z.string().optional(),
/**
* The IBitcoinClient methods to use Electrs as default data provider
* use electrs as default, mempool.space as fallback
*/
BITCOIN_METHODS_USE_ELECTRS_BY_DEFAULT: z
.string()
.default('')
.transform((value) => value.split(','))
.pipe(z.string().array()),
/**
* Bitcoin data provider, support mempool and electrs
* use mempool.space as default, electrs as fallback
Expand Down
20 changes: 16 additions & 4 deletions src/services/bitcoin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,20 +102,32 @@ export default class BitcoinClient implements IBitcoinClient {
method: K,
...args: MethodParameters<IBitcoinDataProvider, K>
): Promise<MethodReturnType<IBitcoinDataProvider, K>> {
const dataSource = { source: this.source, fallback: this.fallback };

const { env } = this.cradle;
if (env.BITCOIN_DATA_PROVIDER === 'mempool' && env.BITCOIN_METHODS_USE_ELECTRS_BY_DEFAULT.includes(method)) {
if (this.fallback) {
dataSource.source = this.fallback;
dataSource.fallback = this.source;
} else {
this.cradle.logger.warn('No fallback provider, skip using Electrs as default');
}
}

try {
this.cradle.logger.debug(`Calling ${method} with args: ${JSON.stringify(args)}`);
const result = await (this.source[method] as Function).apply(this.source, args);
const result = await (dataSource.source[method] as Function).apply(this.source, args);
return result as MethodReturnType<IBitcoinDataProvider, K>;
} catch (err) {
let calledError = err;
this.cradle.logger.error(err);
Sentry.captureException(err);
if (this.fallback) {
if (dataSource.fallback) {
this.cradle.logger.warn(
`Fallback to ${this.fallback.constructor.name} due to error: ${(err as Error).message}`,
`Fallback to ${dataSource.fallback.constructor.name} due to error: ${(err as Error).message}`,
);
try {
const result = await (this.fallback[method] as Function).apply(this.fallback, args);
const result = await (dataSource.fallback[method] as Function).apply(this.fallback, args);
return result as MethodReturnType<IBitcoinDataProvider, K>;
} catch (fallbackError) {
this.cradle.logger.error(fallbackError);
Expand Down
17 changes: 17 additions & 0 deletions test/services/bitcoin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ describe('BitcoinClient', () => {
}
});

test('BitcoinClient: Should be use Electrs as default data provider for methods', async () => {
const cradle = container.cradle;
if (cradle.env.BITCOIN_DATA_PROVIDER === 'mempool') {
cradle.env.BITCOIN_METHODS_USE_ELECTRS_BY_DEFAULT = ['getAddressTxs'];
bitcoin = new BitcoinClient(cradle);
expect(bitcoin['source'].constructor).toBe(MempoolClient);
expect(bitcoin['fallback']?.constructor).toBe(ElectrsClient);

// @ts-expect-error just for test, so we don't need to check the return value
const mempoolFn = vi.spyOn(bitcoin['source']!, 'getAddressTxs').mockResolvedValue([{}]);
const electrsFn = vi.spyOn(bitcoin['fallback']!, 'getAddressTxs').mockResolvedValue([]);
expect(await bitcoin.getAddressTxs({ address: 'test' })).toEqual([]);
expect(mempoolFn).not.toHaveBeenCalled();
expect(electrsFn).toHaveBeenCalled();
}
});

test('BitcoinClient: throw BitcoinClientError when source provider failed', async () => {
bitcoin['fallback'] = undefined;
vi.spyOn(bitcoin['source'], 'postTx').mockRejectedValue(new AxiosError('source provider error'));
Expand Down

0 comments on commit 7ae028b

Please sign in to comment.