Skip to content

Commit

Permalink
feat: browser compatible BlockfrostNetworkInfoProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
rhyslbw committed Nov 16, 2024
1 parent 59d6083 commit 26e1e94
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 153 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BlockfrostProvider } from '../../util/BlockfrostProvider/BlockfrostProvider';
import { BlockfrostToCore, blockfrostToProviderError } from '../../util';
import { BlockfrostClient } from '../blockfrost/BlockfrostClient';
import { BlockfrostProvider } from '../blockfrost/BlockfrostProvider';
import { BlockfrostToCore } from '../blockfrost';
import {
Cardano,
EraSummary,
Expand All @@ -9,55 +10,59 @@ import {
StakeSummary,
SupplySummary
} from '@cardano-sdk/core';
import { Logger } from 'ts-log';
import { Responses } from '@blockfrost/blockfrost-js';
import { Schemas } from '@blockfrost/blockfrost-js/lib/types/open-api';
import { handleError } from '@blockfrost/blockfrost-js/lib/utils/errors';

export class BlockfrostNetworkInfoProvider extends BlockfrostProvider implements NetworkInfoProvider {
constructor(client: BlockfrostClient, logger: Logger) {
super(client, logger);
}

public async stake(): Promise<StakeSummary> {
try {
const network = await this.blockfrost.network();
const { stake } = await this.request<Responses['network']>('network');
return {
active: BigInt(network.stake.active),
live: BigInt(network.stake.live)
active: BigInt(stake.active),
live: BigInt(stake.live)
};
} catch (error) {
throw blockfrostToProviderError(error);
throw this.toProviderError(error);
}
}

public async lovelaceSupply(): Promise<SupplySummary> {
try {
const { supply } = await this.blockfrost.network();
const { supply } = await this.request<Responses['network']>('network');
return {
circulating: BigInt(supply.circulating),
total: BigInt(supply.total)
};
} catch (error) {
throw blockfrostToProviderError(error);
throw this.toProviderError(error);
}
}

public async ledgerTip(): Promise<Cardano.Tip> {
try {
const block = await this.blockfrost.blocksLatest();
const block = await this.request<Responses['block_content']>('blocks/latest');
return BlockfrostToCore.blockToTip(block);
} catch (error) {
throw blockfrostToProviderError(error);
throw this.toProviderError(error);
}
}

public async protocolParameters(): Promise<Cardano.ProtocolParameters> {
try {
const response = await this.blockfrost.epochsLatestParameters();
const response = await this.request<Responses['epoch_param_content']>('epochs/latest/parameters');
return BlockfrostToCore.protocolParameters(response);
} catch (error) {
throw blockfrostToProviderError(error);
throw this.toProviderError(error);
}
}

public async genesisParameters(): Promise<Cardano.CompactGenesis> {
return this.blockfrost
.genesis()
return this.request<Responses['genesis_content']>('genesis')
.then((response) => ({
activeSlotsCoefficient: response.active_slots_coefficient,
epochLength: response.epoch_length,
Expand All @@ -75,18 +80,15 @@ export class BlockfrostNetworkInfoProvider extends BlockfrostProvider implements
updateQuorum: response.update_quorum
}))
.catch((error) => {
throw blockfrostToProviderError(error);
throw this.toProviderError(error);
});
}

protected async fetchEraSummaries(): Promise<Schemas['network-eras']> {
try {
// Although Blockfrost have the endpoint, the blockfrost-js library don't have a call for it
// https://github.com/blockfrost/blockfrost-js/issues/294
const response = await this.blockfrost.instance<Schemas['network-eras']>('network/eras');
return response.body;
return await this.request<Responses['network-eras']>('network/eras');
} catch (error) {
throw handleError(error);
throw this.toProviderError(error);
}
}

Expand All @@ -103,7 +105,7 @@ export class BlockfrostNetworkInfoProvider extends BlockfrostProvider implements
}
}));
} catch (error) {
throw handleError(error);
throw this.toProviderError(error);
}
}

Expand All @@ -113,7 +115,7 @@ export class BlockfrostNetworkInfoProvider extends BlockfrostProvider implements
const summaries = await this.fetchEraSummaries();
return this.parseEraSummaries(summaries, systemStart);
} catch (error) {
throw blockfrostToProviderError(error);
throw this.toProviderError(error);
}
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './BlockfrostNetworkInfoProvider';
export * from './networkInfoHttpProvider';
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-len */
import { BlockFrostAPI, Responses } from '@blockfrost/blockfrost-js';
import { BlockfrostNetworkInfoProvider } from '../../../src';
import { BlockfrostClient, BlockfrostNetworkInfoProvider } from '../../src';
import { Cardano, EraSummary, Milliseconds, StakeSummary, SupplySummary } from '@cardano-sdk/core';
import { Responses } from '@blockfrost/blockfrost-js';
import { logger } from '@cardano-sdk/util-dev';
import { mockResponses } from '../AssetInfoProvider/util';

jest.mock('@blockfrost/blockfrost-js');

Expand Down Expand Up @@ -38,74 +37,36 @@ const mockedNetworkResponse = {
}
} as Responses['network'];

const mockedError = {
error: 'Forbidden',
message: 'Invalid project token.',
status_code: 403,
url: 'test'
};
describe('BlockfrostNetworkInfoProvider', () => {
let request: jest.Mock;
let provider: BlockfrostNetworkInfoProvider;

const mockedErrorMethod = jest.fn().mockRejectedValue(mockedError);
// const mockedProviderError = new ProviderError(ProviderFailure.Unknown, {}, 'testing');
const apiKey = 'someapikey';
const apiUrl = 'http://testnet.endpoint';

describe('blockfrostNetworkInfoProvider', () => {
beforeEach(async () => {
mockedErrorMethod.mockClear();
request = jest.fn();
const client = { request } as unknown as BlockfrostClient;
provider = new BlockfrostNetworkInfoProvider(client, logger);
});
test('stake', async () => {
BlockFrostAPI.prototype.network = jest.fn().mockResolvedValue(mockedNetworkResponse);
BlockFrostAPI.prototype.apiUrl = apiUrl;

const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
const client = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
const response = await client.stake();
test('stake', async () => {
mockResponses(request, [['network', mockedNetworkResponse]]);
const response = await provider.stake();

expect(response).toMatchObject<StakeSummary>({
active: 1_060_378_314_781_343n,
live: 15_001_884_895_856_815n
});
});

test('stake throws', async () => {
BlockFrostAPI.prototype.network = mockedErrorMethod;

BlockFrostAPI.prototype.apiUrl = apiUrl;

const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });

await expect(() => provider.stake()).rejects.toThrow();
expect(mockedErrorMethod).toBeCalledTimes(1);
});

test('lovelaceSupply', async () => {
BlockFrostAPI.prototype.network = jest.fn().mockResolvedValue(mockedNetworkResponse);
BlockFrostAPI.prototype.apiUrl = apiUrl;

const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
const client = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
const response = await client.lovelaceSupply();
mockResponses(request, [['network', mockedNetworkResponse]]);
const response = await provider.lovelaceSupply();

expect(response).toMatchObject<SupplySummary>({
circulating: 42_064_399_450_423_723n,
total: 40_267_211_394_073_980n
});
});

test('lovelace throws', async () => {
BlockFrostAPI.prototype.network = mockedErrorMethod;

BlockFrostAPI.prototype.apiUrl = apiUrl;

const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });

await expect(() => provider.lovelaceSupply()).rejects.toThrow();
expect(mockedErrorMethod).toBeCalledTimes(1);
});

test('eraSummaries', async () => {
const genesis = {
activeSlotsCoefficient: 0.05,
Expand Down Expand Up @@ -286,30 +247,13 @@ describe('blockfrostNetworkInfoProvider', () => {
}
];

const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });

mockResponses(request, [['network/eras', blockfrostResponseBody]]);
// mockResponses(request, [['genesis', genesis]])
provider.genesisParameters = jest.fn().mockResolvedValue(genesis);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
provider.fetchEraSummaries = jest.fn().mockResolvedValue(blockfrostResponseBody);

const response = await provider.eraSummaries();

expect(response).toMatchObject<EraSummary[]>(expected);
});

test('eraSummaries throws', async () => {
BlockFrostAPI.prototype.apiUrl = apiUrl;

const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
provider.fetchEraSummaries = mockedErrorMethod;
await expect(() => provider.eraSummaries()).rejects.toThrow();
});

test('genesisParameters', async () => {
const mockedResponse = {
active_slots_coefficient: 0.05,
Expand All @@ -323,10 +267,7 @@ describe('blockfrostNetworkInfoProvider', () => {
system_start: 1_506_203_091,
update_quorum: 5
};
BlockFrostAPI.prototype.genesis = jest.fn().mockResolvedValue(mockedResponse);

const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
mockResponses(request, [['genesis', mockedResponse]]);
const response = await provider.genesisParameters();

expect(response).toMatchObject({
Expand All @@ -343,18 +284,6 @@ describe('blockfrostNetworkInfoProvider', () => {
} as Cardano.CompactGenesis);
});

test('genesisParameters throws', async () => {
BlockFrostAPI.prototype.genesis = mockedErrorMethod;

BlockFrostAPI.prototype.apiUrl = apiUrl;

const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });

await expect(() => provider.genesisParameters()).rejects.toThrow();
expect(mockedErrorMethod).toBeCalledTimes(1);
});

test('protocolParameters', async () => {
const mockedResponse = {
a0: 0.3,
Expand Down Expand Up @@ -383,12 +312,7 @@ describe('blockfrostNetworkInfoProvider', () => {
rho: 0.003,
tau: 0.2
};
BlockFrostAPI.prototype.epochsLatestParameters = jest.fn().mockResolvedValue(mockedResponse) as any;

const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
BlockFrostAPI.prototype.apiUrl = apiUrl;

const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
mockResponses(request, [['epochs/latest/parameters', mockedResponse]]);
const response = await provider.protocolParameters();

expect(response).toMatchObject({
Expand All @@ -405,23 +329,8 @@ describe('blockfrostNetworkInfoProvider', () => {
});
});

test('protocolParameters throws', async () => {
BlockFrostAPI.prototype.epochsLatestParameters = mockedErrorMethod;

BlockFrostAPI.prototype.apiUrl = apiUrl;

const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });

await expect(() => provider.protocolParameters()).rejects.toThrow();
expect(mockedErrorMethod).toBeCalledTimes(1);
});

test('ledgerTip', async () => {
BlockFrostAPI.prototype.blocksLatest = jest.fn().mockResolvedValue(blockResponse);

const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
mockResponses(request, [['blocks/latest', blockResponse]]);
const response = await provider.ledgerTip();

expect(response).toMatchObject({
Expand All @@ -430,16 +339,4 @@ describe('blockfrostNetworkInfoProvider', () => {
slot: 37_767_194
});
});

test('ledgerTip throws', async () => {
BlockFrostAPI.prototype.blocksLatest = mockedErrorMethod;

BlockFrostAPI.prototype.apiUrl = apiUrl;

const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });

await expect(() => provider.ledgerTip()).rejects.toThrow();
expect(mockedErrorMethod).toBeCalledTimes(1);
});
});

This file was deleted.

1 change: 0 additions & 1 deletion packages/cardano-services/src/NetworkInfo/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './BlockfrostNetworkInfoProvider';
export * from './DbSyncNetworkInfoProvider';
export * from './NetworkInfoHttpService';
12 changes: 8 additions & 4 deletions packages/cardano-services/src/Program/programs/providerServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import {
StakePoolProvider,
UtxoProvider
} from '@cardano-sdk/core';
import { BlockfrostAssetProvider, CardanoWsClient, TxSubmitApiProvider } from '@cardano-sdk/cardano-services-client';
import {
BlockfrostAssetProvider,
BlockfrostNetworkInfoProvider,
CardanoWsClient,
TxSubmitApiProvider
} from '@cardano-sdk/cardano-services-client';
import { Logger } from 'ts-log';
import { Observable } from 'rxjs';
import { OgmiosCardanoNode } from '@cardano-sdk/ogmios';
Expand All @@ -38,7 +43,6 @@ import {
ChainHistoryHttpService,
DbSyncChainHistoryProvider
} from '../../ChainHistory';
import { BlockfrostNetworkInfoProvider, DbSyncNetworkInfoProvider, NetworkInfoHttpService } from '../../NetworkInfo';
import { BlockfrostRewardsProvider, DbSyncRewardsProvider, RewardsHttpService } from '../../Rewards';
import { BlockfrostTxSubmitProvider, NodeTxSubmitProvider, TxSubmitHttpService } from '../../TxSubmit';
import { BlockfrostUtxoProvider, DbSyncUtxoProvider, UtxoHttpService } from '../../Utxo';
Expand All @@ -52,6 +56,7 @@ import {
suffixType2Cli
} from '../options';
import { DbPools, DbSyncEpochPollService, TypeormProvider, getBlockfrostApi, getBlockfrostClient } from '../../util';
import { DbSyncNetworkInfoProvider, NetworkInfoHttpService } from '../../NetworkInfo';
import {
DbSyncStakePoolProvider,
StakePoolHttpService,
Expand Down Expand Up @@ -308,8 +313,7 @@ const serviceMapFactory = (options: ServiceMapFactoryOptions) => {
ServiceNames.Utxo
);

const getBlockfrostNetworkInfoProvider = () =>
new BlockfrostNetworkInfoProvider({ blockfrost: getBlockfrostApi(), logger });
const getBlockfrostNetworkInfoProvider = () => new BlockfrostNetworkInfoProvider(getBlockfrostClient(), logger);

const getDbSyncNetworkInfoProvider = withDbSyncProvider((dbPools, cardanoNode) => {
if (args.useWebSocketApi) return getWebSocketClient().networkInfoProvider;
Expand Down
Loading

0 comments on commit 26e1e94

Please sign in to comment.