@aztec/foundation 0.86.0 → 0.87.0
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/dest/array/array.d.ts.map +1 -1
- package/dest/bigint/index.d.ts +2 -0
- package/dest/bigint/index.d.ts.map +1 -1
- package/dest/bigint/index.js +6 -0
- package/dest/bigint-buffer/index.d.ts +0 -2
- package/dest/bigint-buffer/index.d.ts.map +1 -1
- package/dest/buffer/buffer16.d.ts +80 -0
- package/dest/buffer/buffer16.d.ts.map +1 -0
- package/dest/buffer/buffer16.js +100 -0
- package/dest/buffer/buffer32.d.ts +3 -7
- package/dest/buffer/buffer32.d.ts.map +1 -1
- package/dest/buffer/buffer32.js +6 -6
- package/dest/buffer/index.d.ts +1 -0
- package/dest/buffer/index.d.ts.map +1 -1
- package/dest/buffer/index.js +1 -0
- package/dest/collection/array.d.ts +0 -2
- package/dest/collection/array.d.ts.map +1 -1
- package/dest/collection/object.d.ts +2 -0
- package/dest/collection/object.d.ts.map +1 -1
- package/dest/collection/object.js +8 -0
- package/dest/config/env_var.d.ts +1 -1
- package/dest/config/env_var.d.ts.map +1 -1
- package/dest/crypto/aes128/index.d.ts +2 -4
- package/dest/crypto/aes128/index.d.ts.map +1 -1
- package/dest/crypto/ecdsa/index.d.ts +0 -2
- package/dest/crypto/ecdsa/index.d.ts.map +1 -1
- package/dest/crypto/ecdsa/signature.d.ts +1 -3
- package/dest/crypto/ecdsa/signature.d.ts.map +1 -1
- package/dest/crypto/grumpkin/index.d.ts +0 -2
- package/dest/crypto/grumpkin/index.d.ts.map +1 -1
- package/dest/crypto/keccak/index.d.ts +2 -4
- package/dest/crypto/keccak/index.d.ts.map +1 -1
- package/dest/crypto/keys/index.d.ts +0 -2
- package/dest/crypto/keys/index.d.ts.map +1 -1
- package/dest/crypto/pedersen/pedersen.noble.d.ts +2 -4
- package/dest/crypto/pedersen/pedersen.noble.d.ts.map +1 -1
- package/dest/crypto/pedersen/pedersen.wasm.d.ts +2 -4
- package/dest/crypto/pedersen/pedersen.wasm.d.ts.map +1 -1
- package/dest/crypto/poseidon/index.d.ts +0 -2
- package/dest/crypto/poseidon/index.d.ts.map +1 -1
- package/dest/crypto/random/index.d.ts +1 -3
- package/dest/crypto/random/index.d.ts.map +1 -1
- package/dest/crypto/random/randomness_singleton.d.ts +0 -2
- package/dest/crypto/random/randomness_singleton.d.ts.map +1 -1
- package/dest/crypto/schnorr/signature.d.ts +3 -5
- package/dest/crypto/schnorr/signature.d.ts.map +1 -1
- package/dest/crypto/secp256k1/index.d.ts +4 -6
- package/dest/crypto/secp256k1/index.d.ts.map +1 -1
- package/dest/crypto/secp256k1-signer/utils.d.ts +0 -2
- package/dest/crypto/secp256k1-signer/utils.d.ts.map +1 -1
- package/dest/crypto/serialize.d.ts +4 -6
- package/dest/crypto/serialize.d.ts.map +1 -1
- package/dest/crypto/sha256/index.d.ts +2 -4
- package/dest/crypto/sha256/index.d.ts.map +1 -1
- package/dest/crypto/sha512/index.d.ts +1 -3
- package/dest/crypto/sha512/index.d.ts.map +1 -1
- package/dest/crypto/signature/index.d.ts +0 -2
- package/dest/crypto/signature/index.d.ts.map +1 -1
- package/dest/crypto/sync/pedersen/index.d.ts +2 -4
- package/dest/crypto/sync/pedersen/index.d.ts.map +1 -1
- package/dest/crypto/sync/poseidon/index.d.ts +0 -2
- package/dest/crypto/sync/poseidon/index.d.ts.map +1 -1
- package/dest/decorators/memoize.d.ts.map +1 -1
- package/dest/eth-address/index.d.ts +2 -5
- package/dest/eth-address/index.d.ts.map +1 -1
- package/dest/eth-signature/eth_signature.d.ts +0 -2
- package/dest/eth-signature/eth_signature.d.ts.map +1 -1
- package/dest/fields/coordinate.d.ts +0 -2
- package/dest/fields/coordinate.d.ts.map +1 -1
- package/dest/fields/fields.d.ts +0 -3
- package/dest/fields/fields.d.ts.map +1 -1
- package/dest/fields/point.d.ts +2 -4
- package/dest/fields/point.d.ts.map +1 -1
- package/dest/fields/point.js +2 -2
- package/dest/iterable/filter.js +1 -1
- package/dest/iterable/map.js +1 -1
- package/dest/json-rpc/client/fetch.d.ts +2 -2
- package/dest/json-rpc/client/fetch.d.ts.map +1 -1
- package/dest/json-rpc/client/fetch.js +16 -30
- package/dest/json-rpc/client/safe_json_rpc_client.d.ts +7 -3
- package/dest/json-rpc/client/safe_json_rpc_client.d.ts.map +1 -1
- package/dest/json-rpc/client/safe_json_rpc_client.js +135 -13
- package/dest/json-rpc/client/undici.d.ts.map +1 -1
- package/dest/json-rpc/client/undici.js +6 -7
- package/dest/json-rpc/convert.d.ts +1 -1
- package/dest/json-rpc/convert.d.ts.map +1 -1
- package/dest/json-rpc/convert.js +1 -1
- package/dest/json-rpc/errors.d.ts +4 -0
- package/dest/json-rpc/errors.d.ts.map +1 -0
- package/dest/json-rpc/errors.js +6 -0
- package/dest/json-rpc/fixtures/class_a.d.ts +5 -3
- package/dest/json-rpc/fixtures/class_a.d.ts.map +1 -1
- package/dest/json-rpc/fixtures/class_b.d.ts +5 -3
- package/dest/json-rpc/fixtures/class_b.d.ts.map +1 -1
- package/dest/json-rpc/index.d.ts +1 -0
- package/dest/json-rpc/index.d.ts.map +1 -1
- package/dest/json-rpc/index.js +1 -0
- package/dest/json-rpc/js_utils.d.ts.map +1 -1
- package/dest/json-rpc/server/safe_json_rpc_server.d.ts +2 -1
- package/dest/json-rpc/server/safe_json_rpc_server.d.ts.map +1 -1
- package/dest/json-rpc/server/safe_json_rpc_server.js +112 -39
- package/dest/json-rpc/test/integration.d.ts +0 -1
- package/dest/json-rpc/test/integration.d.ts.map +1 -1
- package/dest/log/console.d.ts.map +1 -1
- package/dest/log/gcloud-logger-config.d.ts.map +1 -1
- package/dest/log/gcloud-logger-config.js +0 -4
- package/dest/log/pino-logger.d.ts +1 -2
- package/dest/log/pino-logger.d.ts.map +1 -1
- package/dest/message/index.d.ts.map +1 -1
- package/dest/mutex/mutex_database.d.ts.map +1 -1
- package/dest/queue/bounded_serial_queue.d.ts.map +1 -1
- package/dest/schemas/api.d.ts.map +1 -1
- package/dest/schemas/parse.js +1 -1
- package/dest/schemas/schemas.d.ts +3 -5
- package/dest/schemas/schemas.d.ts.map +1 -1
- package/dest/schemas/utils.d.ts +1 -3
- package/dest/schemas/utils.d.ts.map +1 -1
- package/dest/serialize/buffer_reader.d.ts +9 -2
- package/dest/serialize/buffer_reader.d.ts.map +1 -1
- package/dest/serialize/buffer_reader.js +15 -2
- package/dest/serialize/field_reader.d.ts +2 -1
- package/dest/serialize/field_reader.d.ts.map +1 -1
- package/dest/serialize/field_reader.js +4 -1
- package/dest/serialize/free_funcs.d.ts +15 -9
- package/dest/serialize/free_funcs.d.ts.map +1 -1
- package/dest/serialize/free_funcs.js +11 -0
- package/dest/serialize/serialize.d.ts +3 -5
- package/dest/serialize/serialize.d.ts.map +1 -1
- package/dest/string/index.d.ts +2 -2
- package/dest/string/index.d.ts.map +1 -1
- package/dest/string/index.js +6 -0
- package/dest/testing/files/index.d.ts +0 -2
- package/dest/testing/files/index.d.ts.map +1 -1
- package/dest/timer/timeout.d.ts.map +1 -1
- package/dest/transport/dispatch/create_dispatch_fn.d.ts.map +1 -1
- package/dest/transport/interface/connector.d.ts.map +1 -1
- package/dest/transport/interface/listener.d.ts +0 -1
- package/dest/transport/interface/listener.d.ts.map +1 -1
- package/dest/transport/interface/socket.d.ts.map +1 -1
- package/dest/transport/node/node_connector.d.ts +0 -1
- package/dest/transport/node/node_connector.d.ts.map +1 -1
- package/dest/transport/node/node_connector_socket.d.ts +0 -1
- package/dest/transport/node/node_connector_socket.d.ts.map +1 -1
- package/dest/transport/node/node_listener.d.ts +0 -1
- package/dest/transport/node/node_listener.d.ts.map +1 -1
- package/dest/transport/node/node_listener_socket.d.ts +0 -1
- package/dest/transport/node/node_listener_socket.d.ts.map +1 -1
- package/dest/transport/transport_client.d.ts +0 -1
- package/dest/transport/transport_client.d.ts.map +1 -1
- package/dest/transport/transport_server.d.ts.map +1 -1
- package/dest/trees/hasher.d.ts +4 -6
- package/dest/trees/hasher.d.ts.map +1 -1
- package/dest/trees/indexed_merkle_tree.d.ts +0 -2
- package/dest/trees/indexed_merkle_tree.d.ts.map +1 -1
- package/dest/trees/indexed_merkle_tree_calculator.d.ts +1 -3
- package/dest/trees/indexed_merkle_tree_calculator.d.ts.map +1 -1
- package/dest/trees/indexed_tree_leaf.d.ts +0 -2
- package/dest/trees/indexed_tree_leaf.d.ts.map +1 -1
- package/dest/trees/membership_witness.d.ts +1 -3
- package/dest/trees/membership_witness.d.ts.map +1 -1
- package/dest/trees/merkle_tree.d.ts +0 -2
- package/dest/trees/merkle_tree.d.ts.map +1 -1
- package/dest/trees/merkle_tree_calculator.d.ts +1 -3
- package/dest/trees/merkle_tree_calculator.d.ts.map +1 -1
- package/dest/trees/sibling_path.d.ts +6 -8
- package/dest/trees/sibling_path.d.ts.map +1 -1
- package/dest/trees/unbalanced_merkle_tree.d.ts +0 -2
- package/dest/trees/unbalanced_merkle_tree.d.ts.map +1 -1
- package/dest/types/index.d.ts +0 -2
- package/dest/types/index.d.ts.map +1 -1
- package/dest/url/index.d.ts.map +1 -1
- package/dest/url/index.js +1 -1
- package/package.json +16 -15
- package/src/bigint/index.ts +8 -0
- package/src/buffer/buffer16.ts +133 -0
- package/src/buffer/buffer32.ts +8 -6
- package/src/buffer/index.ts +1 -0
- package/src/collection/object.ts +10 -0
- package/src/config/env_var.ts +13 -3
- package/src/fields/point.ts +0 -2
- package/src/iterable/filter.ts +1 -1
- package/src/iterable/map.ts +1 -1
- package/src/json-rpc/client/fetch.ts +14 -33
- package/src/json-rpc/client/safe_json_rpc_client.ts +171 -13
- package/src/json-rpc/client/undici.ts +6 -13
- package/src/json-rpc/convert.ts +2 -2
- package/src/json-rpc/errors.ts +6 -0
- package/src/json-rpc/fixtures/class_a.ts +4 -1
- package/src/json-rpc/fixtures/class_b.ts +4 -1
- package/src/json-rpc/index.ts +1 -0
- package/src/json-rpc/server/safe_json_rpc_server.ts +78 -25
- package/src/log/console.ts +4 -1
- package/src/log/gcloud-logger-config.ts +2 -2
- package/src/log/pino-logger.ts +2 -2
- package/src/message/index.ts +5 -1
- package/src/mutex/mutex_database.ts +2 -3
- package/src/queue/bounded_serial_queue.ts +4 -1
- package/src/schemas/api.ts +4 -4
- package/src/schemas/parse.ts +1 -1
- package/src/serialize/buffer_reader.ts +23 -3
- package/src/serialize/field_reader.ts +11 -3
- package/src/serialize/free_funcs.ts +13 -0
- package/src/string/index.ts +9 -1
- package/src/timer/timeout.ts +5 -1
- package/src/transport/interface/connector.ts +0 -1
- package/src/transport/interface/listener.ts +2 -3
- package/src/transport/interface/socket.ts +2 -3
- package/src/transport/transport_client.ts +3 -4
- package/src/transport/transport_server.ts +4 -1
- package/src/trees/hasher.ts +4 -4
- package/src/trees/indexed_merkle_tree.ts +5 -1
- package/src/trees/indexed_merkle_tree_calculator.ts +2 -2
- package/src/trees/merkle_tree.ts +4 -1
- package/src/trees/merkle_tree_calculator.ts +8 -3
- package/src/types/index.ts +0 -5
- package/src/url/index.ts +0 -1
|
@@ -1,27 +1,58 @@
|
|
|
1
1
|
import { format } from 'util';
|
|
2
2
|
|
|
3
3
|
import { type Logger, createLogger } from '../../log/pino-logger.js';
|
|
4
|
+
import { type PromiseWithResolvers, promiseWithResolvers } from '../../promise/utils.js';
|
|
4
5
|
import { type ApiSchema, type ApiSchemaFor, schemaHasMethod } from '../../schemas/api.js';
|
|
5
6
|
import { type JsonRpcFetch, defaultFetch } from './fetch.js';
|
|
6
7
|
|
|
8
|
+
// batch window of 0 would capture all requests in the current sync iteration of the event loop
|
|
9
|
+
// and send them all at once in a single batch
|
|
10
|
+
// minimal latency
|
|
11
|
+
const DEFAULT_BATCH_WINDOW_MS = 0;
|
|
12
|
+
|
|
7
13
|
export type SafeJsonRpcClientOptions = {
|
|
8
|
-
useApiEndpoints?: boolean;
|
|
9
14
|
namespaceMethods?: string | false;
|
|
10
15
|
fetch?: JsonRpcFetch;
|
|
11
16
|
log?: Logger;
|
|
17
|
+
batchWindowMS?: number;
|
|
12
18
|
onResponse?: (res: {
|
|
13
19
|
response: any;
|
|
14
20
|
headers: { get: (header: string) => string | null | undefined };
|
|
15
21
|
}) => Promise<void>;
|
|
16
22
|
};
|
|
17
23
|
|
|
24
|
+
type JsonRpcRequest = {
|
|
25
|
+
jsonrpc: '2.0';
|
|
26
|
+
id?: number;
|
|
27
|
+
method: string;
|
|
28
|
+
params?: Array<any>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type JsonRpcResponse =
|
|
32
|
+
| {
|
|
33
|
+
jsonrpc: '2.0';
|
|
34
|
+
id?: number;
|
|
35
|
+
result: any;
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
jsonrpc: '2.0';
|
|
39
|
+
id?: number;
|
|
40
|
+
error: { code: number; data?: any; message: string };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// expose helpful information on the RPC clients such that we can recognize them later
|
|
44
|
+
const SEND_BATCH = Symbol('JsonRpcClient.sendBatch');
|
|
45
|
+
const CLIENT_ID = Symbol('JsonRpcClient.clientId');
|
|
46
|
+
|
|
47
|
+
let nextClientId = 1;
|
|
48
|
+
// keep a reference to clients so that we can force send a batch
|
|
49
|
+
const clients = new Map<number, WeakRef<{ [SEND_BATCH]: () => Promise<void> }>>();
|
|
50
|
+
|
|
18
51
|
/**
|
|
19
52
|
* Creates a Proxy object that delegates over RPC and validates outputs against a given schema.
|
|
20
53
|
* The server is expected to be a JsonRpcServer.
|
|
21
54
|
* @param host - The host URL.
|
|
22
55
|
* @param schema - The api schema to validate returned data against.
|
|
23
|
-
* @param useApiEndpoints - Whether to use the API endpoints or the default RPC endpoint.
|
|
24
|
-
* @param namespaceMethods - String value (or false/empty) to namespace all methods sent to the server. e.g. 'getInfo' -\> 'pxe_getInfo'
|
|
25
56
|
* @param fetch - The fetch implementation to use.
|
|
26
57
|
*/
|
|
27
58
|
export function createSafeJsonRpcClient<T extends object>(
|
|
@@ -31,25 +62,118 @@ export function createSafeJsonRpcClient<T extends object>(
|
|
|
31
62
|
): T {
|
|
32
63
|
const fetch = config.fetch ?? defaultFetch;
|
|
33
64
|
const log = config.log ?? createLogger('json-rpc:client');
|
|
34
|
-
const {
|
|
65
|
+
const { namespaceMethods = false, batchWindowMS = DEFAULT_BATCH_WINDOW_MS } = config;
|
|
35
66
|
|
|
36
67
|
let id = 0;
|
|
68
|
+
let sendBatchTimeoutHandle: NodeJS.Timeout | undefined;
|
|
69
|
+
let queue: Array<{ request: JsonRpcRequest; deferred: PromiseWithResolvers<JsonRpcResponse> }> = [];
|
|
70
|
+
|
|
71
|
+
const sendBatch = async () => {
|
|
72
|
+
if (sendBatchTimeoutHandle !== undefined) {
|
|
73
|
+
clearTimeout(sendBatchTimeoutHandle);
|
|
74
|
+
sendBatchTimeoutHandle = undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const rpcCalls = queue;
|
|
78
|
+
queue = [];
|
|
79
|
+
|
|
80
|
+
if (rpcCalls.length === 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
log.debug(`Executing JSON-RPC batch of size: ${rpcCalls.length}`, {
|
|
85
|
+
methods: rpcCalls.map(({ request }) => request.method),
|
|
86
|
+
});
|
|
87
|
+
try {
|
|
88
|
+
const { response, headers } = await fetch(
|
|
89
|
+
host,
|
|
90
|
+
rpcCalls.map(({ request }) => request),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (config.onResponse) {
|
|
94
|
+
await config.onResponse({ response, headers });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!Array.isArray(response) || response.length !== rpcCalls.length) {
|
|
98
|
+
log.warn(
|
|
99
|
+
`Invalid response received from JSON-RPC server. Expected array of responses of length ${rpcCalls.length}`,
|
|
100
|
+
{ response },
|
|
101
|
+
);
|
|
102
|
+
for (let i = 0; i < rpcCalls.length; i++) {
|
|
103
|
+
const { request, deferred } = rpcCalls[i];
|
|
104
|
+
deferred.resolve({
|
|
105
|
+
id: request.id,
|
|
106
|
+
jsonrpc: '2.0',
|
|
107
|
+
error: {
|
|
108
|
+
code: -32000,
|
|
109
|
+
data: response,
|
|
110
|
+
message: response.message ?? 'Failed request',
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
for (let i = 0; i < response.length; i++) {
|
|
116
|
+
const resp: JsonRpcResponse = response[i];
|
|
117
|
+
const { request, deferred } = rpcCalls[i];
|
|
118
|
+
|
|
119
|
+
if (resp.id !== request.id) {
|
|
120
|
+
log.warn(`Invalid response received at index ${i} from JSON-RPC server: id mismatch`, {
|
|
121
|
+
requestMethod: request.method,
|
|
122
|
+
requestId: request.id,
|
|
123
|
+
responseId: resp.id,
|
|
124
|
+
});
|
|
125
|
+
deferred.resolve({
|
|
126
|
+
id: request.id,
|
|
127
|
+
jsonrpc: '2.0',
|
|
128
|
+
error: {
|
|
129
|
+
code: -32001,
|
|
130
|
+
data: resp,
|
|
131
|
+
message: 'RPC id mismatch',
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
} else {
|
|
135
|
+
deferred.resolve(resp);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch (err) {
|
|
140
|
+
log.warn(`Failed to fetch from the remote server`, err);
|
|
141
|
+
for (let i = 0; i < rpcCalls.length; i++) {
|
|
142
|
+
const { request, deferred } = rpcCalls[i];
|
|
143
|
+
deferred.resolve({
|
|
144
|
+
id: request.id,
|
|
145
|
+
jsonrpc: '2.0',
|
|
146
|
+
error: {
|
|
147
|
+
code: -32000,
|
|
148
|
+
data: err,
|
|
149
|
+
message: (err as any).message ?? 'Failed request',
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
37
156
|
const request = async (methodName: string, params: any[]): Promise<any> => {
|
|
38
157
|
if (!schemaHasMethod(schema, methodName)) {
|
|
39
158
|
throw new Error(`Unspecified method ${methodName} in client schema`);
|
|
40
159
|
}
|
|
41
160
|
const method = namespaceMethods ? `${namespaceMethods}_${methodName}` : methodName;
|
|
42
|
-
const body = { jsonrpc: '2.0', id: id++, method, params };
|
|
161
|
+
const body: JsonRpcRequest = { jsonrpc: '2.0', id: id++, method, params };
|
|
162
|
+
|
|
163
|
+
const deferred = promiseWithResolvers<JsonRpcResponse>();
|
|
164
|
+
queue.push({ request: body, deferred });
|
|
165
|
+
|
|
166
|
+
if (sendBatchTimeoutHandle === undefined) {
|
|
167
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
168
|
+
sendBatchTimeoutHandle = setTimeout(sendBatch, batchWindowMS);
|
|
169
|
+
}
|
|
43
170
|
|
|
44
171
|
log.debug(format(`request`, method, params));
|
|
45
|
-
const
|
|
172
|
+
const response = await deferred.promise;
|
|
46
173
|
log.debug(format(`result`, method, response));
|
|
47
174
|
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
if (response.error) {
|
|
52
|
-
throw response.error;
|
|
175
|
+
if ('error' in response) {
|
|
176
|
+
throw new Error(response.error.message, { cause: response.error });
|
|
53
177
|
}
|
|
54
178
|
// TODO(palla/schemas): Find a better way to handle null responses (JSON.stringify(null) is string "null").
|
|
55
179
|
if ([null, undefined, 'null', 'undefined'].includes(response.result)) {
|
|
@@ -58,10 +182,44 @@ export function createSafeJsonRpcClient<T extends object>(
|
|
|
58
182
|
return (schema as ApiSchema)[methodName].returnType().parseAsync(response.result);
|
|
59
183
|
};
|
|
60
184
|
|
|
61
|
-
const
|
|
185
|
+
const clientId = nextClientId++;
|
|
186
|
+
const proxy: any = { [CLIENT_ID]: clientId, [SEND_BATCH]: sendBatch };
|
|
62
187
|
for (const method of Object.keys(schema)) {
|
|
63
|
-
|
|
188
|
+
// attach the clientId to the promise so that if we want to trigger a batch immediately, we can do that
|
|
189
|
+
proxy[method] = (...params: any[]) => Object.assign(request(method, params), { [CLIENT_ID]: clientId });
|
|
64
190
|
}
|
|
65
191
|
|
|
192
|
+
clients.set(clientId, new WeakRef(proxy));
|
|
193
|
+
|
|
66
194
|
return proxy as T;
|
|
67
195
|
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Triggers a batch to be sent immediately
|
|
199
|
+
*/
|
|
200
|
+
export async function batch<T extends readonly unknown[]>(
|
|
201
|
+
values: T,
|
|
202
|
+
): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }> {
|
|
203
|
+
const clientIdsSeen = new Set<number>();
|
|
204
|
+
|
|
205
|
+
await Promise.allSettled(
|
|
206
|
+
values.map(val => {
|
|
207
|
+
if (typeof val === 'object' && val && Object.hasOwn(val, CLIENT_ID)) {
|
|
208
|
+
const clientId = (val as { [CLIENT_ID]: any })[CLIENT_ID];
|
|
209
|
+
if (typeof clientId === 'number') {
|
|
210
|
+
if (clientIdsSeen.has(clientId)) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
clientIdsSeen.add(clientId);
|
|
215
|
+
const client = clients.get(clientId)?.deref();
|
|
216
|
+
if (client) {
|
|
217
|
+
return client[SEND_BATCH]();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}),
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
return Promise.all(values);
|
|
225
|
+
}
|
|
@@ -10,21 +10,14 @@ const log = createLogger('json-rpc:json_rpc_client:undici');
|
|
|
10
10
|
export { Agent };
|
|
11
11
|
|
|
12
12
|
export function makeUndiciFetch(client = new Agent()): JsonRpcFetch {
|
|
13
|
-
return async (
|
|
14
|
-
|
|
15
|
-
rpcMethod: string,
|
|
16
|
-
body: any,
|
|
17
|
-
useApiEndpoints: boolean,
|
|
18
|
-
extraHeaders: Record<string, string> = {},
|
|
19
|
-
noRetry = false,
|
|
20
|
-
) => {
|
|
21
|
-
log.trace(`JsonRpcClient.fetch: ${host} ${rpcMethod}`, { host, rpcMethod, body });
|
|
13
|
+
return async (host: string, body: unknown, extraHeaders: Record<string, string> = {}, noRetry = false) => {
|
|
14
|
+
log.trace(`JsonRpcClient.fetch: ${host}`, { host, body });
|
|
22
15
|
let resp: Dispatcher.ResponseData;
|
|
23
16
|
try {
|
|
24
17
|
resp = await client.request({
|
|
25
18
|
method: 'POST',
|
|
26
19
|
origin: new URL(host),
|
|
27
|
-
path:
|
|
20
|
+
path: '/',
|
|
28
21
|
body: jsonStringify(body),
|
|
29
22
|
headers: {
|
|
30
23
|
...extraHeaders,
|
|
@@ -32,7 +25,7 @@ export function makeUndiciFetch(client = new Agent()): JsonRpcFetch {
|
|
|
32
25
|
},
|
|
33
26
|
});
|
|
34
27
|
} catch (err) {
|
|
35
|
-
const errorMessage = `Error fetching from host ${host}
|
|
28
|
+
const errorMessage = `Error fetching from host ${host}: ${String(err)}`;
|
|
36
29
|
throw new Error(errorMessage);
|
|
37
30
|
}
|
|
38
31
|
|
|
@@ -40,7 +33,7 @@ export function makeUndiciFetch(client = new Agent()): JsonRpcFetch {
|
|
|
40
33
|
const responseOk = resp.statusCode >= 200 && resp.statusCode <= 299;
|
|
41
34
|
try {
|
|
42
35
|
responseJson = await resp.body.json();
|
|
43
|
-
} catch
|
|
36
|
+
} catch {
|
|
44
37
|
if (!responseOk) {
|
|
45
38
|
throw new Error('HTTP ' + resp.statusCode);
|
|
46
39
|
}
|
|
@@ -48,7 +41,7 @@ export function makeUndiciFetch(client = new Agent()): JsonRpcFetch {
|
|
|
48
41
|
}
|
|
49
42
|
|
|
50
43
|
if (!responseOk) {
|
|
51
|
-
const errorMessage = `Error ${resp.statusCode} response from server ${host}
|
|
44
|
+
const errorMessage = `Error ${resp.statusCode} response from server ${host}: ${responseJson}`;
|
|
52
45
|
if (noRetry || (resp.statusCode >= 400 && resp.statusCode < 500)) {
|
|
53
46
|
throw new NoRetryError(errorMessage);
|
|
54
47
|
} else {
|
package/src/json-rpc/convert.ts
CHANGED
|
@@ -26,7 +26,7 @@ export function jsonParseWithSchemaSync<T>(json: string, schema: ZodFor<T>): T {
|
|
|
26
26
|
* @param obj - The object to be stringified.
|
|
27
27
|
* @returns The resulting string.
|
|
28
28
|
*/
|
|
29
|
-
export function jsonStringify(obj:
|
|
29
|
+
export function jsonStringify(obj: unknown, prettify?: boolean): string {
|
|
30
30
|
return JSON.stringify(
|
|
31
31
|
obj,
|
|
32
32
|
(_key, value) => {
|
|
@@ -55,7 +55,7 @@ export function jsonStringify(obj: object, prettify?: boolean): string {
|
|
|
55
55
|
export function tryJsonStringify(obj: any, prettify?: boolean): string | undefined {
|
|
56
56
|
try {
|
|
57
57
|
return jsonStringify(obj, prettify);
|
|
58
|
-
} catch
|
|
58
|
+
} catch {
|
|
59
59
|
return undefined;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
* Test class for testing string converter.
|
|
3
3
|
*/
|
|
4
4
|
export class ToStringClass {
|
|
5
|
-
constructor(
|
|
5
|
+
constructor(
|
|
6
|
+
/** A value */ public readonly x: string,
|
|
7
|
+
/** Another value */ public readonly y: string,
|
|
8
|
+
) {}
|
|
6
9
|
|
|
7
10
|
toString(): string {
|
|
8
11
|
return [this.x, this.y].join('-');
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
* Test class for testing string converter.
|
|
3
3
|
*/
|
|
4
4
|
export class ToStringClass {
|
|
5
|
-
constructor(
|
|
5
|
+
constructor(
|
|
6
|
+
/** A value */ public readonly x: string,
|
|
7
|
+
/** Another value */ public readonly y: string,
|
|
8
|
+
) {}
|
|
6
9
|
|
|
7
10
|
toString(): string {
|
|
8
11
|
return [this.x, this.y].join('-');
|
package/src/json-rpc/index.ts
CHANGED
|
@@ -63,20 +63,13 @@ export class SafeJsonRpcServer {
|
|
|
63
63
|
await next();
|
|
64
64
|
} catch (err: any) {
|
|
65
65
|
const method = (ctx.request.body as any)?.method ?? 'unknown';
|
|
66
|
-
this.log.warn(`
|
|
67
|
-
if (err
|
|
66
|
+
this.log.warn(`Uncaught error in JSON RPC server call ${method}: ${inspect(err)}`);
|
|
67
|
+
if (err && 'name' in err && err.name === 'BadRequestError') {
|
|
68
68
|
ctx.status = 400;
|
|
69
|
-
ctx.body = { jsonrpc: '2.0', id: null, error: { code: -
|
|
70
|
-
} else if (err instanceof
|
|
71
|
-
const message = err.issues.map(e => `${e.message} (${e.path.join('.')})`).join('. ') || 'Validation error';
|
|
69
|
+
ctx.body = { jsonrpc: '2.0', id: null, error: { code: -32000, message: `Bad request: ${err.message}` } };
|
|
70
|
+
} else if (err && err instanceof SyntaxError) {
|
|
72
71
|
ctx.status = 400;
|
|
73
|
-
ctx.body = { jsonrpc: '2.0', id: null, error: { code: -
|
|
74
|
-
} else if (this.http200OnError) {
|
|
75
|
-
ctx.body = {
|
|
76
|
-
jsonrpc: '2.0',
|
|
77
|
-
id: null,
|
|
78
|
-
error: { code: err.code || -32600, data: err.data, message: err.message },
|
|
79
|
-
};
|
|
72
|
+
ctx.body = { jsonrpc: '2.0', id: null, error: { code: -32700, message: `Parse error: ${err.message}` } };
|
|
80
73
|
} else {
|
|
81
74
|
ctx.status = 500;
|
|
82
75
|
ctx.body = { jsonrpc: '2.0', id: null, error: { code: -32600, message: err.message ?? 'Internal error' } };
|
|
@@ -124,23 +117,77 @@ export class SafeJsonRpcServer {
|
|
|
124
117
|
const router = new Router({ prefix });
|
|
125
118
|
// "JSON RPC mode" where a single endpoint is used and the method is given in the request body
|
|
126
119
|
router.post('/', async (ctx: Koa.Context) => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
120
|
+
if (Array.isArray(ctx.request.body)) {
|
|
121
|
+
const resp = await this.processBatch(ctx.request.body);
|
|
122
|
+
if (Array.isArray(resp)) {
|
|
123
|
+
ctx.status = 200;
|
|
124
|
+
ctx.body = resp;
|
|
125
|
+
} else {
|
|
126
|
+
ctx.status = this.http200OnError ? 200 : 400;
|
|
127
|
+
ctx.body = resp;
|
|
128
|
+
}
|
|
134
129
|
} else {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
130
|
+
const resp = await this.processRequest(ctx.request.body);
|
|
131
|
+
if ('error' in resp) {
|
|
132
|
+
ctx.status = this.http200OnError ? 200 : 400;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
ctx.body = resp;
|
|
138
136
|
}
|
|
139
137
|
});
|
|
140
138
|
|
|
141
139
|
return router;
|
|
142
140
|
}
|
|
143
141
|
|
|
142
|
+
private async processBatch(requests: any[]) {
|
|
143
|
+
if (requests.length === 0) {
|
|
144
|
+
return { jsonrpc: '2.0', error: { code: -32600, message: 'Invalid Request' }, id: null };
|
|
145
|
+
}
|
|
146
|
+
const results = await Promise.allSettled(requests.map(req => this.processRequest(req)));
|
|
147
|
+
return results.map(res => {
|
|
148
|
+
if (res.status === 'fulfilled') {
|
|
149
|
+
return res.value;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.log.warn(`Uncaught error executing request in batch: ${res.reason}.`);
|
|
153
|
+
return { jsonrpc: '2.0', error: { code: -32600, message: 'Invalid Request' }, id: null };
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async processRequest(request: any) {
|
|
158
|
+
if (!request || typeof request !== 'object') {
|
|
159
|
+
return { jsonrpc: '2.0', error: { code: -32600, message: 'Invalid Request' }, id: null };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const { params = [], jsonrpc, id, method } = request;
|
|
163
|
+
// Fail if not a registered function in the proxy
|
|
164
|
+
if (typeof method !== 'string' || method === 'constructor' || !this.proxy.hasMethod(method)) {
|
|
165
|
+
return { jsonrpc, id, error: { code: -32601, message: `Method not found: ${method}` } };
|
|
166
|
+
} else {
|
|
167
|
+
try {
|
|
168
|
+
const result = await this.proxy.call(method, params);
|
|
169
|
+
return { jsonrpc, id, result };
|
|
170
|
+
} catch (err: any) {
|
|
171
|
+
if (err && err instanceof ZodError) {
|
|
172
|
+
const message = err.issues.map(e => `${e.message} (${e.path.join('.')})`).join('. ') || 'Validation error';
|
|
173
|
+
return { jsonrpc: '2.0', id, error: { code: -32701, message } };
|
|
174
|
+
} else if (err) {
|
|
175
|
+
return {
|
|
176
|
+
jsonrpc,
|
|
177
|
+
id,
|
|
178
|
+
error: { code: -32702, data: err.data, message: err.message },
|
|
179
|
+
};
|
|
180
|
+
} else {
|
|
181
|
+
return {
|
|
182
|
+
jsonrpc,
|
|
183
|
+
id,
|
|
184
|
+
error: { code: -32702, message: 'Error executing request' },
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
144
191
|
/**
|
|
145
192
|
* Start this server with koa.
|
|
146
193
|
* @param port - Port number.
|
|
@@ -201,7 +248,10 @@ export class SafeJsonProxy<T extends object = any> implements Proxy {
|
|
|
201
248
|
private log = createLogger('json-rpc:proxy');
|
|
202
249
|
private schema: ApiSchema;
|
|
203
250
|
|
|
204
|
-
constructor(
|
|
251
|
+
constructor(
|
|
252
|
+
private handler: T,
|
|
253
|
+
schema: ApiSchemaFor<T>,
|
|
254
|
+
) {
|
|
205
255
|
this.schema = schema;
|
|
206
256
|
}
|
|
207
257
|
|
|
@@ -265,7 +315,10 @@ function makeAggregateHealthcheck(namedHandlers: NamespacedApiHandlers, log?: Lo
|
|
|
265
315
|
return async () => {
|
|
266
316
|
try {
|
|
267
317
|
const results = await Promise.all(
|
|
268
|
-
Object.entries(namedHandlers).map(([name, [, , healthCheck]]) => [
|
|
318
|
+
Object.entries(namedHandlers).map(async ([name, [, , healthCheck]]) => [
|
|
319
|
+
name,
|
|
320
|
+
healthCheck ? await healthCheck() : true,
|
|
321
|
+
]),
|
|
269
322
|
);
|
|
270
323
|
const failed = results.filter(([_, result]) => !result);
|
|
271
324
|
if (failed.length > 0) {
|
|
@@ -326,7 +379,7 @@ export function createStatusRouter(getCurrentStatus: StatusCheckFn, apiPrefix =
|
|
|
326
379
|
let ok: boolean;
|
|
327
380
|
try {
|
|
328
381
|
ok = (await getCurrentStatus()) === true;
|
|
329
|
-
} catch
|
|
382
|
+
} catch {
|
|
330
383
|
ok = false;
|
|
331
384
|
}
|
|
332
385
|
|
package/src/log/console.ts
CHANGED
|
@@ -7,7 +7,10 @@ import type { LogFn } from './log_fn.js';
|
|
|
7
7
|
* which can be useful for controlling the format of the output or redirecting logs to a different destination.
|
|
8
8
|
*/
|
|
9
9
|
class ConsoleLogger {
|
|
10
|
-
constructor(
|
|
10
|
+
constructor(
|
|
11
|
+
private prefix: string,
|
|
12
|
+
private logger: (...args: any[]) => void = console.log,
|
|
13
|
+
) {}
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Log messages with the specified prefix using the provided logger.
|
|
@@ -39,7 +39,7 @@ export const GoogleCloudLoggerConfig = {
|
|
|
39
39
|
// Severity labels https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
|
|
40
40
|
let severity: string;
|
|
41
41
|
|
|
42
|
-
switch (label as pino.Level | keyof
|
|
42
|
+
switch (label as pino.Level | keyof CustomLevels) {
|
|
43
43
|
case 'trace':
|
|
44
44
|
case 'debug':
|
|
45
45
|
severity = 'DEBUG';
|
|
@@ -68,4 +68,4 @@ export const GoogleCloudLoggerConfig = {
|
|
|
68
68
|
} satisfies pino.LoggerOptions;
|
|
69
69
|
|
|
70
70
|
// Define custom logging levels for pino. Duplicate from pino-logger.ts.
|
|
71
|
-
|
|
71
|
+
type CustomLevels = { verbose: 25 };
|
package/src/log/pino-logger.ts
CHANGED
|
@@ -248,7 +248,7 @@ export function registerLoggingStream(stream: Writable): void {
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
/** Log function that accepts an exception object */
|
|
251
|
-
type ErrorLogFn = (msg: string, err?:
|
|
251
|
+
type ErrorLogFn = (msg: string, err?: unknown, data?: LogData) => void;
|
|
252
252
|
|
|
253
253
|
/**
|
|
254
254
|
* Logger that supports multiple severity levels.
|
|
@@ -265,6 +265,6 @@ export type Logger = { [K in LogLevel]: LogFn } & { /** Error log function */ er
|
|
|
265
265
|
* @param err - Error to log
|
|
266
266
|
* @returns A string with both the log message and the error message.
|
|
267
267
|
*/
|
|
268
|
-
function formatErr(msg: string, err?:
|
|
268
|
+
function formatErr(msg: string, err?: unknown): string {
|
|
269
269
|
return err ? `${msg}: ${inspect(err)}` : msg;
|
|
270
270
|
}
|
package/src/message/index.ts
CHANGED
|
@@ -31,7 +31,11 @@ interface TypedMessageLike {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export class TypedMessage<T, B> {
|
|
34
|
-
public constructor(
|
|
34
|
+
public constructor(
|
|
35
|
+
public readonly msgType: T,
|
|
36
|
+
public readonly header: MessageHeader,
|
|
37
|
+
public readonly value: B,
|
|
38
|
+
) {}
|
|
35
39
|
|
|
36
40
|
static fromMessagePack<T, B>(data: TypedMessageLike): TypedMessage<T, B> {
|
|
37
41
|
return new TypedMessage<T, B>(data['msgType'] as T, MessageHeader.fromMessagePack(data['header']), data['value']);
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
* Provides functionality for acquiring, extending, and releasing locks on resources to ensure exclusive access and prevent conflicts in concurrent applications.
|
|
4
4
|
*/
|
|
5
5
|
export interface MutexDatabase {
|
|
6
|
-
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
7
6
|
acquireLock(name: string, timeout: number): Promise<boolean>;
|
|
8
|
-
|
|
7
|
+
|
|
9
8
|
extendLock(name: string, timeout: number): Promise<void>;
|
|
10
|
-
|
|
9
|
+
|
|
11
10
|
releaseLock(name: string): Promise<void>;
|
|
12
11
|
}
|
|
@@ -10,7 +10,10 @@ export class BoundedSerialQueue {
|
|
|
10
10
|
private readonly queue = new SerialQueue();
|
|
11
11
|
private semaphore: Semaphore;
|
|
12
12
|
|
|
13
|
-
constructor(
|
|
13
|
+
constructor(
|
|
14
|
+
maxQueueSize: number,
|
|
15
|
+
private log = createLogger('foundation:bounded_serial_queue'),
|
|
16
|
+
) {
|
|
14
17
|
this.semaphore = new Semaphore(maxQueueSize);
|
|
15
18
|
}
|
|
16
19
|
|
package/src/schemas/api.ts
CHANGED
|
@@ -19,10 +19,10 @@ type ZodReturnTypeFor<T> = z.ZodType<T, z.ZodTypeDef, any>;
|
|
|
19
19
|
type ZodMapParameterTypes<T> = T extends []
|
|
20
20
|
? []
|
|
21
21
|
: T extends [item: infer Head, ...infer Rest]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
? [ZodParameterTypeFor<Head>, ...{ [K in keyof Rest]: ZodParameterTypeFor<Rest[K]> }]
|
|
23
|
+
: T extends [item?: infer Head, ...infer Rest]
|
|
24
|
+
? [ZodNullableOptional<ZodParameterTypeFor<Head>>, ...{ [K in keyof Rest]: ZodParameterTypeFor<Rest[K]> }]
|
|
25
|
+
: never;
|
|
26
26
|
|
|
27
27
|
/** Maps all functions in an interface to their schema representation. */
|
|
28
28
|
export type ApiSchemaFor<T> = {
|
package/src/schemas/parse.ts
CHANGED
|
@@ -22,7 +22,7 @@ export function parseWithOptionals<T extends z.AnyZodTuple>(args: any[], schema:
|
|
|
22
22
|
function isOptional(schema: z.ZodTypeAny) {
|
|
23
23
|
try {
|
|
24
24
|
return schema.isOptional();
|
|
25
|
-
} catch
|
|
25
|
+
} catch {
|
|
26
26
|
// See https://github.com/colinhacks/zod/issues/1911
|
|
27
27
|
return schema._def.typeName === 'ZodOptional';
|
|
28
28
|
}
|
|
@@ -19,7 +19,10 @@ import type { Tuple } from './types.js';
|
|
|
19
19
|
*/
|
|
20
20
|
export class BufferReader {
|
|
21
21
|
private index: number;
|
|
22
|
-
constructor(
|
|
22
|
+
constructor(
|
|
23
|
+
private buffer: Buffer,
|
|
24
|
+
offset = 0,
|
|
25
|
+
) {
|
|
23
26
|
this.index = offset;
|
|
24
27
|
}
|
|
25
28
|
|
|
@@ -70,6 +73,23 @@ export class BufferReader {
|
|
|
70
73
|
return result as Tuple<number, N>;
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Reads a 256-bit unsigned integer from the buffer at the current index position.
|
|
78
|
+
* Updates the index position by 32 bytes after reading the number.
|
|
79
|
+
*
|
|
80
|
+
* Assumes the number is stored in big-endian format.
|
|
81
|
+
*
|
|
82
|
+
* @returns The read 256 bit value as a bigint.
|
|
83
|
+
*/
|
|
84
|
+
public readUInt64(): bigint {
|
|
85
|
+
this.#rangeCheck(8);
|
|
86
|
+
|
|
87
|
+
const result = this.buffer.readBigUInt64BE(this.index);
|
|
88
|
+
|
|
89
|
+
this.index += 8;
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
|
|
73
93
|
/**
|
|
74
94
|
* Reads a 256-bit unsigned integer from the buffer at the current index position.
|
|
75
95
|
* Updates the index position by 32 bytes after reading the number.
|
|
@@ -82,8 +102,8 @@ export class BufferReader {
|
|
|
82
102
|
this.#rangeCheck(32);
|
|
83
103
|
|
|
84
104
|
let result = BigInt(0);
|
|
85
|
-
for (let i = 0; i <
|
|
86
|
-
result = (result << BigInt(
|
|
105
|
+
for (let i = 0; i < 4; i++) {
|
|
106
|
+
result = (result << BigInt(64)) | this.buffer.readBigUInt64BE(this.index + i * 8);
|
|
87
107
|
}
|
|
88
108
|
|
|
89
109
|
this.index += 32;
|