Skip to content

Commit 7cda2c4

Browse files
authored
Update commons library (#244)
* Update commons * Use the released commons version
1 parent 2129d8f commit 7cda2c4

16 files changed

+73
-209
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"keywords": [],
2525
"license": "MIT",
2626
"devDependencies": {
27-
"@api3/commons": "^0.6.2",
27+
"@api3/commons": "^0.7.0",
2828
"@types/jest": "^29.5.12",
2929
"@types/node": "^20.11.19",
3030
"@typescript-eslint/eslint-plugin": "^7.0.1",

packages/airnode-feed/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"@api3/airnode-adapter": "^0.14.0",
3232
"@api3/airnode-node": "^0.14.0",
3333
"@api3/airnode-validator": "^0.14.0",
34-
"@api3/commons": "^0.6.2",
34+
"@api3/commons": "^0.7.0",
3535
"@api3/ois": "^2.3.2",
3636
"@api3/promise-utils": "^0.4.0",
3737
"axios": "^1.6.7",

packages/airnode-feed/src/heartbeat/heartbeat-utils.test.ts

-18
This file was deleted.

packages/airnode-feed/src/heartbeat/heartbeat-utils.ts

+2-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createSha256Hash, serializePlainObject } from '@api3/commons';
12
import { ethers } from 'ethers';
23

34
import { logger } from '../logger';
@@ -16,19 +17,11 @@ export interface HeartbeatPayload {
1617
signature: string;
1718
}
1819

19-
// We need to make sure the object is stringified in the same way every time, so we sort the keys alphabetically.
20-
export const stringifyUnsignedHeartbeatPayload = (unsignedHeartbeatPayload: Omit<HeartbeatPayload, 'signature'>) =>
21-
JSON.stringify(unsignedHeartbeatPayload, Object.keys(unsignedHeartbeatPayload).sort());
22-
2320
export const signHeartbeat = async (
2421
airnodeWallet: ethers.Wallet,
2522
unsignedHeartbeatPayload: Omit<HeartbeatPayload, 'signature'>
2623
) => {
2724
logger.debug('Signing heartbeat payload.');
28-
const messageToSign = ethers.utils.arrayify(
29-
createConfigHash(stringifyUnsignedHeartbeatPayload(unsignedHeartbeatPayload))
30-
);
25+
const messageToSign = ethers.utils.arrayify(createSha256Hash(serializePlainObject(unsignedHeartbeatPayload)));
3126
return airnodeWallet.signMessage(messageToSign);
3227
};
33-
34-
export const createConfigHash = (value: string) => ethers.utils.keccak256(ethers.utils.toUtf8Bytes(value));

packages/airnode-feed/src/heartbeat/heartbeat.test.ts

+22-14
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { readFileSync } from 'node:fs';
22
import { join } from 'node:path';
33

4+
import { createSha256Hash, serializePlainObject } from '@api3/commons';
45
import * as promiseUtilsModule from '@api3/promise-utils';
6+
import { ethers } from 'ethers';
57

68
import packageJson from '../../package.json';
79
import { config, verifyHeartbeatLog } from '../../test/fixtures';
810
import * as stateModule from '../state';
911
import * as configModule from '../validation/config';
1012

13+
import { signHeartbeat } from './heartbeat-utils';
1114
import { heartbeatLogger } from './logger';
1215

1316
import { initiateHeartbeatLoop, logHeartbeat } from '.';
@@ -46,29 +49,34 @@ describe(logHeartbeat.name, () => {
4649
});
4750

4851
describe(verifyHeartbeatLog.name, () => {
49-
it('heartbeat payload can be parsed from JSON log', () => {
52+
it('heartbeat payload can be parsed from JSON log', async () => {
53+
const rawConfig = JSON.parse(readFileSync(join(__dirname, '../../config/airnode-feed.example.json'), 'utf8'));
54+
const serializedConfig = serializePlainObject(rawConfig);
55+
const configHash = createSha256Hash(serializedConfig);
56+
const unsignedPayload = {
57+
airnode: '0xbF3137b0a7574563a23a8fC8badC6537F98197CC',
58+
configHash,
59+
currentTimestamp: '1674172803',
60+
deploymentTimestamp: '1674172800',
61+
nodeVersion: '0.1.0',
62+
stage: 'test',
63+
};
64+
const signature = await signHeartbeat(
65+
ethers.Wallet.fromMnemonic('diamond result history offer forest diagram crop armed stumble orchard stage glance'),
66+
unsignedPayload
67+
);
5068
const jsonLog = {
5169
context: {
52-
airnode: '0xbF3137b0a7574563a23a8fC8badC6537F98197CC',
53-
configHash: '0x0a36630da26fa987561ff8b692f2015a6fe632bdabcf3dcdd010ccc8262f4a3a',
54-
currentTimestamp: '1674172803',
55-
deploymentTimestamp: '1674172800',
56-
nodeVersion: '0.1.0',
57-
signature:
58-
'0x15fb32178d3c6e30385e448b21a4b9086c715a11e8044513bf3b6a578643f7a327498b59cc3d9442fbd2f3b3b4991f94398727e54558ac24871e2df44d1664e11c',
59-
stage: 'test',
70+
...unsignedPayload,
71+
signature,
6072
},
6173
level: 'info',
6274
message: 'Sending heartbeat log',
6375
ms: '+0ms',
6476
timestamp: '2023-01-20T00:00:03.000Z',
6577
};
66-
// The config hash was taken from the example config at a certain version with all spaces removed.
67-
const rawConfig = JSON.parse(readFileSync(join(__dirname, '../../config/airnode-feed.example.json'), 'utf8'));
68-
rawConfig.nodeSettings.nodeVersion = '0.1.0';
69-
const stringifiedConfig = JSON.stringify(rawConfig);
7078

71-
expect(() => verifyHeartbeatLog(jsonLog.context, stringifiedConfig)).not.toThrow();
79+
expect(() => verifyHeartbeatLog(jsonLog.context, serializedConfig)).not.toThrow();
7280
});
7381
});
7482

packages/airnode-feed/src/heartbeat/heartbeat.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import { createSha256Hash } from '@api3/commons';
12
import { go } from '@api3/promise-utils';
23

34
import { logger } from '../logger';
45
import { getState } from '../state';
56
import { loadRawConfig } from '../validation/config';
67

7-
import { HEARTBEAT_LOG_MESSAGE, type HeartbeatPayload, createConfigHash, signHeartbeat } from './heartbeat-utils';
8+
import { HEARTBEAT_LOG_MESSAGE, type HeartbeatPayload, signHeartbeat } from './heartbeat-utils';
89
import { heartbeatLogger } from './logger';
910

1011
export const initiateHeartbeatLoop = () => {
@@ -19,7 +20,7 @@ export const logHeartbeat = async () => {
1920
logger.debug('Creating heartbeat log.');
2021

2122
const rawConfig = loadRawConfig(); // We want to log the raw config, not the one with interpolated secrets.
22-
const configHash = createConfigHash(JSON.stringify(rawConfig));
23+
const configHash = createSha256Hash(JSON.stringify(rawConfig));
2324
const {
2425
airnodeWallet,
2526
deploymentTimestamp,

packages/airnode-feed/src/validation/config.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
1-
import fs, { readFileSync } from 'node:fs';
21
import { join } from 'node:path';
32
import { cwd } from 'node:process';
43

4+
import {
5+
interpolateSecretsIntoConfig,
6+
loadConfig as loadRawConfigFromFilesystem,
7+
loadSecrets as loadRawSecretsFromFilesystem,
8+
} from '@api3/commons';
59
import { go } from '@api3/promise-utils';
6-
import dotenv from 'dotenv';
710

811
import { logger } from '../logger';
912

1013
import { configSchema } from './schema';
11-
import { interpolateSecrets, parseSecrets } from './utils';
1214

1315
// When Airnode feed is built, the "/dist" file contains "src" folder and "package.json" and the config is expected to
1416
// be located next to the "/dist" folder. When run in development, the config is expected to be located next to the
1517
// "src" folder (one less import level). We resolve the config by CWD as a workaround. Since the Airnode feed is
1618
// dockerized, this is hidden from the user.
1719
const getConfigPath = () => join(cwd(), './config');
1820

19-
export const loadRawConfig = () => JSON.parse(fs.readFileSync(join(getConfigPath(), 'airnode-feed.json'), 'utf8'));
21+
export const loadRawConfig = () => loadRawConfigFromFilesystem(join(getConfigPath(), 'airnode-feed.json'));
2022

2123
export const loadConfig = async () => {
2224
const goLoadConfig = await go(async () => {
23-
const rawSecrets = dotenv.parse(readFileSync(join(getConfigPath(), 'secrets.env'), 'utf8'));
2425
const rawConfig = loadRawConfig();
25-
const secrets = parseSecrets(rawSecrets);
26-
return configSchema.parseAsync(interpolateSecrets(rawConfig, secrets));
26+
const rawSecrets = loadRawSecretsFromFilesystem(join(getConfigPath(), 'secrets.env'));
27+
return configSchema.parseAsync(interpolateSecretsIntoConfig(rawConfig, rawSecrets));
2728
});
2829

2930
if (!goLoadConfig.success) {

packages/airnode-feed/src/validation/schema.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { readFileSync } from 'node:fs';
22
import { join } from 'node:path';
33

4+
import { interpolateSecretsIntoConfig } from '@api3/commons';
45
import dotenv from 'dotenv';
56
import { ZodError } from 'zod';
67

78
import { config } from '../../test/fixtures';
89

910
import { type Config, configSchema, signedApisSchema } from './schema';
10-
import { interpolateSecrets } from './utils';
1111

1212
test('validates example config', async () => {
1313
const exampleConfig = JSON.parse(readFileSync(join(__dirname, '../../config/airnode-feed.example.json'), 'utf8'));
@@ -24,9 +24,9 @@ test('validates example config', async () => {
2424
);
2525

2626
const exampleSecrets = dotenv.parse(readFileSync(join(__dirname, '../../config/secrets.example.env'), 'utf8'));
27-
await expect(configSchema.parseAsync(interpolateSecrets(exampleConfig, exampleSecrets))).resolves.toStrictEqual(
28-
expect.any(Object)
29-
);
27+
await expect(
28+
configSchema.parseAsync(interpolateSecretsIntoConfig(exampleConfig, exampleSecrets))
29+
).resolves.toStrictEqual(expect.any(Object));
3030
});
3131

3232
test('ensures nodeVersion matches Airnode feed version', async () => {

packages/airnode-feed/src/validation/utils.ts

-41
This file was deleted.

packages/airnode-feed/test/fixtures.ts

+4-9
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1+
import { createSha256Hash, serializePlainObject } from '@api3/commons';
12
import type { AxiosResponse } from 'axios';
23
import { ethers } from 'ethers';
34
import { omit } from 'lodash';
45

56
import packageJson from '../package.json';
6-
import {
7-
type HeartbeatPayload,
8-
createConfigHash,
9-
stringifyUnsignedHeartbeatPayload,
10-
} from '../src/heartbeat/heartbeat-utils';
7+
import type { HeartbeatPayload } from '../src/heartbeat/heartbeat-utils';
118
import type { SignedResponse, TemplateResponse } from '../src/sign-template-data';
129
import type { Config } from '../src/validation/schema';
1310

@@ -204,13 +201,11 @@ export const signedApiResponse: Partial<AxiosResponse> = {
204201
export const verifyHeartbeatLog = (heartbeatPayload: HeartbeatPayload, rawConfig: string) => {
205202
// Verify that the signature is valid.
206203
const unsignedHeartbeatPayload = omit(heartbeatPayload, 'signature');
207-
const messageToSign = ethers.utils.arrayify(
208-
createConfigHash(stringifyUnsignedHeartbeatPayload(unsignedHeartbeatPayload))
209-
);
204+
const messageToSign = ethers.utils.arrayify(createSha256Hash(serializePlainObject(unsignedHeartbeatPayload)));
210205
const expectedAirnodeAddress = ethers.utils.verifyMessage(messageToSign, heartbeatPayload.signature);
211206
if (expectedAirnodeAddress !== heartbeatPayload.airnode) throw new Error('Invalid signature');
212207

213208
// Verify that the config hash is valid.
214-
const expectedConfigHash = createConfigHash(rawConfig);
209+
const expectedConfigHash = createSha256Hash(rawConfig);
215210
if (expectedConfigHash !== heartbeatPayload.configHash) throw new Error('Invalid config hash');
216211
};

packages/e2e/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
},
2323
"license": "MIT",
2424
"dependencies": {
25-
"@api3/commons": "^0.6.2",
25+
"@api3/commons": "^0.7.0",
2626
"@api3/promise-utils": "^0.4.0",
2727
"axios": "^1.6.7",
2828
"ethers": "^5.7.2",

packages/signed-api/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"ts-node": "^10.9.2"
3434
},
3535
"dependencies": {
36-
"@api3/commons": "^0.6.2",
36+
"@api3/commons": "^0.7.0",
3737
"@api3/promise-utils": "^0.4.0",
3838
"@aws-sdk/client-s3": "^3.515.0",
3939
"dotenv": "^16.4.4",

packages/signed-api/src/config/config.test.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { readFileSync } from 'node:fs';
22
import { join } from 'node:path';
33

4+
import * as commonsModule from '@api3/commons';
45
import dotenv from 'dotenv';
56

67
import type { AllowedAirnode } from '../schema';
@@ -9,10 +10,10 @@ import * as configModule from './config';
910

1011
test('interpolates example config and secrets', async () => {
1112
jest
12-
.spyOn(configModule, 'loadRawConfigFromFilesystem')
13+
.spyOn(commonsModule, 'loadConfig')
1314
.mockReturnValue(JSON.parse(readFileSync(join(__dirname, '../../config/signed-api.example.json'), 'utf8')));
1415
jest
15-
.spyOn(configModule, 'loadRawSecretsFromFilesystem')
16+
.spyOn(commonsModule, 'loadSecrets')
1617
.mockReturnValue(dotenv.parse(readFileSync(join(__dirname, '../../config/secrets.example.env'), 'utf8')));
1718

1819
const config = await configModule.loadConfig();

packages/signed-api/src/config/config.ts

+8-14
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
import { readFileSync } from 'node:fs';
21
import { join } from 'node:path';
32
import { cwd } from 'node:process';
43

4+
import {
5+
interpolateSecretsIntoConfig,
6+
loadConfig as loadRawConfigFromFilesystem,
7+
loadSecrets as loadRawSecretsFromFilesystem,
8+
} from '@api3/commons';
59
import { go, goSync } from '@api3/promise-utils';
610
import { S3 } from '@aws-sdk/client-s3';
7-
import dotenv from 'dotenv';
811

912
import { loadEnv } from '../env';
1013
import { logger } from '../logger';
1114
import { type Config, configSchema } from '../schema';
1215

13-
import { interpolateSecrets, parseSecrets } from './secrets';
14-
1516
let config: Config | undefined;
1617

1718
export const getConfig = (): Config => {
@@ -26,18 +27,11 @@ export const getConfig = (): Config => {
2627
// this is hidden from the user.
2728
const getConfigPath = () => join(cwd(), './config');
2829

29-
export const loadRawConfigFromFilesystem = () =>
30-
JSON.parse(readFileSync(join(getConfigPath(), 'signed-api.json'), 'utf8'));
31-
32-
export const loadRawSecretsFromFilesystem = () =>
33-
dotenv.parse(readFileSync(join(getConfigPath(), 'secrets.env'), 'utf8'));
34-
3530
export const loadConfigFromFilesystem = () => {
3631
const goLoadConfig = goSync(() => {
37-
const rawSecrets = loadRawSecretsFromFilesystem();
38-
const rawConfig = loadRawConfigFromFilesystem();
39-
const secrets = parseSecrets(rawSecrets);
40-
return interpolateSecrets(rawConfig, secrets);
32+
const rawConfig = loadRawConfigFromFilesystem(join(getConfigPath(), 'signed-api.json'));
33+
const rawSecrets = loadRawSecretsFromFilesystem(join(getConfigPath(), 'secrets.env'));
34+
return interpolateSecretsIntoConfig(rawConfig, rawSecrets);
4135
});
4236

4337
if (!goLoadConfig.success) {

0 commit comments

Comments
 (0)