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 18 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
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
}
31 changes: 18 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,28 @@
"node": "^18.14.0",
"pnpm": "^8.8.0"
},
"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\"",
"docker:build": "docker build -t api3/airseekerv2:latest -f docker/Dockerfile .",
"docker:run": "docker run -it --rm api3/airseekerv2:latest",
"eslint:check": "eslint . --ext .js,.ts --max-warnings 0",
"eslint:fix": "eslint . --ext .js,.ts --fix",
"lint": "yarn eslint:fix",
aquarat marked this conversation as resolved.
Show resolved Hide resolved
"prepare": "husky install",
"prettier:check": "prettier --check \"./**/*.{js,ts,md,json}\"",
"prettier:fix": "prettier --write \"./**/*.{js,ts,md,json}\"",
"prettier:write": "yarn prettier:fix",
"test": "jest",
"tsc": "tsc --project ."
},
"files": [
"dist",
"*.js"
],
"devDependencies": {
"@api3/ois": "^2.2.0",
"@types/jest": "^29.5.5",
"@types/lodash": "^4.14.199",
"@types/node": "^20.8.0",
Expand All @@ -32,22 +49,10 @@
"dependencies": {
"@api3/airnode-protocol-v1": "^2.10.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 . --ext .js,.ts --max-warnings 0",
"eslint:fix": "eslint . --ext .js,.ts --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"
}
}
76 changes: 74 additions & 2 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[a-fA-F0-9]{40}$/, 'Must be

export const evmIdSchema = z.string().regex(/^0x[a-fA-F0-9]{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().optional().default(10),
aquarat marked this conversation as resolved.
Show resolved Hide resolved
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 = 1_000;
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export * from './signed-api-fetch';
aquarat marked this conversation as resolved.
Show resolved Hide resolved
export * from './signed-data-store';
export * from './utils';

// eslint-disable-next-line no-console
console.log('works');
29 changes: 29 additions & 0 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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[]) =>
// eslint-disable-next-line no-console
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 = '') => {
promiseResults
.filter((result) => result.status === 'rejected')
.forEach((rejectedPromise) => error(additionalText, rejectedPromise));
};

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

describe('data fetcher', () => {
const mockAxios = jest.fn();

beforeAll(() => {
setAxios(mockAxios);
aquarat marked this conversation as resolved.
Show resolved Hide resolved
});

beforeEach(() => {
mockAxios.mockReset();
aquarat marked this conversation as resolved.
Show resolved Hide resolved
localDataStore.clear();
});

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

const mockAxios = jest.fn();
setAxios(mockAxios);

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

mockAxios.mockImplementation(async () =>
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();

await stopDataFetcher();

expect(setStoreDataPointSpy).toHaveBeenCalledTimes(30);
aquarat marked this conversation as resolved.
Show resolved Hide resolved
});
});
Loading