@chainlink/cre-sdk 1.6.0-alpha.1 → 1.6.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -7
- package/bin/cre-compile.ts +17 -34
- package/dist/generated/chain-selectors/mainnet/evm/ab-mainnet.d.ts +3 -0
- package/dist/generated/chain-selectors/mainnet/evm/ab-mainnet.js +12 -0
- package/dist/generated/chain-selectors/mainnet/evm/adi-mainnet.d.ts +3 -0
- package/dist/generated/chain-selectors/mainnet/evm/adi-mainnet.js +12 -0
- package/dist/generated/chain-selectors/mainnet/evm/edge-mainnet.d.ts +3 -0
- package/dist/generated/chain-selectors/mainnet/evm/edge-mainnet.js +12 -0
- package/dist/generated/chain-selectors/mainnet/evm/everclear-mainnet.d.ts +3 -0
- package/dist/generated/chain-selectors/mainnet/evm/everclear-mainnet.js +12 -0
- package/dist/generated/chain-selectors/mainnet/evm/gate-chain-mainnet.d.ts +3 -0
- package/dist/generated/chain-selectors/mainnet/evm/gate-chain-mainnet.js +12 -0
- package/dist/generated/chain-selectors/mainnet/evm/gate-layer-mainnet.d.ts +3 -0
- package/dist/generated/chain-selectors/mainnet/evm/gate-layer-mainnet.js +12 -0
- package/dist/generated/chain-selectors/mainnet/evm/jovay-mainnet.d.ts +3 -0
- package/dist/generated/chain-selectors/mainnet/evm/jovay-mainnet.js +12 -0
- package/dist/generated/chain-selectors/mainnet/evm/megaeth-mainnet.d.ts +3 -0
- package/dist/generated/chain-selectors/mainnet/evm/megaeth-mainnet.js +12 -0
- package/dist/generated/chain-selectors/mainnet/evm/pharos-mainnet.d.ts +3 -0
- package/dist/generated/chain-selectors/mainnet/evm/pharos-mainnet.js +12 -0
- package/dist/generated/chain-selectors/mainnet/evm/stable-mainnet.d.ts +3 -0
- package/dist/generated/chain-selectors/mainnet/evm/stable-mainnet.js +12 -0
- package/dist/generated/chain-selectors/mainnet/evm/tempo-mainnet.d.ts +3 -0
- package/dist/generated/chain-selectors/mainnet/evm/tempo-mainnet.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/0g-testnet-galileo-1.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/0g-testnet-galileo-1.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/ab-testnet.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/ab-testnet.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/adi-testnet.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/adi-testnet.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/arc-testnet.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/arc-testnet.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/celo-sepolia.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/celo-sepolia.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/dogeos-testnet-chikyu.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/dogeos-testnet-chikyu.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/edge-testnet.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/edge-testnet.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/ethereum-testnet-hoodi-morph.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/ethereum-testnet-hoodi-morph.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/ethereum-testnet-hoodi-taiko-1.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/ethereum-testnet-hoodi-taiko-1.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/ethereum-testnet-hoodi-taiko.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/ethereum-testnet-hoodi-taiko.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/ethereum-testnet-hoodi.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/ethereum-testnet-hoodi.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/ethereum-testnet-sepolia-ronin-1.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/ethereum-testnet-sepolia-ronin-1.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/everclear-testnet-sepolia.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/everclear-testnet-sepolia.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/gate-chain-testnet-meteora.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/gate-chain-testnet-meteora.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/gate-layer-testnet.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/gate-layer-testnet.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/megaeth-testnet-2.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/megaeth-testnet-2.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/pharos-atlantic-testnet.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/pharos-atlantic-testnet.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/robinhood-testnet.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/robinhood-testnet.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/sonic-testnet.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/sonic-testnet.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/stable-testnet.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/stable-testnet.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/tempo-testnet-moderato.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/tempo-testnet-moderato.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/tempo-testnet.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/tempo-testnet.js +12 -0
- package/dist/generated/chain-selectors/testnet/evm/xlayer-testnet.d.ts +3 -0
- package/dist/generated/chain-selectors/testnet/evm/xlayer-testnet.js +12 -0
- package/dist/generated/networks.d.ts +2 -2
- package/dist/generated/networks.js +238 -0
- package/dist/sdk/don-info.d.ts +10 -0
- package/dist/sdk/don-info.js +9 -0
- package/dist/sdk/errors.d.ts +24 -1
- package/dist/sdk/errors.js +46 -1
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.js +2 -0
- package/dist/sdk/report-internals.d.ts +6 -0
- package/dist/sdk/report-internals.js +3 -0
- package/dist/sdk/report.d.ts +38 -0
- package/dist/sdk/report.js +489 -2
- package/dist/sdk/testutils/test-runtime.js +4 -0
- package/package.json +2 -2
- package/scripts/run-standard-tests.sh +3 -3
- package/scripts/run.ts +1 -5
- package/scripts/src/compile-to-js.ts +7 -46
- package/scripts/src/compile-to-wasm.ts +32 -18
- package/scripts/src/compile-workflow.ts +27 -55
- package/scripts/src/compile-cli-args.test.ts +0 -32
- package/scripts/src/compile-cli-args.ts +0 -35
- package/scripts/src/compile-to-js.test.ts +0 -90
- package/scripts/src/typecheck-workflow.test.ts +0 -77
- package/scripts/src/typecheck-workflow.ts +0 -96
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type Environment = {
|
|
2
|
+
chainSelector: bigint;
|
|
3
|
+
registryAddress: string;
|
|
4
|
+
};
|
|
5
|
+
export type Zone = {
|
|
6
|
+
environment: Environment;
|
|
7
|
+
donID: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function productionEnvironment(): Environment;
|
|
10
|
+
export declare function zoneFromEnvironment(environment: Environment, donID: number): Zone;
|
package/dist/sdk/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { SecretRequest } from '../generated/sdk/v1alpha/sdk_pb';
|
|
2
2
|
export declare class DonModeError extends Error {
|
|
3
3
|
constructor();
|
|
4
4
|
}
|
|
@@ -10,3 +10,26 @@ export declare class SecretsError extends Error {
|
|
|
10
10
|
error: string;
|
|
11
11
|
constructor(secretRequest: SecretRequest, error: string);
|
|
12
12
|
}
|
|
13
|
+
export declare class NullReportError extends Error {
|
|
14
|
+
constructor();
|
|
15
|
+
}
|
|
16
|
+
export declare class WrongSignatureCountError extends Error {
|
|
17
|
+
constructor();
|
|
18
|
+
}
|
|
19
|
+
export declare class ParseSignatureError extends Error {
|
|
20
|
+
constructor();
|
|
21
|
+
}
|
|
22
|
+
export declare class RecoverSignerError extends Error {
|
|
23
|
+
constructor();
|
|
24
|
+
}
|
|
25
|
+
export declare class UnknownSignerError extends Error {
|
|
26
|
+
constructor();
|
|
27
|
+
}
|
|
28
|
+
export declare class DuplicateSignerError extends Error {
|
|
29
|
+
constructor();
|
|
30
|
+
}
|
|
31
|
+
export declare class RawReportTooShortError extends Error {
|
|
32
|
+
readonly need: number;
|
|
33
|
+
readonly got: number;
|
|
34
|
+
constructor(need: number, got: number);
|
|
35
|
+
}
|
package/dist/sdk/errors.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Mode } from '../generated/sdk/v1alpha/sdk_pb';
|
|
2
1
|
export class DonModeError extends Error {
|
|
3
2
|
constructor() {
|
|
4
3
|
super('cannot use Runtime inside RunInNodeMode');
|
|
@@ -21,3 +20,49 @@ export class SecretsError extends Error {
|
|
|
21
20
|
this.name = 'SecretsError';
|
|
22
21
|
}
|
|
23
22
|
}
|
|
23
|
+
export class NullReportError extends Error {
|
|
24
|
+
constructor() {
|
|
25
|
+
super('null report');
|
|
26
|
+
this.name = 'NullReportError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class WrongSignatureCountError extends Error {
|
|
30
|
+
constructor() {
|
|
31
|
+
super('wrong number of signatures');
|
|
32
|
+
this.name = 'WrongSignatureCountError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export class ParseSignatureError extends Error {
|
|
36
|
+
constructor() {
|
|
37
|
+
super('failed to parse signature');
|
|
38
|
+
this.name = 'ParseSignatureError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export class RecoverSignerError extends Error {
|
|
42
|
+
constructor() {
|
|
43
|
+
super('failed to recover signer address from signature');
|
|
44
|
+
this.name = 'RecoverSignerError';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export class UnknownSignerError extends Error {
|
|
48
|
+
constructor() {
|
|
49
|
+
super('invalid signature');
|
|
50
|
+
this.name = 'UnknownSignerError';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export class DuplicateSignerError extends Error {
|
|
54
|
+
constructor() {
|
|
55
|
+
super('duplicate signer');
|
|
56
|
+
this.name = 'DuplicateSignerError';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export class RawReportTooShortError extends Error {
|
|
60
|
+
need;
|
|
61
|
+
got;
|
|
62
|
+
constructor(need, got) {
|
|
63
|
+
super(`raw report too short to contain metadata header: need ${need} bytes, got ${got}`);
|
|
64
|
+
this.need = need;
|
|
65
|
+
this.got = got;
|
|
66
|
+
this.name = 'RawReportTooShortError';
|
|
67
|
+
}
|
|
68
|
+
}
|
package/dist/sdk/index.d.ts
CHANGED
package/dist/sdk/index.js
CHANGED
package/dist/sdk/report.d.ts
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
import { type ReportResponse, type ReportResponseJson } from '../generated/sdk/v1alpha/sdk_pb';
|
|
2
|
+
import { type Environment, type Zone } from './don-info';
|
|
3
|
+
import type { BaseRuntime } from './runtime';
|
|
4
|
+
export type ReportParseConfig = {
|
|
5
|
+
acceptedZones?: Zone[];
|
|
6
|
+
acceptedEnvironments?: Environment[];
|
|
7
|
+
skipSignatureVerification?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare const REPORT_METADATA_HEADER_LENGTH = 109;
|
|
10
|
+
export type ReportMetadataHeader = {
|
|
11
|
+
version: number;
|
|
12
|
+
executionId: string;
|
|
13
|
+
timestamp: number;
|
|
14
|
+
donId: number;
|
|
15
|
+
donConfigVersion: number;
|
|
16
|
+
workflowId: string;
|
|
17
|
+
workflowName: string;
|
|
18
|
+
workflowOwner: string;
|
|
19
|
+
reportId: string;
|
|
20
|
+
body: Uint8Array;
|
|
21
|
+
};
|
|
2
22
|
export declare class Report {
|
|
3
23
|
private readonly report;
|
|
24
|
+
private cachedHeader;
|
|
4
25
|
constructor(report: ReportResponse | ReportResponseJson);
|
|
26
|
+
static parse(runtime: BaseRuntime<unknown>, rawReport: Uint8Array, signatures: Uint8Array[], reportContext: Uint8Array, config?: ReportParseConfig): Promise<Report>;
|
|
27
|
+
private parseHeader;
|
|
28
|
+
private verifySignaturesWithConfig;
|
|
29
|
+
seqNr(): bigint;
|
|
30
|
+
configDigest(): Uint8Array;
|
|
31
|
+
reportContext(): Uint8Array;
|
|
32
|
+
rawReport(): Uint8Array;
|
|
33
|
+
version(): number;
|
|
34
|
+
executionId(): string;
|
|
35
|
+
timestamp(): number;
|
|
36
|
+
donId(): number;
|
|
37
|
+
donConfigVersion(): number;
|
|
38
|
+
workflowId(): string;
|
|
39
|
+
workflowName(): string;
|
|
40
|
+
workflowOwner(): string;
|
|
41
|
+
reportId(): string;
|
|
42
|
+
body(): Uint8Array;
|
|
5
43
|
x_generatedCodeOnly_unwrap(): ReportResponse;
|
|
6
44
|
}
|
package/dist/sdk/report.js
CHANGED
|
@@ -1,12 +1,499 @@
|
|
|
1
|
-
import { fromJson } from '@bufbuild/protobuf';
|
|
2
|
-
import {
|
|
1
|
+
import { create, fromJson } from '@bufbuild/protobuf';
|
|
2
|
+
import { BinaryReader, WireType } from '@bufbuild/protobuf/wire';
|
|
3
|
+
import { AnySchema } from '@bufbuild/protobuf/wkt';
|
|
4
|
+
import { AttributedSignatureSchema, AwaitCapabilitiesRequestSchema, CapabilityRequestSchema, ReportResponseSchema, } from '../generated/sdk/v1alpha/sdk_pb';
|
|
5
|
+
import { concatHex, getAddress, hexToBytes, keccak256, recoverAddress, toHex } from 'viem';
|
|
6
|
+
import { productionEnvironment } from './don-info';
|
|
7
|
+
import { DuplicateSignerError, NullReportError, ParseSignatureError, RawReportTooShortError, RecoverSignerError, UnknownSignerError, WrongSignatureCountError, } from './errors';
|
|
8
|
+
import { donInfoCache } from './report-internals';
|
|
9
|
+
const GET_DON_SELECTOR = new Uint8Array([0x23, 0x53, 0x74, 0x05]);
|
|
10
|
+
const GET_NODES_BY_P2P_IDS_SELECTOR = new Uint8Array([0x05, 0xa5, 0x19, 0x66]);
|
|
11
|
+
function cacheKey(env, donID) {
|
|
12
|
+
return `${env.chainSelector.toString()}:${donID}`;
|
|
13
|
+
}
|
|
14
|
+
function normalizeRegistryHex(addr) {
|
|
15
|
+
return addr
|
|
16
|
+
.trim()
|
|
17
|
+
.replace(/^0[xX]/, '')
|
|
18
|
+
.toLowerCase();
|
|
19
|
+
}
|
|
20
|
+
function isProductionEnvironmentForReport(env) {
|
|
21
|
+
const pe = productionEnvironment();
|
|
22
|
+
return (env.chainSelector === pe.chainSelector &&
|
|
23
|
+
normalizeRegistryHex(env.registryAddress) === normalizeRegistryHex(pe.registryAddress));
|
|
24
|
+
}
|
|
25
|
+
function decodeRegistryAddress(registryAddress) {
|
|
26
|
+
const hex = normalizeRegistryHex(registryAddress);
|
|
27
|
+
if (hex.length !== 40) {
|
|
28
|
+
throw new Error(`invalid registry address ${JSON.stringify(registryAddress)}`);
|
|
29
|
+
}
|
|
30
|
+
return hexToBytes(`0x${hex}`);
|
|
31
|
+
}
|
|
32
|
+
function padUint256(v) {
|
|
33
|
+
const n = typeof v === 'bigint' ? v : BigInt(v);
|
|
34
|
+
const b = new Uint8Array(32);
|
|
35
|
+
const view = new DataView(b.buffer);
|
|
36
|
+
view.setBigUint64(24, n, false);
|
|
37
|
+
return b;
|
|
38
|
+
}
|
|
39
|
+
function bytesToBigIntBE(word) {
|
|
40
|
+
let x = 0n;
|
|
41
|
+
for (let i = 0; i < word.length; i++) {
|
|
42
|
+
x = (x << 8n) | BigInt(word[i]);
|
|
43
|
+
}
|
|
44
|
+
return x;
|
|
45
|
+
}
|
|
46
|
+
function readUint256AsInt(word) {
|
|
47
|
+
const b = bytesToBigIntBE(word);
|
|
48
|
+
if (b > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
49
|
+
throw new Error('ABI uint256 value too large for Number');
|
|
50
|
+
}
|
|
51
|
+
return Number(b);
|
|
52
|
+
}
|
|
53
|
+
function protoVarint(v) {
|
|
54
|
+
const out = [];
|
|
55
|
+
let n = typeof v === 'bigint' ? v : BigInt(v);
|
|
56
|
+
while (n >= 128n) {
|
|
57
|
+
out.push(Number(n & 0x7fn) | 0x80);
|
|
58
|
+
n >>= 7n;
|
|
59
|
+
}
|
|
60
|
+
out.push(Number(n));
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
function protoTag(field, wireType) {
|
|
64
|
+
return protoVarint((field << 3) | wireType);
|
|
65
|
+
}
|
|
66
|
+
function protoLenBytes(data) {
|
|
67
|
+
return [...protoVarint(data.length), ...data];
|
|
68
|
+
}
|
|
69
|
+
function buildCallContractRequestBytes(registryAddr, callData) {
|
|
70
|
+
const callMsg = new Uint8Array([
|
|
71
|
+
...protoTag(2, 2),
|
|
72
|
+
...protoLenBytes(registryAddr),
|
|
73
|
+
...protoTag(3, 2),
|
|
74
|
+
...protoLenBytes(callData),
|
|
75
|
+
]);
|
|
76
|
+
const bigInt = new Uint8Array([
|
|
77
|
+
...protoTag(1, 2),
|
|
78
|
+
...protoLenBytes(new Uint8Array([0x03])),
|
|
79
|
+
...protoTag(2, 0),
|
|
80
|
+
...protoVarint(0xffffffffffffffffn),
|
|
81
|
+
]);
|
|
82
|
+
return new Uint8Array([
|
|
83
|
+
...protoTag(2, 2),
|
|
84
|
+
...protoLenBytes(bigInt),
|
|
85
|
+
...protoTag(1, 2),
|
|
86
|
+
...protoLenBytes(callMsg),
|
|
87
|
+
]);
|
|
88
|
+
}
|
|
89
|
+
function decodeCallContractReplyData(bytes) {
|
|
90
|
+
const reader = new BinaryReader(bytes);
|
|
91
|
+
while (reader.pos < reader.len) {
|
|
92
|
+
const [fieldNo, wireType] = reader.tag();
|
|
93
|
+
if (fieldNo === 1 && wireType === WireType.LengthDelimited) {
|
|
94
|
+
return reader.bytes();
|
|
95
|
+
}
|
|
96
|
+
reader.skip(wireType);
|
|
97
|
+
}
|
|
98
|
+
throw new Error('data field not found in CallContractReply');
|
|
99
|
+
}
|
|
100
|
+
const CALL_CONTRACT_REQUEST_TYPE_URL = 'type.googleapis.com/capabilities.blockchain.evm.v1alpha.CallContractRequest';
|
|
101
|
+
function callContract(runtime, capID, registryAddr, callData) {
|
|
102
|
+
const reqBytes = buildCallContractRequestBytes(registryAddr, callData);
|
|
103
|
+
const anyPayload = create(AnySchema, {
|
|
104
|
+
typeUrl: CALL_CONTRACT_REQUEST_TYPE_URL,
|
|
105
|
+
value: reqBytes,
|
|
106
|
+
});
|
|
107
|
+
// biome-ignore lint/suspicious/noExplicitAny: intentional bypass of typed callCapability to avoid EVM schema imports
|
|
108
|
+
const rt = runtime;
|
|
109
|
+
const callbackId = rt.nextCallId++;
|
|
110
|
+
const req = create(CapabilityRequestSchema, {
|
|
111
|
+
id: capID,
|
|
112
|
+
method: 'CallContract',
|
|
113
|
+
payload: anyPayload,
|
|
114
|
+
callbackId,
|
|
115
|
+
});
|
|
116
|
+
if (!rt.helpers.call(req)) {
|
|
117
|
+
throw new Error(`EVM capability '${capID}' not found`);
|
|
118
|
+
}
|
|
119
|
+
// Must use create() — the real WASM bridge serializes this as a protobuf message and
|
|
120
|
+
// requires a proper typed message (with $typeName). A plain { ids: [...] } object works
|
|
121
|
+
// in the test runtime but silently breaks in simulation.
|
|
122
|
+
const awaitResp = rt.helpers.await(create(AwaitCapabilitiesRequestSchema, { ids: [callbackId] }), rt.maxResponseSize);
|
|
123
|
+
const capResp = awaitResp?.responses?.[callbackId];
|
|
124
|
+
if (!capResp) {
|
|
125
|
+
throw new Error(`no response from EVM capability '${capID}'`);
|
|
126
|
+
}
|
|
127
|
+
const response = capResp.response;
|
|
128
|
+
if (response.case === 'error') {
|
|
129
|
+
throw new Error(response.value);
|
|
130
|
+
}
|
|
131
|
+
if (response.case !== 'payload') {
|
|
132
|
+
throw new Error(`unexpected response '${response.case}' from EVM capability '${capID}'`);
|
|
133
|
+
}
|
|
134
|
+
return decodeCallContractReplyData(response.value.value);
|
|
135
|
+
}
|
|
136
|
+
function encodeGetDONCalldata(donID) {
|
|
137
|
+
const padded = new Uint8Array(32);
|
|
138
|
+
const view = new DataView(padded.buffer);
|
|
139
|
+
view.setUint32(28, donID >>> 0, false);
|
|
140
|
+
const out = new Uint8Array(4 + 32);
|
|
141
|
+
out.set(GET_DON_SELECTOR, 0);
|
|
142
|
+
out.set(padded, 4);
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
function concatBytes(parts) {
|
|
146
|
+
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
147
|
+
const r = new Uint8Array(total);
|
|
148
|
+
let o = 0;
|
|
149
|
+
for (const p of parts) {
|
|
150
|
+
r.set(p, o);
|
|
151
|
+
o += p.length;
|
|
152
|
+
}
|
|
153
|
+
return r;
|
|
154
|
+
}
|
|
155
|
+
function encodeGetNodesByP2PIdsCalldata(p2pIds) {
|
|
156
|
+
const chunks = [
|
|
157
|
+
GET_NODES_BY_P2P_IDS_SELECTOR,
|
|
158
|
+
padUint256(32),
|
|
159
|
+
padUint256(p2pIds.length),
|
|
160
|
+
];
|
|
161
|
+
for (const id of p2pIds) {
|
|
162
|
+
if (id.length !== 32) {
|
|
163
|
+
throw new Error('p2p id must be 32 bytes');
|
|
164
|
+
}
|
|
165
|
+
chunks.push(new Uint8Array(id));
|
|
166
|
+
}
|
|
167
|
+
return concatBytes(chunks);
|
|
168
|
+
}
|
|
169
|
+
const NODE_TUPLE_HEAD_SIZE = 288;
|
|
170
|
+
function fetchDONInfo(runtime, env, donID) {
|
|
171
|
+
const key = cacheKey(env, donID);
|
|
172
|
+
const hit = donInfoCache.get(key);
|
|
173
|
+
if (hit) {
|
|
174
|
+
return hit;
|
|
175
|
+
}
|
|
176
|
+
const registryAddr = decodeRegistryAddress(env.registryAddress);
|
|
177
|
+
const capID = `evm:ChainSelector:${env.chainSelector.toString()}@1.0.0`;
|
|
178
|
+
const getDONABI = callContract(runtime, capID, registryAddr, encodeGetDONCalldata(donID));
|
|
179
|
+
if (getDONABI.length < 224) {
|
|
180
|
+
throw new Error(`getDON ABI response too short: ${getDONABI.length} bytes`);
|
|
181
|
+
}
|
|
182
|
+
const f = readUint256AsInt(getDONABI.subarray(96, 128));
|
|
183
|
+
const tupleStart = 32;
|
|
184
|
+
const nodeP2PIdsPtr = readUint256AsInt(getDONABI.subarray(192, 224));
|
|
185
|
+
const nodeCountOff = tupleStart + nodeP2PIdsPtr;
|
|
186
|
+
if (nodeCountOff + 32 > getDONABI.length) {
|
|
187
|
+
throw new Error('getDON ABI: nodeP2PIds pointer out of range');
|
|
188
|
+
}
|
|
189
|
+
const nodeCount = readUint256AsInt(getDONABI.subarray(nodeCountOff, nodeCountOff + 32));
|
|
190
|
+
if (nodeCountOff + 32 + nodeCount * 32 > getDONABI.length) {
|
|
191
|
+
throw new Error('getDON ABI: nodeP2PIds data out of range');
|
|
192
|
+
}
|
|
193
|
+
const nodeP2PIds = [];
|
|
194
|
+
for (let i = 0; i < nodeCount; i++) {
|
|
195
|
+
const start = nodeCountOff + 32 + i * 32;
|
|
196
|
+
nodeP2PIds.push(getDONABI.subarray(start, start + 32));
|
|
197
|
+
}
|
|
198
|
+
if (nodeCount === 0) {
|
|
199
|
+
const info = { f, signers: new Map() };
|
|
200
|
+
donInfoCache.set(key, info);
|
|
201
|
+
return info;
|
|
202
|
+
}
|
|
203
|
+
const getNodesABI = callContract(runtime, capID, registryAddr, encodeGetNodesByP2PIdsCalldata(nodeP2PIds));
|
|
204
|
+
if (getNodesABI.length < 64) {
|
|
205
|
+
throw new Error(`getNodesByP2PIds ABI response too short: ${getNodesABI.length} bytes`);
|
|
206
|
+
}
|
|
207
|
+
const outerPtr = readUint256AsInt(getNodesABI.subarray(0, 32));
|
|
208
|
+
if (outerPtr + 32 > getNodesABI.length) {
|
|
209
|
+
throw new Error('getNodesByP2PIds ABI: outer pointer out of range');
|
|
210
|
+
}
|
|
211
|
+
const returnedCount = readUint256AsInt(getNodesABI.subarray(outerPtr, outerPtr + 32));
|
|
212
|
+
const signers = new Map();
|
|
213
|
+
for (let i = 0; i < returnedCount; i++) {
|
|
214
|
+
const elemPtrOff = outerPtr + 32 + i * 32;
|
|
215
|
+
if (elemPtrOff + 32 > getNodesABI.length) {
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
const elemPtr = readUint256AsInt(getNodesABI.subarray(elemPtrOff, elemPtrOff + 32));
|
|
219
|
+
const tupleBase = outerPtr + 32 + elemPtr;
|
|
220
|
+
if (tupleBase + NODE_TUPLE_HEAD_SIZE > getNodesABI.length) {
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
const nodeOperatorId = Number(bytesToBigIntBE(getNodesABI.subarray(tupleBase, tupleBase + 32)) & 0xffffffffn);
|
|
224
|
+
const signerSlot = tupleBase + 3 * 32;
|
|
225
|
+
const addrBytes = getNodesABI.subarray(signerSlot, signerSlot + 20);
|
|
226
|
+
const addr = getAddress(toHex(addrBytes));
|
|
227
|
+
signers.set(addr, nodeOperatorId);
|
|
228
|
+
}
|
|
229
|
+
const info = { f, signers };
|
|
230
|
+
if (donID === 1 && isProductionEnvironmentForReport(env)) {
|
|
231
|
+
const patch = (hexAddr, id) => {
|
|
232
|
+
info.signers.set(getAddress(hexAddr), id);
|
|
233
|
+
};
|
|
234
|
+
patch('0xcdf20f8ffd41b02c680988b20e68735cc8c1ca17', 5 + 1);
|
|
235
|
+
patch('0xff9b062fccb2f042311343048b9518068370f837', 4 + 1);
|
|
236
|
+
patch('0xde5cd1dd4300a0b4854f8223add60d20e1dfe21b', 1 + 1);
|
|
237
|
+
patch('0x4d6cfd44f94408a39fb1af94a53c107a730ba161', 9 + 1);
|
|
238
|
+
patch('0xf3baa9a99b5ad64f50779f449bac83baac8bfdb6', 0 + 1);
|
|
239
|
+
patch('0xd7f22fb5382ff477d2ff5c702cab0ef8abf18233', 7 + 1);
|
|
240
|
+
patch('0x4d7d71c7e584cfa1f5c06275e5d283b9d3176924', 8 + 1);
|
|
241
|
+
patch('0x1a89c98e75983ec384ad8e83eaf7d0176eeaf155', 3 + 1);
|
|
242
|
+
patch('0x4f99b550623e77b807df7cbed9c79d55e1163b48', 2 + 1);
|
|
243
|
+
patch('0xe55fcaf921e76c6bbcf9415bba12b1236f07b0c3', 6 + 1);
|
|
244
|
+
}
|
|
245
|
+
donInfoCache.set(key, info);
|
|
246
|
+
return info;
|
|
247
|
+
}
|
|
248
|
+
function computeReportHash(rawReport, reportContext) {
|
|
249
|
+
const innerHash = keccak256(toHex(rawReport));
|
|
250
|
+
return keccak256(concatHex([innerHash, toHex(reportContext)]));
|
|
251
|
+
}
|
|
252
|
+
function addressKeyNo0x(addr) {
|
|
253
|
+
return addr.slice(2).toLowerCase();
|
|
254
|
+
}
|
|
255
|
+
async function verifySigs(report, f, signers) {
|
|
256
|
+
const required = f + 1;
|
|
257
|
+
const sigs = report.sigs;
|
|
258
|
+
if (sigs.length < required) {
|
|
259
|
+
throw new WrongSignatureCountError();
|
|
260
|
+
}
|
|
261
|
+
const reportHash = computeReportHash(report.rawReport, report.reportContext);
|
|
262
|
+
const seen = new Set();
|
|
263
|
+
const accepted = [];
|
|
264
|
+
const skipErrs = [];
|
|
265
|
+
for (let i = 0; i < sigs.length; i++) {
|
|
266
|
+
if (accepted.length === required) {
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
const attrSig = sigs[i];
|
|
270
|
+
const sigBytes = new Uint8Array(attrSig.signature);
|
|
271
|
+
if (sigBytes.length !== 65) {
|
|
272
|
+
skipErrs.push(new ParseSignatureError());
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const normalized = new Uint8Array(sigBytes);
|
|
276
|
+
if (normalized[64] === 27 || normalized[64] === 28) {
|
|
277
|
+
normalized[64] -= 27;
|
|
278
|
+
}
|
|
279
|
+
let recovered;
|
|
280
|
+
try {
|
|
281
|
+
recovered = await recoverAddress({
|
|
282
|
+
hash: reportHash,
|
|
283
|
+
signature: toHex(normalized),
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
skipErrs.push(new RecoverSignerError());
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const key = addressKeyNo0x(recovered);
|
|
291
|
+
if (seen.has(key)) {
|
|
292
|
+
skipErrs.push(new DuplicateSignerError());
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
seen.add(key);
|
|
296
|
+
const nodeOperatorId = signers.get(key);
|
|
297
|
+
if (nodeOperatorId === undefined) {
|
|
298
|
+
skipErrs.push(new UnknownSignerError());
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
attrSig.signerId = nodeOperatorId;
|
|
302
|
+
accepted.push(attrSig);
|
|
303
|
+
}
|
|
304
|
+
if (accepted.length < required) {
|
|
305
|
+
if (skipErrs.length > 0) {
|
|
306
|
+
throw new AggregateError(skipErrs);
|
|
307
|
+
}
|
|
308
|
+
throw new WrongSignatureCountError();
|
|
309
|
+
}
|
|
310
|
+
report.sigs = accepted;
|
|
311
|
+
}
|
|
312
|
+
function mergeReportParseConfig(overrides) {
|
|
313
|
+
return {
|
|
314
|
+
acceptedZones: overrides?.acceptedZones ?? [],
|
|
315
|
+
acceptedEnvironments: overrides !== undefined ? (overrides.acceptedEnvironments ?? []) : [productionEnvironment()],
|
|
316
|
+
skipSignatureVerification: overrides?.skipSignatureVerification ?? false,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function normalizeDonSigners(signers) {
|
|
320
|
+
const out = new Map();
|
|
321
|
+
for (const [addr, id] of signers) {
|
|
322
|
+
out.set(addr.slice(2).toLowerCase(), id);
|
|
323
|
+
}
|
|
324
|
+
return out;
|
|
325
|
+
}
|
|
326
|
+
export const REPORT_METADATA_HEADER_LENGTH = 109;
|
|
327
|
+
const REPORT_METADATA_OFFSETS = {
|
|
328
|
+
version: 0,
|
|
329
|
+
versionSize: 1,
|
|
330
|
+
executionId: 1,
|
|
331
|
+
executionIdSize: 32,
|
|
332
|
+
timestamp: 33,
|
|
333
|
+
timestampSize: 4,
|
|
334
|
+
donId: 37,
|
|
335
|
+
donIdSize: 4,
|
|
336
|
+
donConfigVersion: 41,
|
|
337
|
+
donConfigVersionSize: 4,
|
|
338
|
+
workflowId: 45,
|
|
339
|
+
workflowIdSize: 32,
|
|
340
|
+
workflowName: 77,
|
|
341
|
+
workflowNameSize: 10,
|
|
342
|
+
workflowOwner: 87,
|
|
343
|
+
workflowOwnerSize: 20,
|
|
344
|
+
reportId: 107,
|
|
345
|
+
reportIdSize: 2,
|
|
346
|
+
bodyStart: 109,
|
|
347
|
+
};
|
|
348
|
+
function encodeHexLower(bytes) {
|
|
349
|
+
let out = '';
|
|
350
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
351
|
+
out += bytes[i].toString(16).padStart(2, '0');
|
|
352
|
+
}
|
|
353
|
+
return out;
|
|
354
|
+
}
|
|
355
|
+
function readUint32BE(raw, offset) {
|
|
356
|
+
return new DataView(raw.buffer, raw.byteOffset + offset, 4).getUint32(0, false);
|
|
357
|
+
}
|
|
358
|
+
function parseReportMetadataHeader(raw) {
|
|
359
|
+
if (raw === undefined || raw === null) {
|
|
360
|
+
throw new NullReportError();
|
|
361
|
+
}
|
|
362
|
+
if (raw.length < REPORT_METADATA_HEADER_LENGTH) {
|
|
363
|
+
throw new RawReportTooShortError(REPORT_METADATA_HEADER_LENGTH, raw.length);
|
|
364
|
+
}
|
|
365
|
+
const o = REPORT_METADATA_OFFSETS;
|
|
366
|
+
const workflowNameBytes = raw.subarray(o.workflowName, o.workflowName + o.workflowNameSize);
|
|
367
|
+
return {
|
|
368
|
+
version: raw[o.version],
|
|
369
|
+
executionId: encodeHexLower(raw.subarray(o.executionId, o.executionId + o.executionIdSize)),
|
|
370
|
+
timestamp: readUint32BE(raw, o.timestamp),
|
|
371
|
+
donId: readUint32BE(raw, o.donId),
|
|
372
|
+
donConfigVersion: readUint32BE(raw, o.donConfigVersion),
|
|
373
|
+
workflowId: encodeHexLower(raw.subarray(o.workflowId, o.workflowId + o.workflowIdSize)),
|
|
374
|
+
workflowName: new TextDecoder('utf-8', { fatal: false }).decode(workflowNameBytes),
|
|
375
|
+
workflowOwner: encodeHexLower(raw.subarray(o.workflowOwner, o.workflowOwner + o.workflowOwnerSize)),
|
|
376
|
+
reportId: encodeHexLower(raw.subarray(o.reportId, o.reportId + o.reportIdSize)),
|
|
377
|
+
body: raw.subarray(REPORT_METADATA_HEADER_LENGTH),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
3
380
|
export class Report {
|
|
4
381
|
report;
|
|
382
|
+
cachedHeader;
|
|
5
383
|
constructor(report) {
|
|
6
384
|
this.report = report.$typeName
|
|
7
385
|
? report
|
|
8
386
|
: fromJson(ReportResponseSchema, report);
|
|
9
387
|
}
|
|
388
|
+
static async parse(runtime, rawReport, signatures, reportContext, config) {
|
|
389
|
+
const configDigest = reportContext.length >= 32 ? reportContext.slice(0, 32) : new Uint8Array(32);
|
|
390
|
+
const seqNr = reportContext.length >= 40
|
|
391
|
+
? new DataView(reportContext.buffer, reportContext.byteOffset + 32, 8).getBigUint64(0, false)
|
|
392
|
+
: 0n;
|
|
393
|
+
const reportResponse = create(ReportResponseSchema, {
|
|
394
|
+
configDigest,
|
|
395
|
+
seqNr,
|
|
396
|
+
reportContext,
|
|
397
|
+
rawReport,
|
|
398
|
+
sigs: signatures.map((signature) => create(AttributedSignatureSchema, { signature, signerId: 0 })),
|
|
399
|
+
});
|
|
400
|
+
const merged = mergeReportParseConfig(config);
|
|
401
|
+
const report = new Report(reportResponse);
|
|
402
|
+
if (merged.skipSignatureVerification) {
|
|
403
|
+
report.donId();
|
|
404
|
+
return report;
|
|
405
|
+
}
|
|
406
|
+
await report.verifySignaturesWithConfig(runtime, merged);
|
|
407
|
+
return report;
|
|
408
|
+
}
|
|
409
|
+
parseHeader() {
|
|
410
|
+
if (this.cachedHeader !== undefined) {
|
|
411
|
+
return this.cachedHeader;
|
|
412
|
+
}
|
|
413
|
+
this.cachedHeader = parseReportMetadataHeader(this.report.rawReport);
|
|
414
|
+
return this.cachedHeader;
|
|
415
|
+
}
|
|
416
|
+
async verifySignaturesWithConfig(runtime, config) {
|
|
417
|
+
const donId = this.donId();
|
|
418
|
+
const candidates = [];
|
|
419
|
+
for (const z of config.acceptedZones) {
|
|
420
|
+
if (z.donID === donId) {
|
|
421
|
+
candidates.push(z.environment);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
candidates.push(...config.acceptedEnvironments);
|
|
425
|
+
if (candidates.length === 0) {
|
|
426
|
+
throw new Error(`DON ID ${donId} is not in accepted zones`);
|
|
427
|
+
}
|
|
428
|
+
const fetchFailures = [];
|
|
429
|
+
let lastVerifyErr = null;
|
|
430
|
+
for (const env of candidates) {
|
|
431
|
+
let info;
|
|
432
|
+
try {
|
|
433
|
+
info = fetchDONInfo(runtime, env, donId);
|
|
434
|
+
}
|
|
435
|
+
catch (err) {
|
|
436
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
437
|
+
fetchFailures.push(new Error(`could not read from chain ${env.chainSelector} contract ${env.registryAddress}: ${msg}`));
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
await verifySigs(this.report, info.f, normalizeDonSigners(info.signers));
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
catch (err) {
|
|
445
|
+
lastVerifyErr = err instanceof Error ? err : new Error(String(err));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (fetchFailures.length > 0) {
|
|
449
|
+
throw new AggregateError(fetchFailures, fetchFailures.map((e) => e.message).join('\n'));
|
|
450
|
+
}
|
|
451
|
+
if (lastVerifyErr !== null) {
|
|
452
|
+
throw lastVerifyErr;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
seqNr() {
|
|
456
|
+
return this.report.seqNr;
|
|
457
|
+
}
|
|
458
|
+
configDigest() {
|
|
459
|
+
return this.report.configDigest;
|
|
460
|
+
}
|
|
461
|
+
reportContext() {
|
|
462
|
+
return this.report.reportContext;
|
|
463
|
+
}
|
|
464
|
+
rawReport() {
|
|
465
|
+
return this.report.rawReport;
|
|
466
|
+
}
|
|
467
|
+
version() {
|
|
468
|
+
return this.parseHeader().version;
|
|
469
|
+
}
|
|
470
|
+
executionId() {
|
|
471
|
+
return this.parseHeader().executionId;
|
|
472
|
+
}
|
|
473
|
+
timestamp() {
|
|
474
|
+
return this.parseHeader().timestamp;
|
|
475
|
+
}
|
|
476
|
+
donId() {
|
|
477
|
+
return this.parseHeader().donId;
|
|
478
|
+
}
|
|
479
|
+
donConfigVersion() {
|
|
480
|
+
return this.parseHeader().donConfigVersion;
|
|
481
|
+
}
|
|
482
|
+
workflowId() {
|
|
483
|
+
return this.parseHeader().workflowId;
|
|
484
|
+
}
|
|
485
|
+
workflowName() {
|
|
486
|
+
return this.parseHeader().workflowName;
|
|
487
|
+
}
|
|
488
|
+
workflowOwner() {
|
|
489
|
+
return this.parseHeader().workflowOwner;
|
|
490
|
+
}
|
|
491
|
+
reportId() {
|
|
492
|
+
return this.parseHeader().reportId;
|
|
493
|
+
}
|
|
494
|
+
body() {
|
|
495
|
+
return this.parseHeader().body;
|
|
496
|
+
}
|
|
10
497
|
// x_generatedCodeOnly_unwrap is meant to be used by the code generator only.
|
|
11
498
|
x_generatedCodeOnly_unwrap() {
|
|
12
499
|
return this.report;
|
|
@@ -166,6 +166,10 @@ function createTestRuntimeHelpers(registry, secrets, testWriter, state, _maxResp
|
|
|
166
166
|
return true;
|
|
167
167
|
},
|
|
168
168
|
await(request, maxResponseSizeBytes) {
|
|
169
|
+
if (request.$typeName !==
|
|
170
|
+
'sdk.v1alpha.AwaitCapabilitiesRequest') {
|
|
171
|
+
throw new Error('await: expected a typed AwaitCapabilitiesRequest (created via create(AwaitCapabilitiesRequestSchema, ...)); got a plain object. The real WASM bridge serializes this to binary and will fail with a plain object.');
|
|
172
|
+
}
|
|
169
173
|
const responses = {};
|
|
170
174
|
for (const id of request.ids) {
|
|
171
175
|
const resp = pendingCalls.get(id);
|