Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch data from signed APIs #29

Merged
merged 33 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
523bf33
Initial data fetcher and signed data store implementation
aquarat Oct 4, 2023
3a1a7f9
Add config->dataFetcher call prep
aquarat Oct 4, 2023
85c166d
Continue data fetcher implementation, test against actual signed data…
aquarat Oct 5, 2023
4d49e91
Add logger stub, modify config transform
aquarat Oct 5, 2023
9e1db51
Refactor
aquarat Oct 9, 2023
c580de8
Allow config to change periodically
aquarat Oct 9, 2023
ab92498
Add local datastore tests
aquarat Oct 9, 2023
e97ecea
Continue building data-fetcher tests
aquarat Oct 9, 2023
c4564f1
Rewrite without staggered calls, implement signature check
aquarat Oct 11, 2023
7a6cd4a
Add tests and benchmark
aquarat Oct 11, 2023
f6d7d0b
Merge in main, resolve conflicts
aquarat Oct 11, 2023
25fdca2
Fix build
aquarat Oct 11, 2023
dae75d3
Add state stub
aquarat Oct 11, 2023
c434687
Refactor
aquarat Oct 11, 2023
16620bf
Add additional signed data URL for better testing
aquarat Oct 12, 2023
323de22
Fix test
aquarat Oct 12, 2023
b21a1cd
Minor schema switch
aquarat Oct 12, 2023
8f570f4
PR comments
aquarat Oct 16, 2023
dd9ea25
Trim package.json script commands and do sbhallow state reads
aquarat Oct 17, 2023
ef0a51e
Removed unnecessary exports
aquarat Oct 17, 2023
efca23d
Update src/signed-data-store/signed-data-store.ts
aquarat Oct 17, 2023
e4bfdff
PR comments
aquarat Oct 17, 2023
1276b19
Merge branch '6-fetch-data-from-signed-api-all-at-once' of github.com…
aquarat Oct 17, 2023
d686e15
Fix test
aquarat Oct 17, 2023
3e1ed3e
Refactor local data store based on PR comments
aquarat Oct 17, 2023
34c3b98
Modify tests to mock axios directly
aquarat Oct 17, 2023
0b024aa
merge in main, resolve conflicts
aquarat Oct 17, 2023
d980e6d
Apply eslint rules
aquarat Oct 17, 2023
4d38c20
Fix tests temporarily
aquarat Oct 17, 2023
dbc1a6c
PR comments
aquarat Oct 18, 2023
a064275
PR comments
aquarat Oct 18, 2023
6731212
More PR related fixes
aquarat Oct 18, 2023
4fc3148
Fix test
aquarat Oct 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ coverage
.DS_Store
airseeker.json
secrets.env
.eslintcache
.eslintcache
1 change: 1 addition & 0 deletions config/airseeker.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@
}
}
},
"fetchInterval": 10000,
"deviationThresholdCoefficient": 1
}
30 changes: 17 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,23 @@
"dist",
"*.js"
],
"scripts": {
"build": "tsc --project tsconfig.build.json",
"clean": "rm -rf coverage dist",
"dev": "nodemon --ext ts,js,json,env --exec \"pnpm ts-node src/index.ts\"",
"eslint:check": "eslint --report-unused-disable-directives --cache --ext js,ts . --max-warnings 0",
"eslint:fix": "pnpm run eslint:check --fix",
"prettier:check": "prettier --check \"./**/*.{js,ts,md,json}\"",
"prettier:fix": "prettier --write \"./**/*.{js,ts,md,json}\"",
"test": "jest --verbose --runInBand --bail --detectOpenHandles --silent",
"test:e2e": "jest --selectProjects e2e --runInBand",
"tsc": "tsc --project .",
"docker:build": "docker build -t api3/airseekerv2:latest -f docker/Dockerfile .",
"docker:run": "docker run -it --rm api3/airseekerv2:latest",
"dev:eth-node": "hardhat node"
},
"devDependencies": {
"@api3/ois": "^2.2.0",
"@types/jest": "^29.5.5",
"@types/lodash": "^4.14.199",
"@types/node": "^20.8.0",
Expand All @@ -33,22 +49,10 @@
"@api3/airnode-protocol-v1": "^2.10.0",
"@api3/commons": "^0.2.0",
"@api3/promise-utils": "^0.4.0",
"axios": "^1.5.1",
"dotenv": "^16.3.1",
"ethers": "^5.7.2",
"lodash": "^4.17.21",
"zod": "^3.22.2"
},
"scripts": {
"build": "tsc --project tsconfig.build.json",
"clean": "rm -rf coverage dist",
"dev": "nodemon --ext ts,js,json,env --exec \"pnpm ts-node src/index.ts\"",
"eslint:check": "eslint --report-unused-disable-directives --cache --ext js,ts . --max-warnings 0",
"eslint:fix": "pnpm run eslint:check --fix",
"prettier:check": "prettier --check \"./**/*.{js,ts,md,json}\"",
"prettier:fix": "prettier --write \"./**/*.{js,ts,md,json}\"",
"test": "jest",
"tsc": "tsc --project .",
"docker:build": "docker build -t api3/airseekerv2:latest -f docker/Dockerfile .",
"docker:run": "docker run -it --rm api3/airseekerv2:latest"
}
}
70 changes: 67 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ export const evmAddressSchema = z.string().regex(/^0x[\dA-Fa-f]{40}$/, 'Must be

export const evmIdSchema = z.string().regex(/^0x[\dA-Fa-f]{64}$/, 'Must be a valid EVM hash');

export type EvmAddress = z.infer<typeof evmAddressSchema>;
export type EvmId = z.infer<typeof evmIdSchema>;

export const providerSchema = z
.object({
url: z.string().url(),
Expand Down Expand Up @@ -98,6 +101,7 @@ export const configSchema = z
.object({
sponsorWalletMnemonic: z.string().refine((mnemonic) => ethers.utils.isValidMnemonic(mnemonic), 'Invalid mnemonic'),
chains: chainsSchema,
fetchInterval: z.number().positive(),
deviationThresholdCoefficient: z.number(),
})
.strict();
Expand Down
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const HTTP_SIGNED_DATA_API_ATTEMPT_TIMEOUT = 10_000;
export const HTTP_SIGNED_DATA_API_HEADROOM = 1000;
27 changes: 27 additions & 0 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const debug = (...args: any[]) =>
// eslint-disable-next-line no-console
console.debug(...args);
const error = (...args: any[]) =>
// eslint-disable-next-line no-console
console.error(...args);
const info = (...args: any[]) => console.info(...args);
const log = (...args: any[]) =>
// eslint-disable-next-line no-console
console.log(...args);
const warn = (...args: any[]) =>
// eslint-disable-next-line no-console
console.warn(...args);

export const logErrors = (promiseResults: PromiseSettledResult<any>[], additionalText = '') => {
for (const rejectedPromise of promiseResults.filter((result) => result.status === 'rejected')) {
error(additionalText, rejectedPromise);
}
};

export const logger = {
debug,
error,
info,
log,
warn,
};
64 changes: 64 additions & 0 deletions src/signed-api-fetch/data-fetcher.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import axios from 'axios';
import { runDataFetcher, stopDataFetcher } from './data-fetcher';
import * as localDataStore from '../signed-data-store';
import { init } from '../../test/fixtures/mock-config';

const mockedAxios = axios as jest.MockedFunction<typeof axios>;
jest.mock('axios');

describe('data fetcher', () => {
// eslint-disable-next-line jest/no-hooks
beforeEach(() => {
localDataStore.clear();
});

it('retrieves signed data from urls', async () => {
init();

const setStoreDataPointSpy = jest.spyOn(localDataStore, 'setStoreDataPoint');

mockedAxios.mockResolvedValue(
Promise.resolve({
status: 200,
data: {
count: 3,
data: {
'0x91be0acf2d58a15c7cf687edabe4e255fdb27fbb77eba2a52f3bb3b46c99ec04': {
signature:
'0x0fe25ad7debe4d018aa53acfe56d84f35c8bedf58574611f5569a8d4415e342311c093bfe0648d54e0a02f13987ac4b033b24220880638df9103a60d4f74090b1c',
timestamp: '1687850583',
templateId: '0x154c34adf151cf4d91b7abe7eb6dcd193104ef2a29738ddc88020a58d6cf6183',
encodedValue: '0x000000000000000000000000000000000000000000000065954b143faff77440',
airnode: '0xC04575A2773Da9Cd23853A69694e02111b2c4182',
},
'0xddc6ca9cc6f5768d9bfa8cc59f79bde8cf97a6521d0b95835255951ce06f19e6': {
signature:
'0x1f8993bae330ff73f050aeb8221207f80d22c43174e56079663d520fd2ccaec52b87f56d2fb2184f99d0c37dabd78cf7ff4f2cd27f7fd337d06ebfe590e09a7d1c',
timestamp: '1687850583',
templateId: '0x55d08a477d28519c8bc889b0be4f4d08625cfec5369f047258a1a4d7e1e405f3',
encodedValue: '0x00000000000000000000000000000000000000000000066e419d6bdc61e19680',
airnode: '0xC04575A2773Da9Cd23853A69694e02111b2c4182',
},
'0x5dd8d9e1429f69ba4bd76df5709155110429857d19670cc157632f66a48ee1f7': {
signature:
'0x48c9c53645b5e69c986ab02fcae88ddd5247ce000bf1fddb2cd83ac6af8553e554164d3f6d5906fa8d24ce9224484a2664a70bb75893e9cf18bcffadee4345bc1c',
timestamp: '1687850583',
templateId: '0x96504241fb9ae9a5941f97c9561dcfcd7cee77ee9486a58c8e78551c1268ddec',
encodedValue: '0x0000000000000000000000000000000000000000000000000e461510ad9d8678',
airnode: '0xC04575A2773Da9Cd23853A69694e02111b2c4182',
},
},
},
})
);

const dataFetcherPromise = runDataFetcher();

await expect(dataFetcherPromise).resolves.toBeDefined();

stopDataFetcher();

expect(mockedAxios).toHaveBeenCalledTimes(2);
expect(setStoreDataPointSpy).toHaveBeenCalledTimes(6);
});
});
Loading