From aa4c3dc45cb2d65e32c2b774bcbcb0f935df0391 Mon Sep 17 00:00:00 2001 From: Derek Croote Date: Mon, 3 Oct 2022 20:43:14 -0700 Subject: [PATCH] Enable cross-chain authorizers --- .changeset/wild-walls-beg.md | 9 ++++ .../config/config.example.json | 3 +- .../test/fixtures/config.valid.json | 3 +- .../config.example.json | 3 +- .../create-config.ts | 1 + .../config.example.json | 3 +- .../create-config.ts | 1 + .../config.example.json | 3 +- .../coingecko-pre-processing/create-config.ts | 1 + .../coingecko-signed-data/config.example.json | 3 +- .../coingecko-signed-data/create-config.ts | 1 + .../coingecko-template/config.example.json | 3 +- .../coingecko-template/create-config.ts | 1 + .../coingecko-testable/config.example.json | 3 +- .../coingecko-testable/create-config.ts | 1 + .../coingecko/config.example.json | 3 +- .../integrations/coingecko/create-config.ts | 1 + .../failing-example/config.example.json | 3 +- .../failing-example/create-config.ts | 1 + .../config.example.json | 3 +- .../relay-security-schemes/create-config.ts | 1 + .../weather-multi-value/config.example.json | 3 +- .../weather-multi-value/create-config.ts | 1 + .../airnode-node/config/config.example.json | 3 +- .../coordinator/calls/chain-limits.test.ts | 1 + .../authorization-fetching.test.ts | 13 +++-- .../evm/handlers/initialize-provider.test.ts | 30 ++++++++++- .../src/evm/handlers/initialize-provider.ts | 51 +++++++++++++++++-- .../src/providers/actions.test.ts | 17 +++++-- .../airnode-node/src/providers/state.test.ts | 17 +++++-- .../test/fixtures/config/config.ts | 1 + .../test/fixtures/operation/deploy-config.ts | 1 + .../test/fixtures/provider-states/evm.ts | 1 + packages/airnode-node/test/setup/e2e/utils.ts | 1 + .../src/config/evm-dev-config.json | 3 +- packages/airnode-operation/src/types.ts | 3 +- .../src/config/config.test.ts | 28 ++++++++++ .../airnode-validator/src/config/config.ts | 11 ++++ .../test/fixtures/config.valid.json | 15 +++++- .../fixtures/interpolated-config.valid.json | 15 +++++- .../fixtures/invalid-secret-name/config.json | 3 +- 41 files changed, 236 insertions(+), 33 deletions(-) create mode 100644 .changeset/wild-walls-beg.md diff --git a/.changeset/wild-walls-beg.md b/.changeset/wild-walls-beg.md new file mode 100644 index 0000000000..e72ac96671 --- /dev/null +++ b/.changeset/wild-walls-beg.md @@ -0,0 +1,9 @@ +--- +'@api3/airnode-deployer': minor +'@api3/airnode-examples': minor +'@api3/airnode-node': minor +'@api3/airnode-operation': minor +'@api3/airnode-validator': minor +--- + +Enable cross-chain authorizers diff --git a/packages/airnode-deployer/config/config.example.json b/packages/airnode-deployer/config/config.example.json index 17dc84b051..2ef480ac71 100644 --- a/packages/airnode-deployer/config/config.example.json +++ b/packages/airnode-deployer/config/config.example.json @@ -3,7 +3,8 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-deployer/test/fixtures/config.valid.json b/packages/airnode-deployer/test/fixtures/config.valid.json index 17dc84b051..2ef480ac71 100644 --- a/packages/airnode-deployer/test/fixtures/config.valid.json +++ b/packages/airnode-deployer/test/fixtures/config.valid.json @@ -3,7 +3,8 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-examples/integrations/authenticated-coinmarketcap/config.example.json b/packages/airnode-examples/integrations/authenticated-coinmarketcap/config.example.json index b5d13dc1a0..2edcf54f71 100644 --- a/packages/airnode-examples/integrations/authenticated-coinmarketcap/config.example.json +++ b/packages/airnode-examples/integrations/authenticated-coinmarketcap/config.example.json @@ -3,7 +3,8 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-examples/integrations/authenticated-coinmarketcap/create-config.ts b/packages/airnode-examples/integrations/authenticated-coinmarketcap/create-config.ts index fb18b74480..b34252e220 100644 --- a/packages/airnode-examples/integrations/authenticated-coinmarketcap/create-config.ts +++ b/packages/airnode-examples/integrations/authenticated-coinmarketcap/create-config.ts @@ -13,6 +13,7 @@ const createConfig = async (generateExampleFile: boolean): Promise => ({ maxConcurrency: 100, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-examples/integrations/coingecko-post-processing/config.example.json b/packages/airnode-examples/integrations/coingecko-post-processing/config.example.json index 11fb4c7f25..d11ab14614 100644 --- a/packages/airnode-examples/integrations/coingecko-post-processing/config.example.json +++ b/packages/airnode-examples/integrations/coingecko-post-processing/config.example.json @@ -3,7 +3,8 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-examples/integrations/coingecko-post-processing/create-config.ts b/packages/airnode-examples/integrations/coingecko-post-processing/create-config.ts index d872973cfe..b491e15d92 100644 --- a/packages/airnode-examples/integrations/coingecko-post-processing/create-config.ts +++ b/packages/airnode-examples/integrations/coingecko-post-processing/create-config.ts @@ -13,6 +13,7 @@ const createConfig = async (generateExampleFile: boolean): Promise => ({ maxConcurrency: 100, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-examples/integrations/coingecko-pre-processing/config.example.json b/packages/airnode-examples/integrations/coingecko-pre-processing/config.example.json index 45763c262f..4c818d9675 100644 --- a/packages/airnode-examples/integrations/coingecko-pre-processing/config.example.json +++ b/packages/airnode-examples/integrations/coingecko-pre-processing/config.example.json @@ -3,7 +3,8 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-examples/integrations/coingecko-pre-processing/create-config.ts b/packages/airnode-examples/integrations/coingecko-pre-processing/create-config.ts index 659af12251..0515af2a29 100644 --- a/packages/airnode-examples/integrations/coingecko-pre-processing/create-config.ts +++ b/packages/airnode-examples/integrations/coingecko-pre-processing/create-config.ts @@ -13,6 +13,7 @@ const createConfig = async (generateExampleFile: boolean): Promise => ({ maxConcurrency: 100, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-examples/integrations/coingecko-signed-data/config.example.json b/packages/airnode-examples/integrations/coingecko-signed-data/config.example.json index 021215eb01..8e5e88609d 100644 --- a/packages/airnode-examples/integrations/coingecko-signed-data/config.example.json +++ b/packages/airnode-examples/integrations/coingecko-signed-data/config.example.json @@ -3,7 +3,8 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-examples/integrations/coingecko-signed-data/create-config.ts b/packages/airnode-examples/integrations/coingecko-signed-data/create-config.ts index 7f770071c5..7d15be27bf 100644 --- a/packages/airnode-examples/integrations/coingecko-signed-data/create-config.ts +++ b/packages/airnode-examples/integrations/coingecko-signed-data/create-config.ts @@ -13,6 +13,7 @@ const createConfig = async (generateExampleFile: boolean): Promise => ({ maxConcurrency: 100, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-examples/integrations/coingecko-template/config.example.json b/packages/airnode-examples/integrations/coingecko-template/config.example.json index 6679d64a95..8fbd7be06f 100644 --- a/packages/airnode-examples/integrations/coingecko-template/config.example.json +++ b/packages/airnode-examples/integrations/coingecko-template/config.example.json @@ -3,7 +3,8 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-examples/integrations/coingecko-template/create-config.ts b/packages/airnode-examples/integrations/coingecko-template/create-config.ts index 6094d2d4fc..9fc74d61a5 100644 --- a/packages/airnode-examples/integrations/coingecko-template/create-config.ts +++ b/packages/airnode-examples/integrations/coingecko-template/create-config.ts @@ -14,6 +14,7 @@ const createConfig = async (generateExampleFile: boolean): Promise => ({ maxConcurrency: 100, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-examples/integrations/coingecko-testable/config.example.json b/packages/airnode-examples/integrations/coingecko-testable/config.example.json index 537139d86c..f1bc866eb3 100644 --- a/packages/airnode-examples/integrations/coingecko-testable/config.example.json +++ b/packages/airnode-examples/integrations/coingecko-testable/config.example.json @@ -3,7 +3,8 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-examples/integrations/coingecko-testable/create-config.ts b/packages/airnode-examples/integrations/coingecko-testable/create-config.ts index 1d83e93f9c..056e373a1f 100644 --- a/packages/airnode-examples/integrations/coingecko-testable/create-config.ts +++ b/packages/airnode-examples/integrations/coingecko-testable/create-config.ts @@ -13,6 +13,7 @@ const createConfig = async (generateExampleFile: boolean): Promise => ({ maxConcurrency: 100, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-examples/integrations/coingecko/config.example.json b/packages/airnode-examples/integrations/coingecko/config.example.json index b208644671..9412d3b964 100644 --- a/packages/airnode-examples/integrations/coingecko/config.example.json +++ b/packages/airnode-examples/integrations/coingecko/config.example.json @@ -3,7 +3,8 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-examples/integrations/coingecko/create-config.ts b/packages/airnode-examples/integrations/coingecko/create-config.ts index 77ff23ea18..3d09555f3c 100644 --- a/packages/airnode-examples/integrations/coingecko/create-config.ts +++ b/packages/airnode-examples/integrations/coingecko/create-config.ts @@ -13,6 +13,7 @@ const createConfig = async (generateExampleFile: boolean): Promise => ({ maxConcurrency: 100, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-examples/integrations/failing-example/config.example.json b/packages/airnode-examples/integrations/failing-example/config.example.json index 4e08a98cd6..d970e7795b 100644 --- a/packages/airnode-examples/integrations/failing-example/config.example.json +++ b/packages/airnode-examples/integrations/failing-example/config.example.json @@ -3,7 +3,8 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-examples/integrations/failing-example/create-config.ts b/packages/airnode-examples/integrations/failing-example/create-config.ts index b16c976701..bd88389bc5 100644 --- a/packages/airnode-examples/integrations/failing-example/create-config.ts +++ b/packages/airnode-examples/integrations/failing-example/create-config.ts @@ -13,6 +13,7 @@ const createConfig = async (generateExampleFile: boolean): Promise => ({ maxConcurrency: 100, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-examples/integrations/relay-security-schemes/config.example.json b/packages/airnode-examples/integrations/relay-security-schemes/config.example.json index 1c9ba03402..da8c459414 100644 --- a/packages/airnode-examples/integrations/relay-security-schemes/config.example.json +++ b/packages/airnode-examples/integrations/relay-security-schemes/config.example.json @@ -3,7 +3,8 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-examples/integrations/relay-security-schemes/create-config.ts b/packages/airnode-examples/integrations/relay-security-schemes/create-config.ts index fb4006c14d..4e0ea52cd0 100644 --- a/packages/airnode-examples/integrations/relay-security-schemes/create-config.ts +++ b/packages/airnode-examples/integrations/relay-security-schemes/create-config.ts @@ -13,6 +13,7 @@ const createConfig = async (generateExampleFile: boolean): Promise => ({ maxConcurrency: 100, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-examples/integrations/weather-multi-value/config.example.json b/packages/airnode-examples/integrations/weather-multi-value/config.example.json index ab29c45716..75ab8b92a4 100644 --- a/packages/airnode-examples/integrations/weather-multi-value/config.example.json +++ b/packages/airnode-examples/integrations/weather-multi-value/config.example.json @@ -3,7 +3,8 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-examples/integrations/weather-multi-value/create-config.ts b/packages/airnode-examples/integrations/weather-multi-value/create-config.ts index f56cfc6356..23c52e93df 100644 --- a/packages/airnode-examples/integrations/weather-multi-value/create-config.ts +++ b/packages/airnode-examples/integrations/weather-multi-value/create-config.ts @@ -13,6 +13,7 @@ const createConfig = async (generateExampleFile: boolean): Promise => ({ maxConcurrency: 100, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-node/config/config.example.json b/packages/airnode-node/config/config.example.json index f71a166276..da13756b75 100644 --- a/packages/airnode-node/config/config.example.json +++ b/packages/airnode-node/config/config.example.json @@ -3,7 +3,8 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-node/src/coordinator/calls/chain-limits.test.ts b/packages/airnode-node/src/coordinator/calls/chain-limits.test.ts index a3088241e3..90e2895583 100644 --- a/packages/airnode-node/src/coordinator/calls/chain-limits.test.ts +++ b/packages/airnode-node/src/coordinator/calls/chain-limits.test.ts @@ -9,6 +9,7 @@ const createChainConfig = (overrides: Partial): ChainConfig => { maxConcurrency: 100, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-node/src/evm/authorization/authorization-fetching.test.ts b/packages/airnode-node/src/evm/authorization/authorization-fetching.test.ts index ee8b9229f8..7c678f7189 100644 --- a/packages/airnode-node/src/evm/authorization/authorization-fetching.test.ts +++ b/packages/airnode-node/src/evm/authorization/authorization-fetching.test.ts @@ -24,6 +24,7 @@ describe('fetch (authorizations)', () => { '0x711c93B32c0D28a5d18feD87434cce11C3e5699B', '0x9E0e23766b0ed0C492804872c5164E9187fB56f5', ], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, @@ -41,7 +42,7 @@ describe('fetch (authorizations)', () => { expect(res).toEqual({}); }); - it('returns true for all pending requests if authorizers array is empty', async () => { + it('returns true for all pending requests if authorizers arrays are empty', async () => { const apiCalls = Array.from(Array(19).keys()).map((n) => { return fixtures.requests.buildApiCall({ id: `${n}`, @@ -52,7 +53,10 @@ describe('fetch (authorizations)', () => { }); const [logs, res] = await authorization.fetch(apiCalls, { ...mutableFetchOptions, - authorizers: { requesterEndpointAuthorizers: [] }, + authorizers: { + requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], + }, }); expect(logs).toEqual([]); @@ -318,7 +322,10 @@ describe('fetch (authorizations)', () => { }); describe('fetchAuthorizationStatus', () => { - const authorizers = { requesterEndpointAuthorizers: ['0x0000000000000000000000000000000000000000'] }; + const authorizers = { + requesterEndpointAuthorizers: ['0x0000000000000000000000000000000000000000'], + crossChainRequesterAuthorizers: [], + }; const airnodeAddress = '0xairnodeAddress'; let mutableAirnodeRrp: AirnodeRrpV0; diff --git a/packages/airnode-node/src/evm/handlers/initialize-provider.test.ts b/packages/airnode-node/src/evm/handlers/initialize-provider.test.ts index 3026ba2666..142929e22e 100644 --- a/packages/airnode-node/src/evm/handlers/initialize-provider.test.ts +++ b/packages/airnode-node/src/evm/handlers/initialize-provider.test.ts @@ -10,8 +10,9 @@ mockEthers({ import { ethers } from 'ethers'; import * as adapter from '@api3/airnode-adapter'; -import { initializeProvider } from './initialize-provider'; +import { initializeProvider, mergeAuthorizationsByRequestId } from './initialize-provider'; import * as fixtures from '../../../test/fixtures'; +import { AuthorizationByRequestId } from '../../types'; describe('initializeProvider', () => { jest.setTimeout(30_000); @@ -228,4 +229,31 @@ describe('initializeProvider', () => { expect(res).toEqual(null); expect(getLogsSpy).toHaveBeenCalledTimes(1); }); + + it('merges authorizations and cross-chain authorizations', () => { + const authorizations: AuthorizationByRequestId = { + '0x1': false, + '0x2': true, + '0x3': true, + '0x4': false, + '0x5': false, + '0x6': true, + }; + const crossChainAuthorizations: AuthorizationByRequestId = { + '0x1': true, + '0x2': true, + '0x3': true, + '0x4': false, + }; + + const merged = mergeAuthorizationsByRequestId([authorizations, crossChainAuthorizations]); + expect(merged).toEqual({ + '0x1': true, + '0x2': true, + '0x3': true, + '0x4': false, + '0x5': false, + '0x6': true, + } as AuthorizationByRequestId); + }); }); diff --git a/packages/airnode-node/src/evm/handlers/initialize-provider.ts b/packages/airnode-node/src/evm/handlers/initialize-provider.ts index 0eaf428493..6b12bf6828 100644 --- a/packages/airnode-node/src/evm/handlers/initialize-provider.ts +++ b/packages/airnode-node/src/evm/handlers/initialize-provider.ts @@ -1,3 +1,5 @@ +import flatMap from 'lodash/flatMap'; +import mergeWith from 'lodash/mergeWith'; import { logger, PendingLog } from '@api3/airnode-utilities'; import { go } from '@api3/promise-utils'; import { fetchPendingRequests } from './fetch-pending-requests'; @@ -7,7 +9,8 @@ import * as state from '../../providers/state'; import * as templates from '../templates'; import * as transactionCounts from '../transaction-counts'; import * as verification from '../verification'; -import { EVMProviderState, ProviderState } from '../../types'; +import { buildEVMProvider } from '../evm-provider'; +import { AuthorizationByRequestId, EVMProviderState, ProviderState } from '../../types'; type ParallelPromise = Promise<{ readonly id: string; readonly data: any; readonly logs: PendingLog[] }>; @@ -23,6 +26,26 @@ async function fetchAuthorizations(currentState: ProviderState return { id: 'authorizations', data: res, logs }; } +async function fetchCrossChainAuthorizations(currentState: ProviderState) { + const promises = currentState.settings.authorizers.crossChainRequesterAuthorizers.map(async (authorizer) => { + const fetchOptions: authorizations.FetchOptions = { + authorizers: currentState.settings.authorizers, + authorizations: currentState.settings.authorizations, + airnodeAddress: currentState.settings.airnodeAddress, + airnodeRrpAddress: authorizer.contracts.AirnodeRrp, + provider: buildEVMProvider(authorizer.chainProvider.url, authorizer.chainId), + }; + const result = await authorizations.fetch(currentState.requests.apiCalls, fetchOptions); + return result; + }); + + const responses = await Promise.all(promises); + const logs = flatMap(responses, (r) => r[0]); + const authorizationStatuses = responses.map((r) => r[1]); + + return { id: 'crossChainAuthorizations', data: authorizationStatuses, logs }; +} + async function fetchTransactionCounts(currentState: ProviderState) { const sponsors = requests.mapUniqueSponsorAddresses(currentState.requests); const fetchOptions = { @@ -36,6 +59,18 @@ async function fetchTransactionCounts(currentState: ProviderState { + // authorized if any authorizer has authorized (logical OR) + if (objVal === true || srcVal === true) { + return true; + } else { + return false; + } + }); + return merged; +} + export async function initializeProvider( initialState: ProviderState ): Promise | null> { @@ -109,6 +144,7 @@ export async function initializeProvider( const authAndTxCountPromises: readonly ParallelPromise[] = [ fetchAuthorizations(state5), fetchTransactionCounts(state5), + fetchCrossChainAuthorizations(state5), ]; const authAndTxResults = await Promise.all(authAndTxCountPromises); @@ -119,9 +155,18 @@ export async function initializeProvider( const authRes = authAndTxResults.find((r) => r.id === 'authorizations')!; logger.logPending(authRes.logs); + const crossAuthRes = authAndTxResults.find((r) => r.id === 'crossChainAuthorizations')!; + logger.logPending(crossAuthRes.logs); + const transactionCountsBySponsorAddress = txCountRes.data!; - const authorizationsByRequestId = authRes.data!; + // Merge authorization statuses + const authorizationsByRequestId: AuthorizationByRequestId = authRes.data!; + const crossAuthorizationsByRequestId: AuthorizationByRequestId[] = crossAuthRes.data!; + const mergedAuthorizationsByRequestId = mergeAuthorizationsByRequestId([ + authorizationsByRequestId, + ...crossAuthorizationsByRequestId, + ]); const state6 = state.update(state5, { transactionCountsBySponsorAddress }); // ================================================================= @@ -129,7 +174,7 @@ export async function initializeProvider( // ================================================================= const [authLogs, authorizedApiCalls] = authorizations.mergeAuthorizations( state5.requests.apiCalls, - authorizationsByRequestId + mergedAuthorizationsByRequestId ); logger.logPending(authLogs); diff --git a/packages/airnode-node/src/providers/actions.test.ts b/packages/airnode-node/src/providers/actions.test.ts index 6511664b60..aa51ce61aa 100644 --- a/packages/airnode-node/src/providers/actions.test.ts +++ b/packages/airnode-node/src/providers/actions.test.ts @@ -37,7 +37,10 @@ const chainProviderName3 = 'Infura Sepolia'; const airnodeAddress = '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace'; const chains: ChainConfig[] = [ { - authorizers: { requesterEndpointAuthorizers: [ethers.constants.AddressZero] }, + authorizers: { + requesterEndpointAuthorizers: [ethers.constants.AddressZero], + crossChainRequesterAuthorizers: [], + }, authorizations: { requesterEndpointAuthorizations: {}, }, @@ -66,7 +69,7 @@ const chains: ChainConfig[] = [ }, }, { - authorizers: { requesterEndpointAuthorizers: [ethers.constants.AddressZero] }, + authorizers: { requesterEndpointAuthorizers: [ethers.constants.AddressZero], crossChainRequesterAuthorizers: [] }, authorizations: { requesterEndpointAuthorizations: {}, }, @@ -122,7 +125,10 @@ describe('initialize', () => { settings: { airnodeAddress: '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace', airnodeAddressShort: 'a30ca71', - authorizers: { requesterEndpointAuthorizers: [ethers.constants.AddressZero] }, + authorizers: { + requesterEndpointAuthorizers: [ethers.constants.AddressZero], + crossChainRequesterAuthorizers: [], + }, authorizations: { requesterEndpointAuthorizations: {}, }, @@ -173,7 +179,10 @@ describe('initialize', () => { settings: { airnodeAddress: '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace', airnodeAddressShort: 'a30ca71', - authorizers: { requesterEndpointAuthorizers: [ethers.constants.AddressZero] }, + authorizers: { + requesterEndpointAuthorizers: [ethers.constants.AddressZero], + crossChainRequesterAuthorizers: [], + }, authorizations: { requesterEndpointAuthorizations: {}, }, diff --git a/packages/airnode-node/src/providers/state.test.ts b/packages/airnode-node/src/providers/state.test.ts index d684dc62bb..0e9408ceba 100644 --- a/packages/airnode-node/src/providers/state.test.ts +++ b/packages/airnode-node/src/providers/state.test.ts @@ -15,7 +15,10 @@ describe('create', () => { const airnodeAddress = '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace'; const chainConfig: ChainConfig = { maxConcurrency: 100, - authorizers: { requesterEndpointAuthorizers: [ethers.constants.AddressZero] }, + authorizers: { + requesterEndpointAuthorizers: [ethers.constants.AddressZero], + crossChainRequesterAuthorizers: [], + }, authorizations: { requesterEndpointAuthorizations: {}, }, @@ -51,7 +54,10 @@ describe('create', () => { settings: { airnodeAddress: '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace', airnodeAddressShort: 'a30ca71', - authorizers: { requesterEndpointAuthorizers: [ethers.constants.AddressZero] }, + authorizers: { + requesterEndpointAuthorizers: [ethers.constants.AddressZero], + crossChainRequesterAuthorizers: [], + }, authorizations: { requesterEndpointAuthorizations: {}, }, @@ -105,7 +111,7 @@ describe('create', () => { const airnodeAddress = '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace'; const chainConfig: ChainConfig = { maxConcurrency: 100, - authorizers: { requesterEndpointAuthorizers: [ethers.constants.AddressZero] }, + authorizers: { requesterEndpointAuthorizers: [ethers.constants.AddressZero], crossChainRequesterAuthorizers: [] }, authorizations: { requesterEndpointAuthorizations: {}, }, @@ -143,7 +149,10 @@ describe('create', () => { settings: { airnodeAddress: '0xA30CA71Ba54E83127214D3271aEA8F5D6bD4Dace', airnodeAddressShort: 'a30ca71', - authorizers: { requesterEndpointAuthorizers: [ethers.constants.AddressZero] }, + authorizers: { + requesterEndpointAuthorizers: [ethers.constants.AddressZero], + crossChainRequesterAuthorizers: [], + }, authorizations: { requesterEndpointAuthorizations: {}, }, diff --git a/packages/airnode-node/test/fixtures/config/config.ts b/packages/airnode-node/test/fixtures/config/config.ts index a17902aa4d..3efdc66976 100644 --- a/packages/airnode-node/test/fixtures/config/config.ts +++ b/packages/airnode-node/test/fixtures/config/config.ts @@ -38,6 +38,7 @@ export function buildConfig(overrides?: Partial): Config { maxConcurrency: 100, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-node/test/fixtures/operation/deploy-config.ts b/packages/airnode-node/test/fixtures/operation/deploy-config.ts index df754c5bae..0cac664e8e 100644 --- a/packages/airnode-node/test/fixtures/operation/deploy-config.ts +++ b/packages/airnode-node/test/fixtures/operation/deploy-config.ts @@ -8,6 +8,7 @@ export function buildDeployConfig(mnemonic: string, config?: Partial): C mnemonic, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-node/test/fixtures/provider-states/evm.ts b/packages/airnode-node/test/fixtures/provider-states/evm.ts index ad6329dbc1..a112f9276f 100644 --- a/packages/airnode-node/test/fixtures/provider-states/evm.ts +++ b/packages/airnode-node/test/fixtures/provider-states/evm.ts @@ -15,6 +15,7 @@ export function buildEVMProviderState( maxConcurrency: 100, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-node/test/setup/e2e/utils.ts b/packages/airnode-node/test/setup/e2e/utils.ts index fde7695d6b..9803c2848e 100644 --- a/packages/airnode-node/test/setup/e2e/utils.ts +++ b/packages/airnode-node/test/setup/e2e/utils.ts @@ -17,6 +17,7 @@ export function buildChainConfig(contracts: Contracts): ChainConfig { }, authorizers: { requesterEndpointAuthorizers: [], + crossChainRequesterAuthorizers: [], }, authorizations: { requesterEndpointAuthorizations: {}, diff --git a/packages/airnode-operation/src/config/evm-dev-config.json b/packages/airnode-operation/src/config/evm-dev-config.json index 686b90ac8a..65906af883 100644 --- a/packages/airnode-operation/src/config/evm-dev-config.json +++ b/packages/airnode-operation/src/config/evm-dev-config.json @@ -4,7 +4,8 @@ "CurrencyConverterAirnode": { "mnemonic": "achieve climb couple wait accident symbol spy blouse reduce foil echo label", "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-operation/src/types.ts b/packages/airnode-operation/src/types.ts index 43b273eb50..29fa8e1645 100644 --- a/packages/airnode-operation/src/types.ts +++ b/packages/airnode-operation/src/types.ts @@ -111,7 +111,8 @@ export interface ConfigTemplate { } export interface ConfigAirnode { - readonly authorizers: { requesterEndpointAuthorizers: string[] }; + // TODO + readonly authorizers: { requesterEndpointAuthorizers: string[]; crossChainRequesterAuthorizers: [] }; readonly authorizations: { requesterEndpointAuthorizations: { [endpointId: string]: string[] }; }; diff --git a/packages/airnode-validator/src/config/config.test.ts b/packages/airnode-validator/src/config/config.test.ts index 4a9d5e34c1..09708c8dbe 100644 --- a/packages/airnode-validator/src/config/config.test.ts +++ b/packages/airnode-validator/src/config/config.test.ts @@ -657,3 +657,31 @@ describe('chainConfigSchema', () => { ); }); }); + +describe('authorizers', () => { + it('allows cross-chain and same-chain authorizers', () => { + const config = JSON.parse( + readFileSync(join(__dirname, '../../test/fixtures/interpolated-config.valid.json')).toString() + ); + const validAuthorizersChainConfig = { + ...config.chains[0], + authorizers: { + requesterEndpointAuthorizers: ['0x5FbDB2315678afecb367f032d93F642f64180aa3'], + crossChainRequesterAuthorizers: [ + { + requesterEndpointAuthorizers: ['0x5FbDB2315678afecb367f032d93F642f64180aa3'], + chainType: 'evm', + chainId: '1', + chainProvider: { + url: 'http://some.random.url', + }, + contracts: { + AirnodeRrp: '0x5FbDB2315678afecb367f032d93F642f64180aa3', + }, + }, + ], + }, + }; + expect(() => chainConfigSchema.parse(validAuthorizersChainConfig)).not.toThrow(); + }); +}); diff --git a/packages/airnode-validator/src/config/config.ts b/packages/airnode-validator/src/config/config.ts index ee8d5aba4b..a6916e18a6 100644 --- a/packages/airnode-validator/src/config/config.ts +++ b/packages/airnode-validator/src/config/config.ts @@ -162,8 +162,18 @@ export const chainAuthorizationsSchema = z.object({ requesterEndpointAuthorizations: z.record(endpointIdSchema, z.array(evmAddressSchema)), }); +// TODO: add crossChainProvider to secrets.env for testing interpolation +export const crossChainRequesterAuthorizerSchema = z.object({ + requesterEndpointAuthorizers: z.array(evmAddressSchema), + chainType: chainTypeSchema, + chainId: z.string(), + contracts: chainContractsSchema, + chainProvider: providerSchema, +}); + export const chainAuthorizersSchema = z.object({ requesterEndpointAuthorizers: z.array(evmAddressSchema), + crossChainRequesterAuthorizers: z.array(crossChainRequesterAuthorizerSchema), }); export const maxConcurrencySchema = z.number().int().positive(); @@ -476,6 +486,7 @@ export type LocalOrCloudProvider = SchemaType export type Providers = SchemaType; export type Gateway = SchemaType; export type ChainAuthorizers = SchemaType; +export type CrossChainAuthorizer = SchemaType; export type ChainAuthorizations = SchemaType; export type ChainOptions = SchemaType; export type ChainType = SchemaType; diff --git a/packages/airnode-validator/test/fixtures/config.valid.json b/packages/airnode-validator/test/fixtures/config.valid.json index db3ce77166..a1834f8382 100644 --- a/packages/airnode-validator/test/fixtures/config.valid.json +++ b/packages/airnode-validator/test/fixtures/config.valid.json @@ -3,7 +3,20 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [ + { + "requesterEndpointAuthorizers": [], + "chainType": "evm", + "chainId": "4", + "contracts": { + "AirnodeRrp": "0x5FbDB2315678afecb367f032d93F642f64180aa3" + }, + "chainProvider": { + "url": "http://some.random.url" + } + } + ] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-validator/test/fixtures/interpolated-config.valid.json b/packages/airnode-validator/test/fixtures/interpolated-config.valid.json index 48a64dc773..1559fa2ccd 100644 --- a/packages/airnode-validator/test/fixtures/interpolated-config.valid.json +++ b/packages/airnode-validator/test/fixtures/interpolated-config.valid.json @@ -3,7 +3,20 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [ + { + "requesterEndpointAuthorizers": [], + "chainType": "evm", + "chainId": "4", + "contracts": { + "AirnodeRrp": "0x5FbDB2315678afecb367f032d93F642f64180aa3" + }, + "chainProvider": { + "url": "http://some.random.url" + } + } + ] }, "authorizations": { "requesterEndpointAuthorizations": {} diff --git a/packages/airnode-validator/test/fixtures/invalid-secret-name/config.json b/packages/airnode-validator/test/fixtures/invalid-secret-name/config.json index 0dabd671fa..a003da66b3 100644 --- a/packages/airnode-validator/test/fixtures/invalid-secret-name/config.json +++ b/packages/airnode-validator/test/fixtures/invalid-secret-name/config.json @@ -3,7 +3,8 @@ { "maxConcurrency": 100, "authorizers": { - "requesterEndpointAuthorizers": [] + "requesterEndpointAuthorizers": [], + "crossChainRequesterAuthorizers": [] }, "authorizations": { "requesterEndpointAuthorizations": {}