Skip to content

Commit 5e2d001

Browse files
committed
feat(cardano-services): implement BlockfrostNetworkInfoProvider.eraSummaries
1 parent 7ce4bc4 commit 5e2d001

File tree

4 files changed

+248
-37
lines changed

4 files changed

+248
-37
lines changed

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

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import { BlockfrostProvider } from '../../util/BlockfrostProvider/BlockfrostProvider';
2-
import { BlockfrostToCore, blockfrostToProviderError, eraSummaries, networkMagicToIdMap } from '../../util';
3-
import { Cardano, EraSummary, NetworkInfoProvider, Seconds, StakeSummary, SupplySummary } from '@cardano-sdk/core';
2+
import { BlockfrostToCore, blockfrostToProviderError, networkMagicToIdMap } from '../../util';
3+
import {
4+
Cardano,
5+
EraSummary,
6+
Milliseconds,
7+
NetworkInfoProvider,
8+
Seconds,
9+
StakeSummary,
10+
SupplySummary
11+
} from '@cardano-sdk/core';
12+
import { Schemas } from '@blockfrost/blockfrost-js/lib/types/open-api';
13+
import { handleError } from '@blockfrost/blockfrost-js/lib/utils/errors';
414

515
export class BlockfrostNetworkInfoProvider extends BlockfrostProvider implements NetworkInfoProvider {
616
public async stake(): Promise<StakeSummary> {
@@ -66,12 +76,41 @@ export class BlockfrostNetworkInfoProvider extends BlockfrostProvider implements
6676
});
6777
}
6878

69-
public eraSummaries = (): Promise<EraSummary[]> => {
79+
protected async fetchEraSummaries(): Promise<Schemas['network-eras']> {
7080
try {
71-
// @todo implement actual era summaries
72-
return eraSummaries();
81+
// Although Blockfrost have the endpoint, the blockfrost-js library don't have a call for it
82+
// https://github.com/blockfrost/blockfrost-js/issues/294
83+
const response = await this.blockfrost.instance<Schemas['network-eras']>('network-eras');
84+
return response.body;
85+
} catch (error) {
86+
throw handleError(error);
87+
}
88+
}
89+
90+
protected async parseEraSummaries(summaries: Schemas['network-eras'], systemStart: Date): Promise<EraSummary[]> {
91+
try {
92+
return summaries.map((r) => ({
93+
parameters: {
94+
epochLength: r.parameters.epoch_length,
95+
slotLength: Milliseconds(r.parameters.slot_length * 1000)
96+
},
97+
start: {
98+
slot: r.start.slot,
99+
time: new Date(systemStart.getTime() + r.start.time * 1000)
100+
}
101+
}));
102+
} catch (error) {
103+
throw handleError(error);
104+
}
105+
}
106+
107+
public async eraSummaries(): Promise<EraSummary[]> {
108+
try {
109+
const { systemStart } = await this.genesisParameters();
110+
const summaries = await this.fetchEraSummaries();
111+
return this.parseEraSummaries(summaries, systemStart);
73112
} catch (error) {
74113
throw blockfrostToProviderError(error);
75114
}
76-
};
115+
}
77116
}

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

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { BlockFrostAPI, BlockfrostServerError } from '@blockfrost/blockfrost-js';
21
import { BlockfrostClientError, isBlockfrostErrorResponse } from '@blockfrost/blockfrost-js/lib/utils/errors';
2+
import { BlockfrostServerError } from '@blockfrost/blockfrost-js';
33
import {
44
Cardano,
55
EraSummary,
66
Milliseconds,
7-
Provider,
87
ProviderError,
98
ProviderFailure,
109
ProviderUtil,
@@ -120,21 +119,3 @@ export const testnetEraSummaries: EraSummary[] = [
120119
start: { slot: 1_598_400, time: new Date(1_595_967_616_000) }
121120
}
122121
];
123-
124-
export const eraSummaries = async () => testnetEraSummaries;
125-
126-
/**
127-
* Check health of the [Blockfrost service](https://docs.blockfrost.io/)
128-
*
129-
* @param {BlockFrostAPI} blockfrost BlockFrostAPI instance
130-
* @returns {HealthCheckResponse} HealthCheckResponse
131-
* @throws {ProviderError}
132-
*/
133-
export const healthCheck = async (blockfrost: BlockFrostAPI): ReturnType<Provider['healthCheck']> => {
134-
try {
135-
const result = await blockfrost.health();
136-
return { ok: result.is_healthy };
137-
} catch (error) {
138-
throw new ProviderError(ProviderFailure.Unknown, error);
139-
}
140-
};

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

Lines changed: 193 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
/* eslint-disable max-len */
33
import { BlockFrostAPI, Responses } from '@blockfrost/blockfrost-js';
44
import { BlockfrostNetworkInfoProvider } from '../../../src';
5-
import { Cardano, EraSummary, StakeSummary, SupplySummary } from '@cardano-sdk/core';
6-
import { logger, testnetEraSummaries } from '@cardano-sdk/util-dev';
5+
import { Cardano, EraSummary, Milliseconds, StakeSummary, SupplySummary } from '@cardano-sdk/core';
6+
import { logger } from '@cardano-sdk/util-dev';
77

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

@@ -106,24 +106,208 @@ describe('blockfrostNetworkInfoProvider', () => {
106106
expect(mockedErrorMethod).toBeCalledTimes(1);
107107
});
108108

109-
test.skip('eraSummaries', async () => {
109+
test('eraSummaries', async () => {
110+
const genesis = {
111+
activeSlotsCoefficient: 0.05,
112+
epochLength: 432_000,
113+
maxKesEvolutions: 62,
114+
maxLovelaceSupply: 45_000_000_000_000_000n,
115+
networkMagic: 1,
116+
securityParameter: 2160,
117+
slotLength: 1,
118+
slotsPerKesPeriod: 129_600,
119+
systemStart: new Date(1_654_041_600_000),
120+
updateQuorum: 5
121+
};
122+
const blockfrostResponseBody = [
123+
{
124+
end: {
125+
epoch: 4,
126+
slot: 86_400,
127+
time: 1_728_000
128+
},
129+
parameters: {
130+
epoch_length: 21_600,
131+
safe_zone: 4320,
132+
slot_length: 20
133+
},
134+
start: {
135+
epoch: 0,
136+
slot: 0,
137+
time: 0
138+
}
139+
},
140+
{
141+
end: {
142+
epoch: 5,
143+
slot: 518_400,
144+
time: 2_160_000
145+
},
146+
parameters: {
147+
epoch_length: 432_000,
148+
safe_zone: 129_600,
149+
slot_length: 1
150+
},
151+
start: {
152+
epoch: 4,
153+
slot: 86_400,
154+
time: 1_728_000
155+
}
156+
},
157+
{
158+
end: {
159+
epoch: 6,
160+
slot: 950_400,
161+
time: 2_592_000
162+
},
163+
parameters: {
164+
epoch_length: 432_000,
165+
safe_zone: 129_600,
166+
slot_length: 1
167+
},
168+
start: {
169+
epoch: 5,
170+
slot: 518_400,
171+
time: 2_160_000
172+
}
173+
},
174+
{
175+
end: {
176+
epoch: 7,
177+
slot: 1_382_400,
178+
time: 3_024_000
179+
},
180+
parameters: {
181+
epoch_length: 432_000,
182+
safe_zone: 129_600,
183+
slot_length: 1
184+
},
185+
start: {
186+
epoch: 6,
187+
slot: 950_400,
188+
time: 2_592_000
189+
}
190+
},
191+
{
192+
end: {
193+
epoch: 12,
194+
slot: 3_542_400,
195+
time: 5_184_000
196+
},
197+
parameters: {
198+
epoch_length: 432_000,
199+
safe_zone: 129_600,
200+
slot_length: 1
201+
},
202+
start: {
203+
epoch: 7,
204+
slot: 1_382_400,
205+
time: 3_024_000
206+
}
207+
},
208+
{
209+
end: {
210+
epoch: 163,
211+
slot: 68_774_400,
212+
time: 70_416_000
213+
},
214+
parameters: {
215+
epoch_length: 432_000,
216+
safe_zone: 129_600,
217+
slot_length: 1
218+
},
219+
start: {
220+
epoch: 12,
221+
slot: 3_542_400,
222+
time: 5_184_000
223+
}
224+
}
225+
];
226+
const expected: EraSummary[] = [
227+
{
228+
parameters: {
229+
epochLength: 21_600,
230+
slotLength: Milliseconds(20_000)
231+
},
232+
start: {
233+
slot: 0,
234+
time: new Date('2022-06-01T00:00:00.000Z')
235+
}
236+
},
237+
{
238+
parameters: {
239+
epochLength: 432_000,
240+
slotLength: Milliseconds(1000)
241+
},
242+
start: {
243+
slot: 86_400,
244+
time: new Date('2022-06-21T00:00:00.000Z')
245+
}
246+
},
247+
{
248+
parameters: {
249+
epochLength: 432_000,
250+
slotLength: Milliseconds(1000)
251+
},
252+
start: {
253+
slot: 518_400,
254+
time: new Date('2022-06-26T00:00:00.000Z')
255+
}
256+
},
257+
{
258+
parameters: {
259+
epochLength: 432_000,
260+
slotLength: Milliseconds(1000)
261+
},
262+
start: {
263+
slot: 950_400,
264+
time: new Date('2022-07-01T00:00:00.000Z')
265+
}
266+
},
267+
{
268+
parameters: {
269+
epochLength: 432_000,
270+
slotLength: Milliseconds(1000)
271+
},
272+
start: {
273+
slot: 1_382_400,
274+
time: new Date('2022-07-06T00:00:00.000Z')
275+
}
276+
},
277+
{
278+
parameters: {
279+
epochLength: 432_000,
280+
slotLength: Milliseconds(1000)
281+
},
282+
start: {
283+
slot: 3_542_400,
284+
time: new Date('2022-07-31T00:00:00.000Z')
285+
}
286+
}
287+
];
288+
110289
const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
111290
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
291+
292+
provider.genesisParameters = jest.fn().mockResolvedValue(genesis);
293+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
294+
// @ts-ignore
295+
provider.fetchEraSummaries = jest.fn().mockResolvedValue(blockfrostResponseBody);
296+
112297
const response = await provider.eraSummaries();
113298

114-
expect(response).toMatchObject<EraSummary[]>(testnetEraSummaries);
299+
expect(response).toMatchObject<EraSummary[]>(expected);
115300
});
116301

117-
test.skip('eraSummaries throws', async () => {
118-
BlockFrostAPI.prototype.network = mockedErrorMethod;
119-
302+
test('eraSummaries throws', async () => {
120303
BlockFrostAPI.prototype.apiUrl = apiUrl;
121304

122305
const blockfrost = new BlockFrostAPI({ network: 'preprod', projectId: apiKey });
123306
const provider = new BlockfrostNetworkInfoProvider({ blockfrost, logger });
124-
307+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
308+
// @ts-ignore
309+
provider.fetchEraSummaries = mockedErrorMethod;
125310
await expect(() => provider.eraSummaries()).rejects.toThrow();
126-
expect(mockedErrorMethod).toBeCalledTimes(1);
127311
});
128312

129313
test('genesisParameters', async () => {
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import * as dotenv from 'dotenv';
2+
import * as envalid from 'envalid';
3+
import { Pool } from 'pg';
24

35
dotenv.config();
46

5-
// eslint-disable-next-line @typescript-eslint/no-empty-function
6-
const setup = async () => {};
7+
const setup = async () => {
8+
const env = envalid.cleanEnv(process.env, { DB_SYNC_CONNECTION_STRING: envalid.str() });
9+
const db = new Pool({ connectionString: env.DB_SYNC_CONNECTION_STRING });
10+
const result = await db.query('SELECT view FROM pool_hash');
11+
process.env.POOLS = JSON.stringify(result.rows.map(({ view }) => view));
12+
await db.end();
13+
};
714

815
export default setup;

0 commit comments

Comments
 (0)