Skip to content

Commit 78232f8

Browse files
committed
feat(cardano-services): improve transaction from blockfrost using cbor to reduce the nr of calls
1 parent 80074d6 commit 78232f8

File tree

9 files changed

+935
-608
lines changed

9 files changed

+935
-608
lines changed

packages/cardano-services/src/ChainHistory/BlockrostChainHistoryProvider/BlockfrostChainHistoryProvider.ts

Lines changed: 422 additions & 153 deletions
Large diffs are not rendered by default.

packages/cardano-services/src/NetworkInfo/BlockfrostNetworkInfoProvider/BlockfrostNetworkInfoProvider.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BlockfrostProvider } from '../../util/BlockfrostProvider/BlockfrostProvider';
2-
import { BlockfrostToCore, blockfrostToProviderError, networkMagicToIdMap } from '../../util';
2+
import { BlockfrostToCore, blockfrostToProviderError } from '../../util';
33
import {
44
Cardano,
55
EraSummary,
@@ -63,7 +63,10 @@ export class BlockfrostNetworkInfoProvider extends BlockfrostProvider implements
6363
epochLength: response.epoch_length,
6464
maxKesEvolutions: response.max_kes_evolutions,
6565
maxLovelaceSupply: BigInt(response.max_lovelace_supply),
66-
networkId: networkMagicToIdMap[response.network_magic],
66+
networkId:
67+
response.network_magic === Cardano.NetworkMagics.Mainnet
68+
? Cardano.NetworkId.Mainnet
69+
: Cardano.NetworkId.Testnet,
6770
networkMagic: response.network_magic,
6871
securityParameter: response.security_param,
6972
slotLength: Seconds(response.slot_length),
@@ -80,7 +83,7 @@ export class BlockfrostNetworkInfoProvider extends BlockfrostProvider implements
8083
try {
8184
// Although Blockfrost have the endpoint, the blockfrost-js library don't have a call for it
8285
// https://github.com/blockfrost/blockfrost-js/issues/294
83-
const response = await this.blockfrost.instance<Schemas['network-eras']>('network-eras');
86+
const response = await this.blockfrost.instance<Schemas['network-eras']>('network/eras');
8487
return response.body;
8588
} catch (error) {
8689
throw handleError(error);

packages/cardano-services/src/Program/programs/providerServer.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
CardanoNode,
77
ChainHistoryProvider,
88
HandleProvider,
9+
NetworkInfoProvider,
910
Provider,
1011
RewardsProvider,
1112
Seconds,
@@ -330,8 +331,17 @@ const serviceMapFactory = (options: ServiceMapFactoryOptions) => {
330331
});
331332
}, ServiceNames.NetworkInfo);
332333

333-
const getBlockfrostChainHistoryProvider = () =>
334-
new BlockfrostChainHistoryProvider({ blockfrost: getBlockfrostApi(), logger });
334+
let networkInfoProvider: NetworkInfoProvider;
335+
const getNetworkInfoProvider = () => {
336+
if (!networkInfoProvider)
337+
networkInfoProvider =
338+
args.networkInfoProvider === ProviderImplementation.BLOCKFROST
339+
? getBlockfrostNetworkInfoProvider()
340+
: getDbSyncNetworkInfoProvider();
341+
return networkInfoProvider;
342+
};
343+
const getBlockfrostChainHistoryProvider = (nInfoProvider: NetworkInfoProvider | DbSyncNetworkInfoProvider) =>
344+
new BlockfrostChainHistoryProvider({ blockfrost: getBlockfrostApi(), logger, networkInfoProvider: nInfoProvider });
335345

336346
const getBlockfrostRewardsProvider = () => new BlockfrostRewardsProvider({ blockfrost: getBlockfrostApi(), logger });
337347

@@ -395,7 +405,10 @@ const serviceMapFactory = (options: ServiceMapFactoryOptions) => {
395405
new ChainHistoryHttpService({
396406
chainHistoryProvider: selectProviderImplementation<ChainHistoryProvider>(
397407
args.chainHistoryProvider ?? ProviderImplementation.DBSYNC,
398-
{ blockfrost: getBlockfrostChainHistoryProvider, dbsync: getDbSyncChainHistoryProvider },
408+
{
409+
blockfrost: () => getBlockfrostChainHistoryProvider(getNetworkInfoProvider()),
410+
dbsync: getDbSyncChainHistoryProvider
411+
},
399412
logger,
400413
ServiceNames.ChainHistory
401414
),
@@ -412,16 +425,11 @@ const serviceMapFactory = (options: ServiceMapFactoryOptions) => {
412425
ServiceNames.Rewards
413426
)
414427
}),
415-
[ServiceNames.NetworkInfo]: async () => {
416-
const networkInfoProvider =
417-
args.networkInfoProvider === ProviderImplementation.BLOCKFROST
418-
? getBlockfrostNetworkInfoProvider()
419-
: getDbSyncNetworkInfoProvider();
420-
return new NetworkInfoHttpService({
428+
[ServiceNames.NetworkInfo]: async () =>
429+
new NetworkInfoHttpService({
421430
logger,
422-
networkInfoProvider
423-
});
424-
},
431+
networkInfoProvider: getNetworkInfoProvider()
432+
}),
425433
[ServiceNames.TxSubmit]: async () => {
426434
const txSubmitProvider = args.useSubmitApi
427435
? getSubmitApiProvider()

packages/cardano-services/src/Utxo/BlockfrostUtxoProvider/BlockfrostUtxoProvider.ts

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,56 @@
11
import { BlockfrostProvider } from '../../util/BlockfrostProvider/BlockfrostProvider';
2-
import { BlockfrostToCore, BlockfrostUtxo, blockfrostToProviderError, fetchByAddressSequentially } from '../../util';
3-
import { Cardano, UtxoByAddressesArgs, UtxoProvider } from '@cardano-sdk/core';
2+
import { BlockfrostToCore, blockfrostToProviderError, fetchByAddressSequentially } from '../../util';
3+
import { Cardano, Serialization, UtxoByAddressesArgs, UtxoProvider } from '@cardano-sdk/core';
4+
import { PaginationOptions } from '@blockfrost/blockfrost-js/lib/types';
45
import { Responses } from '@blockfrost/blockfrost-js';
6+
import { Schemas } from '@blockfrost/blockfrost-js/lib/types/open-api';
57

68
export class BlockfrostUtxoProvider extends BlockfrostProvider implements UtxoProvider {
9+
protected async fetchUtxos(addr: Cardano.PaymentAddress, pagination: PaginationOptions): Promise<Cardano.Utxo[]> {
10+
const utxos: Responses['address_utxo_content'] = (await this.blockfrost.addressesUtxos(
11+
addr.toString(),
12+
pagination
13+
)) as Responses['address_utxo_content'];
14+
15+
const utxoPromises = utxos.map((utxo) =>
16+
this.fetchDetailsFromCBOR(utxo.tx_hash).then((tx) => {
17+
const txOut = tx ? tx.body.outputs.find((output) => output.address === utxo.address) : undefined;
18+
return BlockfrostToCore.addressUtxoContent(addr.toString(), utxo, txOut);
19+
})
20+
);
21+
return Promise.all(utxoPromises);
22+
}
23+
24+
async fetchCBOR(hash: string): Promise<string> {
25+
return this.blockfrost
26+
.instance<Schemas['script_cbor']>(`txs/${hash}/cbor`)
27+
.then((response) => {
28+
if (response.body.cbor) return response.body.cbor;
29+
throw new Error('CBOR is null');
30+
})
31+
.catch((_error) => {
32+
throw new Error('CBOR fetch failed');
33+
});
34+
}
35+
protected async fetchDetailsFromCBOR(hash: string) {
36+
return this.fetchCBOR(hash)
37+
.then((cbor) => {
38+
const tx = Serialization.Transaction.fromCbor(Serialization.TxCBOR(cbor)).toCore();
39+
this.logger.info('Fetched details from CBOR for tx', hash);
40+
return tx;
41+
})
42+
.catch((error) => {
43+
this.logger.warn('Failed to fetch details from CBOR for tx', hash, error);
44+
return null;
45+
});
46+
}
747
public async utxoByAddresses({ addresses }: UtxoByAddressesArgs): Promise<Cardano.Utxo[]> {
848
try {
949
const utxoResults = await Promise.all(
1050
addresses.map(async (address) =>
11-
fetchByAddressSequentially<Cardano.Utxo, BlockfrostUtxo>({
51+
fetchByAddressSequentially<Cardano.Utxo, Cardano.Utxo>({
1252
address,
13-
request: (addr: Cardano.PaymentAddress, pagination) =>
14-
this.blockfrost.addressesUtxos(addr.toString(), pagination),
15-
responseTranslator: (addr: Cardano.PaymentAddress, response: Responses['address_utxo_content']) =>
16-
BlockfrostToCore.addressUtxoContent(addr.toString(), response)
53+
request: async (addr: Cardano.PaymentAddress, pagination) => await this.fetchUtxos(addr, pagination)
1754
})
1855
)
1956
);

packages/cardano-services/src/util/BlockfrostProvider/BlockfrostClientFactory.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export const getBlockfrostApi = () => {
1414
// custom hosted instance
1515
if (process.env.BLOCKFROST_CUSTOM_BACKEND_URL && process.env.BLOCKFROST_CUSTOM_BACKEND_URL !== '') {
1616
blockfrostApi = new BlockFrostAPI({
17-
customBackend: process.env.BLOCKFROST_CUSTOM_BACKEND_URL
17+
customBackend: process.env.BLOCKFROST_CUSTOM_BACKEND_URL,
18+
rateLimiter: false
1819
});
1920

2021
return blockfrostApi;
@@ -29,7 +30,8 @@ export const getBlockfrostApi = () => {
2930
// network is not mandatory, we keep it for safety.
3031
blockfrostApi = new BlockFrostAPI({
3132
network: process.env.NETWORK as AvailableNetworks,
32-
projectId: process.env.BLOCKFROST_API_KEY
33+
projectId: process.env.BLOCKFROST_API_KEY,
34+
rateLimiter: false
3335
});
3436

3537
return blockfrostApi;

packages/cardano-services/src/util/BlockfrostProvider/BlockfrostProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { HealthCheckResponse, Provider, ProviderDependencies } from '@cardano-sd
33
import { blockfrostToProviderError } from './blockfrostUtil';
44
import type { Logger } from 'ts-log';
55

6-
/** Properties that are need to create a BlockfrostProvider */
6+
/** Properties needed to create a BlockfrostProvider */
77
export interface BlockfrostProviderDependencies extends ProviderDependencies {
88
blockfrost: BlockFrostAPI;
99
logger: Logger;

packages/cardano-services/src/util/BlockfrostProvider/BlockfrostToCore.ts

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { Cardano } from '@cardano-sdk/core';
1+
import { Cardano, Serialization } from '@cardano-sdk/core';
2+
import { Hash32ByteBase16 } from '@cardano-sdk/crypto';
3+
import { HexBlob } from '@cardano-sdk/util';
24
import { Responses } from '@blockfrost/blockfrost-js';
35

46
type Unpacked<T> = T extends (infer U)[] ? U : T;
@@ -11,11 +13,15 @@ export type BlockfrostTransactionContent = Unpacked<Responses['address_transacti
1113
export type BlockfrostUtxo = Unpacked<BlockfrostAddressUtxoContent>;
1214

1315
export const BlockfrostToCore = {
14-
addressUtxoContent: (address: string, blockfrost: Responses['address_utxo_content']): Cardano.Utxo[] =>
15-
blockfrost.map((utxo) => [
16+
addressUtxoContent: (
17+
address: string,
18+
utxo: Responses['address_utxo_content'][0],
19+
txOutFromCbor?: Cardano.TxOut
20+
): Cardano.Utxo =>
21+
[
1622
BlockfrostToCore.hydratedTxIn(BlockfrostToCore.inputFromUtxo(address, utxo)),
17-
BlockfrostToCore.txOut(BlockfrostToCore.outputFromUtxo(address, utxo))
18-
]) as Cardano.Utxo[],
23+
BlockfrostToCore.txOut(BlockfrostToCore.outputFromUtxo(address, utxo), txOutFromCbor)
24+
] as Cardano.Utxo,
1925

2026
blockToTip: (block: Responses['block_content']): Cardano.Tip => ({
2127
blockNo: Cardano.BlockNo(block.height!),
@@ -92,30 +98,30 @@ export const BlockfrostToCore = {
9298
treasuryExpansion: blockfrost.tau.toString()
9399
}),
94100

95-
transactionUtxos: (utxoResponse: Responses['tx_content_utxo']) => ({
96-
collaterals: utxoResponse.inputs.filter((input) => input.collateral).map(BlockfrostToCore.hydratedTxIn),
97-
inputs: utxoResponse.inputs.filter((input) => !input.collateral).map(BlockfrostToCore.hydratedTxIn),
98-
outputs: utxoResponse.outputs.map(BlockfrostToCore.txOut)
99-
}),
100-
101-
txContentUtxo: (blockfrost: Responses['tx_content_utxo']) => ({
102-
hash: blockfrost.hash,
103-
inputs: BlockfrostToCore.inputs(blockfrost.inputs),
104-
outputs: BlockfrostToCore.outputs(blockfrost.outputs)
105-
}),
101+
txOut: (blockfrost: BlockfrostOutput, txOutFromCbor?: Cardano.TxOut): Cardano.TxOut => {
102+
const value: Cardano.Value = {
103+
coins: BigInt(blockfrost.amount.find(({ unit }) => unit === 'lovelace')!.quantity)
104+
};
106105

107-
txOut: (blockfrost: BlockfrostOutput): Cardano.TxOut => {
108106
const assets: Cardano.TokenMap = new Map();
109107
for (const { quantity, unit } of blockfrost.amount) {
110108
if (unit === 'lovelace') continue;
111109
assets.set(Cardano.AssetId(unit), BigInt(quantity));
112110
}
113-
return {
111+
112+
if (assets.size > 0) value.assets = assets;
113+
114+
const txOut: Cardano.TxOut = {
114115
address: Cardano.PaymentAddress(blockfrost.address),
115-
value: {
116-
assets,
117-
coins: BigInt(blockfrost.amount.find(({ unit }) => unit === 'lovelace')!.quantity)
118-
}
116+
value
119117
};
118+
119+
if (blockfrost.inline_datum)
120+
txOut.datum = Serialization.PlutusData.fromCbor(HexBlob(blockfrost.inline_datum)).toCore();
121+
if (blockfrost.data_hash) txOut.datumHash = Hash32ByteBase16(blockfrost.data_hash);
122+
123+
if (txOutFromCbor?.scriptReference) txOut.scriptReference = txOutFromCbor.scriptReference;
124+
125+
return txOut;
120126
}
121127
};

packages/cardano-services/src/util/BlockfrostProvider/blockfrostUtil.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const fetchSequentially = async <Item, Arg, Response>(
3939
request: (arg: Arg, pagination: PaginationOptions) => Promise<Response[]>;
4040
responseTranslator?: (response: Response[], arg: Arg) => Item[];
4141
/**
42-
* @returns true to indicatate that current result set should be returned
42+
* @returns true to indicate that current result set should be returned
4343
*/
4444
haveEnoughItems?: (items: Item[]) => boolean;
4545
paginationOptions?: PaginationOptions;
@@ -88,7 +88,7 @@ export const fetchByAddressSequentially = async <Item, Response>(props: {
8888
request: (address: Cardano.PaymentAddress, pagination: PaginationOptions) => Promise<Response[]>;
8989
responseTranslator?: (address: Cardano.PaymentAddress, response: Response[]) => Item[];
9090
/**
91-
* @returns true to indicatate that current result set should be returned
91+
* @returns true to indicate that current result set should be returned
9292
*/
9393
haveEnoughItems?: (items: Item[]) => boolean;
9494
paginationOptions?: PaginationOptions;
@@ -103,11 +103,6 @@ export const fetchByAddressSequentially = async <Item, Response>(props: {
103103
: undefined
104104
});
105105

106-
export const networkMagicToIdMap: { [key in number]: Cardano.NetworkId } = {
107-
[Cardano.NetworkMagics.Mainnet]: Cardano.NetworkId.Mainnet,
108-
[Cardano.NetworkMagics.Preprod]: Cardano.NetworkId.Testnet
109-
};
110-
111106
// copied from util-dev
112107
export const testnetEraSummaries: EraSummary[] = [
113108
{

0 commit comments

Comments
 (0)