Skip to content

Commit 59f380e

Browse files
committed
FIX: add concurrency
1 parent 3f9f342 commit 59f380e

File tree

7 files changed

+77
-80
lines changed

7 files changed

+77
-80
lines changed

.pnp.cjs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/sources/view-function-multi-chain/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"dependencies": {
3737
"@chainlink/external-adapter-framework": "2.8.0",
3838
"ethers": "^6.13.2",
39+
"p-limit": "3",
3940
"tslib": "2.4.1"
4041
}
4142
}

packages/sources/view-function-multi-chain/src/endpoint/function.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const inputParamDefinition = {
2727
description: 'RPC network name',
2828
type: 'string',
2929
},
30-
data: {
30+
additionalRequests: {
3131
description: 'Optional map of function calls',
3232
type: 'object' as unknown as Record<string, any>,
3333
required: false,

packages/sources/view-function-multi-chain/src/transport/function-common.ts

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
AdapterInputError,
1111
} from '@chainlink/external-adapter-framework/validation/error'
1212
import { ethers } from 'ethers'
13+
import pLimit from 'p-limit'
1314

1415
const logger = makeLogger('View Function Multi Chain')
1516

@@ -19,7 +20,7 @@ interface RequestParams {
1920
inputParams?: Array<string>
2021
network: string
2122
resultField?: string
22-
data?: Record<string, RequestParams>
23+
additionalRequests?: Record<string, RequestParams>
2324
}
2425

2526
export type RawOnchainResponse = {
@@ -84,25 +85,39 @@ export class MultiChainFunctionTransport<
8485
}
8586

8687
async _handleRequest(param: RequestParams): Promise<AdapterResponse<T['Response']>> {
87-
const { address, signature, inputParams, network, data } = param
88-
89-
const mainResult = await this._executeFunction({
90-
address,
91-
signature,
92-
inputParams,
93-
network,
94-
resultField: param.resultField,
95-
})
88+
const { address, signature, inputParams, network, additionalRequests } = param
89+
90+
const [mainResult, nestedResultOutcome] = await Promise.allSettled([
91+
this._executeFunction({
92+
address,
93+
signature,
94+
inputParams,
95+
network,
96+
resultField: param.resultField,
97+
}),
98+
this._processNestedDataRequest(additionalRequests, address, network),
99+
])
100+
101+
if (mainResult.status === 'rejected') {
102+
throw new AdapterError({
103+
statusCode: mainResult.reason?.statusCode || null,
104+
message: `${mainResult.reason}`,
105+
})
106+
}
96107

97-
const nestedResults = await this._processNestedDataRequest(data, address, network)
108+
// Nested result is optional
109+
const nestedResults =
110+
nestedResultOutcome.status === 'fulfilled'
111+
? nestedResultOutcome.value
112+
: (console.warn('Nested result failed:', nestedResultOutcome.reason), null)
98113

99-
const combinedData = { result: mainResult.result, ...nestedResults }
114+
const combinedData = { result: mainResult.value.result, ...nestedResults }
100115

101116
return {
102117
data: combinedData,
103118
statusCode: 200,
104-
result: mainResult.result,
105-
timestamps: mainResult.timestamps,
119+
result: mainResult.value.result,
120+
timestamps: mainResult.value.timestamps,
106121
}
107122
}
108123

@@ -161,36 +176,48 @@ export class MultiChainFunctionTransport<
161176
}
162177

163178
private async _processNestedDataRequest(
164-
data: Record<string, unknown> | undefined,
179+
additionalRequests: Record<string, RequestParams> | undefined,
165180
parentAddress: string,
166181
parentNetwork: string,
167182
): Promise<Record<string, any>> {
168-
if (!data || typeof data !== 'object') return {}
169-
183+
const limit = pLimit(5)
170184
const results: Record<string, any> = {}
171185

172-
for (const [key, subReq] of Object.entries(data)) {
173-
try {
174-
const req = subReq as RequestParams
175-
176-
if (!req.signature) {
177-
logger.warn(`Skipping nested key "${key}" — no signature provided.`)
178-
continue
186+
if (!additionalRequests || typeof additionalRequests !== 'object') return results
187+
188+
const tasks = Object.entries(additionalRequests).map(([key, subReq]) =>
189+
limit(async () => {
190+
try {
191+
const req = subReq as RequestParams
192+
193+
if (!req.signature) {
194+
logger.warn(`Skipping nested key "${key}" — no signature provided.`)
195+
return [key, null]
196+
}
197+
198+
const nestedParam = {
199+
address: req.address || parentAddress,
200+
network: req.network || parentNetwork,
201+
signature: req.signature,
202+
inputParams: req.inputParams,
203+
resultField: req.resultField,
204+
}
205+
206+
const subRes = await this._executeFunction(nestedParam)
207+
return [key, subRes.result]
208+
} catch (err) {
209+
logger.warn(`Nested function "${key}" failed: ${err}`)
210+
return [key, null]
179211
}
212+
}),
213+
)
180214

181-
const nestedParam = {
182-
address: req.address || parentAddress,
183-
network: req.network || parentNetwork,
184-
signature: req.signature,
185-
inputParams: req.inputParams,
186-
resultField: req.resultField,
187-
}
215+
const settled = await Promise.allSettled(tasks)
188216

189-
const subRes = await this._executeFunction(nestedParam)
190-
results[key] = subRes.result
191-
} catch (err) {
192-
logger.warn(`Nested function "${key}" failed: ${err}`)
193-
results[key] = null
217+
for (const outcome of settled) {
218+
if (outcome.status === 'fulfilled') {
219+
const [key, value] = outcome.value as [string, string]
220+
results[key] = value
194221
}
195222
}
196223

packages/sources/view-function-multi-chain/test/integration/__snapshots__/adapter.test.ts.snap

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -59,22 +59,6 @@ exports[`execute function endpoint should return success for different network 1
5959
exports[`execute function endpoint should return success with additional data requests 1`] = `
6060
{
6161
"data": {
62-
"decimals": "0x0000000000000000000000000000000000000000000000000000000000000008",
63-
"result": "0x000000000000000000000000000000000000000000000000000000005ad789f8",
64-
},
65-
"result": "0x000000000000000000000000000000000000000000000000000000005ad789f8",
66-
"statusCode": 200,
67-
"timestamps": {
68-
"providerDataReceivedUnixMs": 978347471111,
69-
"providerDataRequestedUnixMs": 978347471111,
70-
},
71-
}
72-
`;
73-
74-
exports[`execute function endpoint should return success with additional data requests 2`] = `
75-
{
76-
"data": {
77-
"decimals": "0x0000000000000000000000000000000000000000000000000000000000000008",
7862
"result": "0x000000000000000000000000000000000000000000000000000000005ad789f8",
7963
},
8064
"result": "0x000000000000000000000000000000000000000000000000000000005ad789f8",
@@ -116,7 +100,7 @@ exports[`execute function endpoint should skip additional data requests in case
116100

117101
exports[`execute function-response-selector endpoint should fail with non-existant resultField 1`] = `
118102
{
119-
"errorMessage": "Invalid resultField not found in response",
103+
"errorMessage": "AdapterError: Invalid resultField not found in response",
120104
"statusCode": 400,
121105
"timestamps": {
122106
"providerDataReceivedUnixMs": 0,

packages/sources/view-function-multi-chain/test/integration/adapter.test.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -75,23 +75,6 @@ describe('execute', () => {
7575
expect(response.json()).toMatchSnapshot()
7676
})
7777

78-
it('should return success with additional data requests ', async () => {
79-
const data = {
80-
contract: '0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c',
81-
function: 'function latestAnswer() external view returns (int256)',
82-
network: 'ethereum_mainnet',
83-
data: {
84-
decimals: {
85-
signature: 'function decimals() view returns (uint8)',
86-
},
87-
},
88-
}
89-
mockETHMainnetContractCallResponseSuccess()
90-
const response = await testAdapter.request(data)
91-
expect(response.statusCode).toBe(200)
92-
expect(response.json()).toMatchSnapshot()
93-
})
94-
9578
it('should skip additional data requests in case of missing signature', async () => {
9679
const data = {
9780
contract: '0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c',

yarn.lock

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6205,6 +6205,7 @@ __metadata:
62056205
"@types/node": "npm:22.14.1"
62066206
ethers: "npm:^6.13.2"
62076207
nock: "npm:13.5.6"
6208+
p-limit: "npm:3"
62086209
tslib: "npm:2.4.1"
62096210
typescript: "npm:5.8.3"
62106211
languageName: unknown
@@ -21948,6 +21949,15 @@ __metadata:
2194821949
languageName: node
2194921950
linkType: hard
2195021951

21952+
"p-limit@npm:3, p-limit@npm:^3.0.2, p-limit@npm:^3.1.0":
21953+
version: 3.1.0
21954+
resolution: "p-limit@npm:3.1.0"
21955+
dependencies:
21956+
yocto-queue: "npm:^0.1.0"
21957+
checksum: 10/7c3690c4dbf62ef625671e20b7bdf1cbc9534e83352a2780f165b0d3ceba21907e77ad63401708145ca4e25bfc51636588d89a8c0aeb715e6c37d1c066430360
21958+
languageName: node
21959+
linkType: hard
21960+
2195121961
"p-limit@npm:^1.1.0":
2195221962
version: 1.3.0
2195321963
resolution: "p-limit@npm:1.3.0"
@@ -21966,15 +21976,6 @@ __metadata:
2196621976
languageName: node
2196721977
linkType: hard
2196821978

21969-
"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0":
21970-
version: 3.1.0
21971-
resolution: "p-limit@npm:3.1.0"
21972-
dependencies:
21973-
yocto-queue: "npm:^0.1.0"
21974-
checksum: 10/7c3690c4dbf62ef625671e20b7bdf1cbc9534e83352a2780f165b0d3ceba21907e77ad63401708145ca4e25bfc51636588d89a8c0aeb715e6c37d1c066430360
21975-
languageName: node
21976-
linkType: hard
21977-
2197821979
"p-locate@npm:^2.0.0":
2197921980
version: 2.0.0
2198021981
resolution: "p-locate@npm:2.0.0"

0 commit comments

Comments
 (0)