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

Enhance processing and add caching #1025

Merged
merged 18 commits into from
May 5, 2022
Merged

Conversation

aquarat
Copy link
Contributor

@aquarat aquarat commented Apr 22, 2022

This PR extends #1008
and adds asynchronous pre and post processing, which enables some fun use cases, like OAuth2. It also adds a caching utility set, but the caching utilities are not currently used in Airnode. The caching is currently being used by my OAuth2 snippet and I anticipate that it will eventually be used by per-endpoint configurable per requestId API response caching (a requirement for RNG).

OAuth2 has many different flow variations and different providers have vastly different ideas on how to implement OAuth2. This PR allows us to implement arbitrary authentication logic prior to a request being generated (until such time as our formal OAuth2 config is standardised and stable). This allows us to support PoCs in future without having many custom Airnode instances in the wild, which reduces maintenance time keeping forks up to date 🙏 .

sync code follows this format:

const output = {...input, something: 3};

async code should follow this format:

({input, resolve}) => {console.log('hello'); resolve({...input, something: 3})};

resolve is passed through a context object to a vm runtime which the code interacts with.

I've separately played around a bit with a WASM variant of this, but it isn't as powerful due to OS linkage issues. Snippets can be written in a separate project and processed using webpack to create static-like JS snippets which don't have to rely on Airnode's libraries, although the payloads are quite large (530KB for something that uses ethers and axios).

@aquarat aquarat changed the base branch from master to add-pre-and-post-processing April 22, 2022 13:29
Copy link
Contributor

@Siegrift Siegrift left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took an initial look (was curious :p ). I like the vm module from node 💪

packages/airnode-node/src/api/unsafe-evaluate.ts Outdated Show resolved Hide resolved
@@ -23,5 +23,6 @@ describe('constants', () => {
expect(constants.PRIORITY_FEE).toEqual(3120000000);
expect(constants.BASE_FEE_MULTIPLIER).toEqual(2);
expect(constants.MAXIMUM_ONCHAIN_ERROR_LENGTH).toEqual(100);
expect(constants.PROCESSING_TIMEOUT).toEqual(10_000);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But isn't 10s default too much? Also this timer affects the Airnode cycle and those are not configurable as of now (I think we should discuss this).

packages/airnode-node/src/constants.ts Show resolved Hide resolved
packages/airnode-utilities/src/caching/index.ts Outdated Show resolved Hide resolved
@aquarat
Copy link
Contributor Author

aquarat commented Apr 22, 2022

Took an initial look (was curious :p ). I like the vm module from node muscle

ahahahahaa
It's a draft PR because I had to leave the room I was working in quickly 😆 long story.

@aquarat
Copy link
Contributor Author

aquarat commented Apr 22, 2022

But isn't 10s default too much? Also this timer affects the Airnode cycle and those are not configurable as of now (I think we should discuss this).

The current OAuth2 snippet I'm using (which I can post on Slack if you're curious), takes around 2s to resolve a token (when it needs to refresh the token, often an existing token will be available from the cache in which case it will be much faster, hence why caching is here). But as above I think the timeout should be exposed in config.json in ProcessingSpecification.

@aquarat
Copy link
Contributor Author

aquarat commented Apr 22, 2022

I've kept the timeout on reduce at 10s (via constants.ts) and given the individual code snippets configurable timeouts.
I've switched out eval for vm.runInNewContext.
I need to run a few real-world tests but this should be pretty close to ideal.

@aquarat
Copy link
Contributor Author

aquarat commented Apr 22, 2022

But isn't 10s default too much? Also this timer affects the Airnode cycle and those are not configurable as of now (I think we should discuss this).

This is now the total timeout and it shouldn't affect anything else? I think the total parent cycle has 30 seconds of execution time?

@aquarat aquarat marked this pull request as ready for review April 27, 2022 15:35
@aquarat
Copy link
Contributor Author

aquarat commented Apr 27, 2022

This is still pending testing on cloud.
Caching is included for OAuth2 operation.

@aquarat aquarat requested a review from Siegrift April 27, 2022 15:37
Copy link
Contributor

@Siegrift Siegrift left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks mostly good to me but I left some comments.

packages/airnode-node/src/api/processing.ts Outdated Show resolved Hide resolved
packages/airnode-node/src/api/processing.ts Outdated Show resolved Hide resolved
packages/airnode-node/src/api/unsafe-evaluate.ts Outdated Show resolved Hide resolved
packages/airnode-node/src/api/unsafe-evaluate.ts Outdated Show resolved Hide resolved
packages/airnode-utilities/src/caching/index.ts Outdated Show resolved Hide resolved
packages/airnode-utilities/src/caching/index.ts Outdated Show resolved Hide resolved
packages/airnode-utilities/src/caching/index.ts Outdated Show resolved Hide resolved
packages/airnode-utilities/src/caching/index.ts Outdated Show resolved Hide resolved
Base automatically changed from add-pre-and-post-processing to master April 29, 2022 07:41
@aquarat
Copy link
Contributor Author

aquarat commented May 1, 2022

Hopefully I can test this on cloud today.

@aquarat
Copy link
Contributor Author

aquarat commented May 2, 2022

Works on cloud with the most recent commit. I tried a few different ways of getting this working, ultimately these were the least offensive.

Copy link
Contributor

@Siegrift Siegrift left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a few more comments, but it LGTM overall.

Good job 💪

packages/airnode-node/src/api/unsafe-evaluate.ts Outdated Show resolved Hide resolved
const input = _input;
export const unsafeEvaluate = (input: any, code: string, timeout: number) => {
const vmContext = {
input,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We stopped passing ethers, I think that one is rather necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think we should also pass all our airnode-* packages.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fear it will be confusing and harder to document and I do not see an use for using Airnode packages. If you this this is improtant we can keep it, but otherwise I'd just pass ethers and axios.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's difficult to know what's useful for the future.
At minimum anything can be achieved given the built-in node modules (like I re-implemented the axios requirement using built-in https in the target code snippet). Everything outside of the built in modules is convenience.
I'll remove the airnode-* require/imports :)

packages/airnode-utilities/src/caching/index.test.ts Outdated Show resolved Hide resolved
packages/airnode-utilities/src/caching/index.ts Outdated Show resolved Hide resolved
@aquarat aquarat requested a review from Siegrift May 3, 2022 16:35
@aquarat
Copy link
Contributor Author

aquarat commented May 3, 2022

Done a few more updates, hopefully it is acceptable :P

@aquarat
Copy link
Contributor Author

aquarat commented May 3, 2022

I will split up nodeModules into nodeModules and extraModules.

@aquarat aquarat force-pushed the enhance-processing-and-add-caching branch from fe83877 to 451bcb6 Compare May 3, 2022 16:48
Copy link
Contributor

@Siegrift Siegrift left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code LGTM 👍 (but I haven't tested manually).

I do not see any more issues just by looking at the code and I believe this was (is) tested by you on the issue you work on.

@@ -23,5 +23,6 @@ describe('constants', () => {
expect(constants.PRIORITY_FEE).toEqual(3120000000);
expect(constants.BASE_FEE_MULTIPLIER).toEqual(2);
expect(constants.MAXIMUM_ONCHAIN_ERROR_LENGTH).toEqual(100);
expect(constants.PROCESSING_TIMEOUT).toEqual(10_000);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore this. We set 30 seconds timeout and postprocessing can only take 10 seconds. So in worst case the API call has only 20 seconds to finish, but that is still all right.

I also think that we need a timer, because it is possible that there will be an infinite loop or something and we should not wait forever until the preprocessing finishes.

return;
}

// Delete oldest cache entries first
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it a strange design that we actually can exceed the CACHE_MAX_FILES when adding a new key - but I don't have better suggestion atm.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would have to sweep on every key addition which would slow things down. We have a lot of overhead @ 20k vs 130k files. It's not "correct" but it is highly unlikely, probably impossible on Lambda, for the cache file count to jump by 110k files in one cycle (Airnode would not be able to process that many requests on Lambda).

@@ -23,5 +23,6 @@ describe('constants', () => {
expect(constants.PRIORITY_FEE).toEqual(3120000000);
expect(constants.BASE_FEE_MULTIPLIER).toEqual(2);
expect(constants.MAXIMUM_ONCHAIN_ERROR_LENGTH).toEqual(100);
expect(constants.PROCESSING_TIMEOUT).toEqual(10_000);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw. your solution handles timeouts well, but I think it would be nice to add a test which tests that. Something like:

const preProcessingSpecifications = [
        {
          environment: 'Node 14' as const,
          value: 'const output = {...input, from: `ETH`}; while(true);',
          timeoutMs: 5_000,
        },
        ...

packages/airnode-utilities/src/caching/index.ts Outdated Show resolved Hide resolved
@aquarat
Copy link
Contributor Author

aquarat commented May 5, 2022

@Siegrift : Regarding

Btw. your solution handles timeouts well, but I think it would be nice to add a test which tests that. Something like:

and

Ignore this. We set 30 seconds timeout and postprocessing can only take 10 seconds. So in worst case the API call has only 20 seconds to finish, but that is still all right.

I assume you mean I should ignore the first comment? I think the timeouts are robust and they definitely work in practice.

@Siegrift
Copy link
Contributor

Siegrift commented May 5, 2022

I assume you mean I should ignore the first comment?

Yeah.

@aquarat aquarat merged commit 20d98cd into master May 5, 2022
@aquarat aquarat deleted the enhance-processing-and-add-caching branch May 5, 2022 12:23
@aquarat
Copy link
Contributor Author

aquarat commented May 5, 2022

Thanks again for your input and patience @Siegrift :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants