@aztec/foundation 0.0.1-commit.42ee6df9b → 0.0.1-commit.431c48d

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.
Files changed (238) hide show
  1. package/dest/bigint-buffer/index.d.ts +1 -1
  2. package/dest/bigint-buffer/index.d.ts.map +1 -1
  3. package/dest/bigint-buffer/index.js +6 -1
  4. package/dest/branded-types/block_number.d.ts +3 -3
  5. package/dest/branded-types/block_number.d.ts.map +1 -1
  6. package/dest/branded-types/block_number.js +15 -4
  7. package/dest/branded-types/buffer32_hash.d.ts +27 -0
  8. package/dest/branded-types/buffer32_hash.d.ts.map +1 -0
  9. package/dest/branded-types/buffer32_hash.js +12 -0
  10. package/dest/branded-types/checkpoint_number.d.ts +9 -3
  11. package/dest/branded-types/checkpoint_number.d.ts.map +1 -1
  12. package/dest/branded-types/checkpoint_number.js +11 -0
  13. package/dest/branded-types/epoch.d.ts +2 -2
  14. package/dest/branded-types/epoch.d.ts.map +1 -1
  15. package/dest/branded-types/index.d.ts +2 -1
  16. package/dest/branded-types/index.d.ts.map +1 -1
  17. package/dest/branded-types/index.js +1 -0
  18. package/dest/branded-types/index_within_checkpoint.d.ts +2 -2
  19. package/dest/branded-types/index_within_checkpoint.d.ts.map +1 -1
  20. package/dest/branded-types/slot.d.ts +2 -2
  21. package/dest/branded-types/slot.d.ts.map +1 -1
  22. package/dest/buffer/buffer16.d.ts +3 -3
  23. package/dest/buffer/buffer16.d.ts.map +1 -1
  24. package/dest/buffer/buffer32.d.ts +28 -63
  25. package/dest/buffer/buffer32.d.ts.map +1 -1
  26. package/dest/buffer/buffer32.js +29 -63
  27. package/dest/collection/array.d.ts +9 -1
  28. package/dest/collection/array.d.ts.map +1 -1
  29. package/dest/collection/array.js +18 -0
  30. package/dest/collection/index.d.ts +2 -1
  31. package/dest/collection/index.d.ts.map +1 -1
  32. package/dest/collection/index.js +1 -0
  33. package/dest/collection/lru_map.d.ts +41 -0
  34. package/dest/collection/lru_map.d.ts.map +1 -0
  35. package/dest/collection/lru_map.js +119 -0
  36. package/dest/committable/committable.js +1 -1
  37. package/dest/config/env_var.d.ts +2 -2
  38. package/dest/config/env_var.d.ts.map +1 -1
  39. package/dest/config/index.d.ts +67 -37
  40. package/dest/config/index.d.ts.map +1 -1
  41. package/dest/config/index.js +72 -71
  42. package/dest/config/network_config.d.ts +11 -55
  43. package/dest/config/network_config.d.ts.map +1 -1
  44. package/dest/config/network_name.d.ts +2 -2
  45. package/dest/config/network_name.d.ts.map +1 -1
  46. package/dest/config/network_name.js +1 -3
  47. package/dest/config/secret_value.d.ts +2 -2
  48. package/dest/config/secret_value.d.ts.map +1 -1
  49. package/dest/crypto/bls/bn254_keystore.d.ts +10 -168
  50. package/dest/crypto/bls/bn254_keystore.d.ts.map +1 -1
  51. package/dest/crypto/bls/bn254_keystore.js +22 -17
  52. package/dest/crypto/schnorr/index.d.ts +8 -7
  53. package/dest/crypto/schnorr/index.d.ts.map +1 -1
  54. package/dest/crypto/schnorr/index.js +6 -6
  55. package/dest/crypto/schnorr/signature.d.ts +9 -29
  56. package/dest/crypto/schnorr/signature.d.ts.map +1 -1
  57. package/dest/crypto/schnorr/signature.js +20 -36
  58. package/dest/curves/bls12/field.d.ts +3 -3
  59. package/dest/curves/bls12/field.d.ts.map +1 -1
  60. package/dest/curves/bls12/field.js +0 -5
  61. package/dest/curves/bls12/point.d.ts +2 -2
  62. package/dest/curves/bls12/point.d.ts.map +1 -1
  63. package/dest/curves/bn254/field.d.ts +23 -19
  64. package/dest/curves/bn254/field.d.ts.map +1 -1
  65. package/dest/curves/bn254/field.js +23 -17
  66. package/dest/curves/grumpkin/point.d.ts +13 -27
  67. package/dest/curves/grumpkin/point.d.ts.map +1 -1
  68. package/dest/curves/grumpkin/point.js +28 -42
  69. package/dest/eth-address/index.d.ts +2 -2
  70. package/dest/eth-address/index.d.ts.map +1 -1
  71. package/dest/eth-address/index.js +0 -3
  72. package/dest/eth-signature/eth_signature.d.ts +2 -2
  73. package/dest/eth-signature/eth_signature.d.ts.map +1 -1
  74. package/dest/fifo_set/fifo_set.d.ts +15 -0
  75. package/dest/fifo_set/fifo_set.d.ts.map +1 -0
  76. package/dest/fifo_set/fifo_set.js +39 -0
  77. package/dest/fifo_set/index.d.ts +2 -0
  78. package/dest/fifo_set/index.d.ts.map +1 -0
  79. package/dest/fifo_set/index.js +1 -0
  80. package/dest/iterable/first.d.ts +8 -0
  81. package/dest/iterable/first.d.ts.map +1 -0
  82. package/dest/iterable/first.js +11 -0
  83. package/dest/iterable/index.d.ts +2 -1
  84. package/dest/iterable/index.d.ts.map +1 -1
  85. package/dest/iterable/index.js +1 -0
  86. package/dest/json-rpc/client/fetch.d.ts +8 -3
  87. package/dest/json-rpc/client/fetch.d.ts.map +1 -1
  88. package/dest/json-rpc/client/fetch.js +8 -5
  89. package/dest/json-rpc/client/safe_json_rpc_client.d.ts +1 -1
  90. package/dest/json-rpc/client/safe_json_rpc_client.d.ts.map +1 -1
  91. package/dest/json-rpc/client/safe_json_rpc_client.js +2 -2
  92. package/dest/json-rpc/client/undici.d.ts +14 -3
  93. package/dest/json-rpc/client/undici.d.ts.map +1 -1
  94. package/dest/json-rpc/client/undici.js +47 -14
  95. package/dest/json-rpc/fixtures/test_state.d.ts +4 -8
  96. package/dest/json-rpc/fixtures/test_state.d.ts.map +1 -1
  97. package/dest/json-rpc/fixtures/test_state.js +57 -17
  98. package/dest/json-rpc/server/safe_json_rpc_server.d.ts +49 -11
  99. package/dest/json-rpc/server/safe_json_rpc_server.d.ts.map +1 -1
  100. package/dest/json-rpc/server/safe_json_rpc_server.js +159 -40
  101. package/dest/log/aws-logger-config.d.ts +8 -0
  102. package/dest/log/aws-logger-config.d.ts.map +1 -0
  103. package/dest/log/aws-logger-config.js +55 -0
  104. package/dest/log/pino-logger.d.ts +1 -1
  105. package/dest/log/pino-logger.d.ts.map +1 -1
  106. package/dest/log/pino-logger.js +27 -7
  107. package/dest/noir/noir_package_config.d.ts +19 -104
  108. package/dest/noir/noir_package_config.d.ts.map +1 -1
  109. package/dest/noir/noir_package_config.js +1 -1
  110. package/dest/promise/running-promise.d.ts +8 -2
  111. package/dest/promise/running-promise.d.ts.map +1 -1
  112. package/dest/promise/running-promise.js +17 -5
  113. package/dest/promise/utils.d.ts +14 -1
  114. package/dest/promise/utils.d.ts.map +1 -1
  115. package/dest/promise/utils.js +20 -0
  116. package/dest/queue/batch_queue.d.ts +1 -1
  117. package/dest/queue/batch_queue.d.ts.map +1 -1
  118. package/dest/queue/batch_queue.js +1 -0
  119. package/dest/retry/index.d.ts +20 -3
  120. package/dest/retry/index.d.ts.map +1 -1
  121. package/dest/retry/index.js +23 -2
  122. package/dest/schemas/api.d.ts +11 -9
  123. package/dest/schemas/api.d.ts.map +1 -1
  124. package/dest/schemas/api.js +7 -1
  125. package/dest/schemas/parse.d.ts +4 -4
  126. package/dest/schemas/parse.d.ts.map +1 -1
  127. package/dest/schemas/parse.js +6 -5
  128. package/dest/schemas/schemas.d.ts +15 -15
  129. package/dest/schemas/types.d.ts +3 -3
  130. package/dest/schemas/types.d.ts.map +1 -1
  131. package/dest/schemas/utils.d.ts +7 -11
  132. package/dest/schemas/utils.d.ts.map +1 -1
  133. package/dest/schemas/utils.js +2 -18
  134. package/dest/serialize/buffer_reader.d.ts +1 -1
  135. package/dest/serialize/buffer_reader.d.ts.map +1 -1
  136. package/dest/serialize/buffer_reader.js +5 -0
  137. package/dest/serialize/buffer_sink.d.ts +134 -0
  138. package/dest/serialize/buffer_sink.d.ts.map +1 -0
  139. package/dest/serialize/buffer_sink.js +297 -0
  140. package/dest/serialize/index.d.ts +2 -1
  141. package/dest/serialize/index.d.ts.map +1 -1
  142. package/dest/serialize/index.js +1 -0
  143. package/dest/serialize/serialize.d.ts +1 -32
  144. package/dest/serialize/serialize.d.ts.map +1 -1
  145. package/dest/serialize/serialize.js +0 -21
  146. package/dest/string/index.js +1 -1
  147. package/dest/testing/files/index.d.ts +2 -3
  148. package/dest/testing/files/index.d.ts.map +1 -1
  149. package/dest/testing/files/index.js +1 -2
  150. package/dest/timer/index.d.ts +2 -2
  151. package/dest/timer/index.d.ts.map +1 -1
  152. package/dest/timer/index.js +1 -1
  153. package/dest/timer/timeout.d.ts +3 -1
  154. package/dest/timer/timeout.d.ts.map +1 -1
  155. package/dest/timer/timeout.js +26 -0
  156. package/dest/trees/balanced_merkle_tree_root.d.ts +2 -3
  157. package/dest/trees/balanced_merkle_tree_root.d.ts.map +1 -1
  158. package/dest/trees/balanced_merkle_tree_root.js +2 -3
  159. package/dest/trees/hasher.d.ts +3 -2
  160. package/dest/trees/hasher.d.ts.map +1 -1
  161. package/dest/trees/hasher.js +5 -5
  162. package/dest/trees/membership_witness.d.ts +2 -6
  163. package/dest/trees/membership_witness.d.ts.map +1 -1
  164. package/dest/trees/membership_witness.js +0 -9
  165. package/dest/trees/merkle_tree_calculator.d.ts +4 -2
  166. package/dest/trees/merkle_tree_calculator.d.ts.map +1 -1
  167. package/dest/trees/merkle_tree_calculator.js +1 -5
  168. package/dest/trees/sibling_path.d.ts +13 -14
  169. package/dest/trees/sibling_path.d.ts.map +1 -1
  170. package/dest/trees/sibling_path.js +23 -28
  171. package/dest/trees/unbalanced_merkle_tree_root.d.ts +2 -3
  172. package/dest/trees/unbalanced_merkle_tree_root.d.ts.map +1 -1
  173. package/dest/trees/unbalanced_merkle_tree_root.js +2 -3
  174. package/dest/types/index.d.ts +23 -1
  175. package/dest/types/index.d.ts.map +1 -1
  176. package/dest/types/index.js +15 -0
  177. package/package.json +5 -4
  178. package/src/bigint-buffer/index.ts +6 -1
  179. package/src/branded-types/block_number.ts +20 -2
  180. package/src/branded-types/buffer32_hash.ts +38 -0
  181. package/src/branded-types/checkpoint_number.ts +16 -1
  182. package/src/branded-types/epoch.ts +1 -1
  183. package/src/branded-types/index.ts +1 -0
  184. package/src/branded-types/index_within_checkpoint.ts +1 -1
  185. package/src/branded-types/slot.ts +1 -1
  186. package/src/buffer/buffer16.ts +2 -2
  187. package/src/buffer/buffer32.ts +39 -71
  188. package/src/collection/array.ts +20 -0
  189. package/src/collection/index.ts +1 -0
  190. package/src/collection/lru_map.ts +143 -0
  191. package/src/committable/committable.ts +1 -1
  192. package/src/config/env_var.ts +50 -22
  193. package/src/config/index.ts +143 -122
  194. package/src/config/network_name.ts +2 -5
  195. package/src/config/secret_value.ts +1 -1
  196. package/src/crypto/bls/bn254_keystore.ts +33 -20
  197. package/src/crypto/schnorr/index.ts +9 -8
  198. package/src/crypto/schnorr/signature.ts +17 -46
  199. package/src/curves/bls12/field.ts +0 -7
  200. package/src/curves/bn254/field.ts +35 -34
  201. package/src/curves/grumpkin/point.ts +22 -39
  202. package/src/eth-address/index.ts +0 -4
  203. package/src/fifo_set/fifo_set.ts +52 -0
  204. package/src/fifo_set/index.ts +1 -0
  205. package/src/iterable/first.ts +14 -0
  206. package/src/iterable/index.ts +1 -0
  207. package/src/json-rpc/client/fetch.ts +16 -3
  208. package/src/json-rpc/client/safe_json_rpc_client.ts +2 -2
  209. package/src/json-rpc/client/undici.ts +54 -12
  210. package/src/json-rpc/fixtures/test_state.ts +10 -10
  211. package/src/json-rpc/server/safe_json_rpc_server.ts +204 -47
  212. package/src/log/aws-logger-config.ts +32 -0
  213. package/src/log/pino-logger.ts +30 -5
  214. package/src/noir/noir_package_config.ts +32 -20
  215. package/src/promise/running-promise.ts +20 -7
  216. package/src/promise/utils.ts +31 -0
  217. package/src/queue/batch_queue.ts +1 -0
  218. package/src/retry/index.ts +36 -4
  219. package/src/schemas/api.ts +21 -24
  220. package/src/schemas/parse.ts +9 -6
  221. package/src/schemas/schemas.ts +4 -4
  222. package/src/schemas/types.ts +2 -2
  223. package/src/schemas/utils.ts +4 -38
  224. package/src/serialize/buffer_reader.ts +5 -0
  225. package/src/serialize/buffer_sink.ts +350 -0
  226. package/src/serialize/index.ts +1 -0
  227. package/src/serialize/serialize.ts +0 -50
  228. package/src/string/index.ts +1 -1
  229. package/src/testing/files/index.ts +1 -2
  230. package/src/timer/index.ts +1 -1
  231. package/src/timer/timeout.ts +31 -0
  232. package/src/trees/balanced_merkle_tree_root.ts +2 -5
  233. package/src/trees/hasher.ts +6 -3
  234. package/src/trees/membership_witness.ts +0 -8
  235. package/src/trees/merkle_tree_calculator.ts +4 -10
  236. package/src/trees/sibling_path.ts +24 -30
  237. package/src/trees/unbalanced_merkle_tree_root.ts +2 -5
  238. package/src/types/index.ts +47 -0
@@ -130,14 +130,14 @@ export class TestState implements TestStateApi {
130
130
  }
131
131
 
132
132
  export const TestStateSchema: ApiSchemaFor<TestStateApi> = {
133
- getNote: z.function().args(z.number()).returns(TestNote.schema.optional()),
134
- getNotes: z.function().args(optional(schemas.Integer)).returns(z.array(TestNote.schema)),
135
- getNotes2: z.function().args(optional(schemas.BigInt)).returns(z.array(TestNote.schema)),
136
- getNotes3: z.function().args(optional(schemas.Integer)).returns(z.array(TestNote.schema)),
137
- clear: z.function().returns(z.void()),
138
- addNotes: z.function().args(z.array(TestNote.schema)).returns(z.array(TestNote.schema)),
139
- fail: z.function().returns(z.void()),
140
- count: z.function().returns(z.number()),
141
- getStatus: z.function().returns(z.object({ status: z.string(), count: schemas.BigInt })),
142
- getTuple: z.function().returns(z.tuple([z.string(), optional(z.string()), z.number()])),
133
+ getNote: z.function({ input: z.tuple([z.number()]), output: TestNote.schema.optional() }),
134
+ getNotes: z.function({ input: z.tuple([optional(schemas.Integer)]), output: z.array(TestNote.schema) }),
135
+ getNotes2: z.function({ input: z.tuple([optional(schemas.BigInt)]), output: z.array(TestNote.schema) }),
136
+ getNotes3: z.function({ input: z.tuple([optional(schemas.Integer)]), output: z.array(TestNote.schema) }),
137
+ clear: z.function({ input: z.tuple([]), output: z.void() }),
138
+ addNotes: z.function({ input: z.tuple([z.array(TestNote.schema)]), output: z.array(TestNote.schema) }),
139
+ fail: z.function({ input: z.tuple([]), output: z.void() }),
140
+ count: z.function({ input: z.tuple([]), output: z.number() }),
141
+ getStatus: z.function({ input: z.tuple([]), output: z.object({ status: z.string(), count: schemas.BigInt }) }),
142
+ getTuple: z.function({ input: z.tuple([]), output: z.tuple([z.string(), optional(z.string()), z.number()]) }),
143
143
  };
@@ -10,7 +10,14 @@ import { ZodError } from 'zod';
10
10
 
11
11
  import { type Logger, createLogger } from '../../log/index.js';
12
12
  import { promiseWithResolvers } from '../../promise/utils.js';
13
- import { type ApiSchema, type ApiSchemaFor, parseWithOptionals, schemaHasMethod } from '../../schemas/index.js';
13
+ import {
14
+ type ApiSchema,
15
+ type ApiSchemaFor,
16
+ getSchemaParameters,
17
+ parseWithOptionals,
18
+ schemaHasMethod,
19
+ } from '../../schemas/index.js';
20
+ import { Timer } from '../../timer/index.js';
14
21
  import { jsonStringify } from '../convert.js';
15
22
  import { assert } from '../js_utils.js';
16
23
 
@@ -19,6 +26,8 @@ export type DiagnosticsData = {
19
26
  method: string;
20
27
  params: any[];
21
28
  headers: http.IncomingHttpHeaders;
29
+ requestValidationDurationMs?: number;
30
+ requestValidationSucceeded?: boolean;
22
31
  };
23
32
 
24
33
  export type DiagnosticsMiddleware = (ctx: DiagnosticsData, next: () => Promise<void>) => Promise<void>;
@@ -30,14 +39,41 @@ export type SafeJsonRpcServerConfig = {
30
39
  http200OnError: boolean;
31
40
  /** The maximum body size the server will accept */
32
41
  maxBodySizeBytes: string;
42
+ /** Origins allowed to make credentialed cross-origin requests. An empty list preserves wildcard CORS. */
43
+ corsAllowedOrigins?: string[];
44
+ /** Headers allowed in cross-origin requests. An empty list reflects the requested headers. */
45
+ corsAllowedHeaders?: string[];
33
46
  };
34
47
 
35
- const defaultServerConfig: SafeJsonRpcServerConfig = {
48
+ type ResolvedSafeJsonRpcServerConfig = Omit<SafeJsonRpcServerConfig, 'corsAllowedOrigins' | 'corsAllowedHeaders'> & {
49
+ corsAllowedOrigins: string[];
50
+ corsAllowedHeaders: string[];
51
+ };
52
+
53
+ const defaultServerConfig: ResolvedSafeJsonRpcServerConfig = {
36
54
  http200OnError: false,
37
55
  maxBatchSize: 100,
38
56
  maxBodySizeBytes: '1mb',
57
+ corsAllowedOrigins: [],
58
+ corsAllowedHeaders: [],
39
59
  };
40
60
 
61
+ function normalizeCorsOrigin(origin: string): string {
62
+ if (origin === '*') {
63
+ return origin;
64
+ }
65
+ const url = new URL(origin);
66
+ if (!['http:', 'https:'].includes(url.protocol)) {
67
+ throw new Error(`Invalid CORS origin protocol: ${origin}`);
68
+ }
69
+ if (url.username || url.password || (url.pathname !== '' && url.pathname !== '/') || url.search || url.hash) {
70
+ throw new Error(
71
+ `CORS allowed origin must not include credentials, a path, query parameters, or a fragment: ${origin}`,
72
+ );
73
+ }
74
+ return url.origin;
75
+ }
76
+
41
77
  export class SafeJsonRpcServer {
42
78
  /**
43
79
  * The HTTP server accepting remote requests.
@@ -45,7 +81,7 @@ export class SafeJsonRpcServer {
45
81
  */
46
82
  private httpServer?: http.Server;
47
83
 
48
- private config: SafeJsonRpcServerConfig;
84
+ private config: ResolvedSafeJsonRpcServerConfig;
49
85
 
50
86
  constructor(
51
87
  /** The proxy object to delegate requests to */
@@ -53,12 +89,15 @@ export class SafeJsonRpcServer {
53
89
  config: Partial<SafeJsonRpcServerConfig> = {},
54
90
  /** Health check function */
55
91
  private readonly healthCheck: StatusCheckFn = () => true,
56
- /** Additional middlewares */
92
+ /** Additional Koa middlewares */
57
93
  private extraMiddlewares: Application.Middleware[] = [],
94
+ /** Additional per-request diagnostics middlewares */
95
+ private diagnosticsMiddleware?: DiagnosticsMiddleware,
58
96
  /** Logger */
59
97
  private log = createLogger('json-rpc:server'),
60
98
  ) {
61
99
  this.config = { ...defaultServerConfig, ...config };
100
+ this.config.corsAllowedOrigins = this.config.corsAllowedOrigins.map(normalizeCorsOrigin);
62
101
 
63
102
  // handle empty string
64
103
  if (!this.config.maxBodySizeBytes) {
@@ -66,8 +105,16 @@ export class SafeJsonRpcServer {
66
105
  }
67
106
  }
68
107
 
108
+ /** Returns the status of this server, including per-component health when the health check reports it. */
109
+ public getStatus(): Promise<ServerStatus> {
110
+ return Promise.resolve(this.healthCheck()).then(result => {
111
+ const { healthy, details } = normalizeStatusCheckResult(result);
112
+ return { ok: healthy, ...details };
113
+ });
114
+ }
115
+
69
116
  public isHealthy(): boolean | Promise<boolean> {
70
- return this.healthCheck();
117
+ return this.getStatus().then(status => status.ok);
71
118
  }
72
119
 
73
120
  /**
@@ -92,7 +139,7 @@ export class SafeJsonRpcServer {
92
139
  ctx.body = { jsonrpc: '2.0', id: null, error: { code: -32700, message: `Parse error: ${err.message}` } };
93
140
  } else {
94
141
  ctx.status = 500;
95
- ctx.body = { jsonrpc: '2.0', id: null, error: { code: -32600, message: err.message ?? 'Internal error' } };
142
+ ctx.body = { jsonrpc: '2.0', id: null, error: { code: -32603, message: err.message ?? 'Internal error' } };
96
143
  }
97
144
  }
98
145
  };
@@ -116,6 +163,36 @@ export class SafeJsonRpcServer {
116
163
 
117
164
  app.use(compress({ br: false }));
118
165
  app.use(jsonResponse);
166
+ const corsAllowedHeaders =
167
+ this.config.corsAllowedHeaders.length > 0 ? { allowHeaders: this.config.corsAllowedHeaders } : {};
168
+ if (this.config.corsAllowedOrigins.length === 0) {
169
+ app.use(cors(corsAllowedHeaders));
170
+ } else {
171
+ const allowedOrigins = new Set(this.config.corsAllowedOrigins);
172
+ const allowAnyOrigin = allowedOrigins.has('*');
173
+ app.use(
174
+ cors({
175
+ origin: ctx => {
176
+ const origin = ctx.get('Origin');
177
+ if (!origin) {
178
+ return '';
179
+ }
180
+
181
+ if (allowAnyOrigin) {
182
+ return origin;
183
+ }
184
+
185
+ if (allowedOrigins.has(origin)) {
186
+ return origin;
187
+ }
188
+
189
+ return '';
190
+ },
191
+ credentials: true,
192
+ ...corsAllowedHeaders,
193
+ }),
194
+ );
195
+ }
119
196
  for (const middleware of this.extraMiddlewares) {
120
197
  app.use(middleware);
121
198
  }
@@ -126,7 +203,6 @@ export class SafeJsonRpcServer {
126
203
  enableTypes: ['json'],
127
204
  }),
128
205
  );
129
- app.use(cors());
130
206
  app.use(router.routes());
131
207
  app.use(router.allowedMethods());
132
208
 
@@ -155,7 +231,7 @@ export class SafeJsonRpcServer {
155
231
  };
156
232
  return;
157
233
  }
158
- const resp = await this.processBatch(ctx.request.body);
234
+ const resp = await this.processBatch(ctx.request.body, ctx.request.headers);
159
235
  if (Array.isArray(resp)) {
160
236
  ctx.status = 200;
161
237
  ctx.body = resp;
@@ -164,7 +240,7 @@ export class SafeJsonRpcServer {
164
240
  ctx.body = resp;
165
241
  }
166
242
  } else {
167
- const resp = await this.processRequest(ctx.request.body);
243
+ const resp = await this.processRequest(ctx.request.body, ctx.request.headers);
168
244
  if ('error' in resp) {
169
245
  ctx.status = this.config.http200OnError ? 200 : 400;
170
246
  }
@@ -176,22 +252,22 @@ export class SafeJsonRpcServer {
176
252
  return router;
177
253
  }
178
254
 
179
- private async processBatch(requests: any[]) {
255
+ private async processBatch(requests: any[], headers: http.IncomingHttpHeaders = {}) {
180
256
  if (requests.length === 0) {
181
257
  return { jsonrpc: '2.0', error: { code: -32600, message: 'Invalid Request' }, id: null };
182
258
  }
183
- const results = await Promise.allSettled(requests.map(req => this.processRequest(req)));
259
+ const results = await Promise.allSettled(requests.map(req => this.processRequest(req, headers)));
184
260
  return results.map(res => {
185
261
  if (res.status === 'fulfilled') {
186
262
  return res.value;
187
263
  }
188
264
 
189
265
  this.log.warn(`Uncaught error executing request in batch: ${res.reason}.`);
190
- return { jsonrpc: '2.0', error: { code: -32600, message: 'Invalid Request' }, id: null };
266
+ return { jsonrpc: '2.0', error: { code: -32603, message: 'Internal error' }, id: null };
191
267
  });
192
268
  }
193
269
 
194
- private async processRequest(request: any) {
270
+ private async processRequest(request: any, headers: http.IncomingHttpHeaders = {}) {
195
271
  if (!request || typeof request !== 'object') {
196
272
  return { jsonrpc: '2.0', error: { code: -32600, message: 'Invalid Request' }, id: null };
197
273
  }
@@ -206,8 +282,25 @@ export class SafeJsonRpcServer {
206
282
  return { jsonrpc, id, error: { code: -32601, message: `Method not found: ${method}` } };
207
283
  } else {
208
284
  try {
209
- const result = await this.proxy.call(method, params);
210
- return { jsonrpc, id, result };
285
+ let result: any;
286
+
287
+ if (this.diagnosticsMiddleware) {
288
+ const diagnosticsData: DiagnosticsData = { id: id ?? null, method, params, headers };
289
+ await this.diagnosticsMiddleware(diagnosticsData, async () => {
290
+ result = await this.proxy.call(method, params, (durationMs, succeeded) => {
291
+ diagnosticsData.requestValidationDurationMs = durationMs;
292
+ diagnosticsData.requestValidationSucceeded = succeeded;
293
+ });
294
+ });
295
+ } else {
296
+ result = await this.proxy.call(method, params);
297
+ }
298
+
299
+ // Coerce an undefined return value to null so the response always carries a `result` key.
300
+ // JSON.stringify drops undefined-valued keys, which would otherwise produce a JSON-RPC
301
+ // response with neither `result` nor `error` — a spec violation that leaves callers unable
302
+ // to distinguish "not found" from a malformed response.
303
+ return { jsonrpc, id, result: result ?? null };
211
304
  } catch (err: any) {
212
305
  if (err && err instanceof ZodError) {
213
306
  const message = err.issues.map(e => `${e.message} (${e.path.join('.')})`).join('. ') || 'Validation error';
@@ -274,11 +367,42 @@ export class SafeJsonRpcServer {
274
367
  }
275
368
  }
276
369
 
277
- export type StatusCheckFn = () => boolean | Promise<boolean>;
370
+ /** Health of a single component, along with any details worth surfacing to whoever queries the status endpoint. */
371
+ export type StatusCheckResult = {
372
+ /** Whether the component is healthy. */
373
+ healthy: boolean;
374
+ /** Component-specific information, reported next to the healthy flag. */
375
+ details?: object;
376
+ };
377
+
378
+ /** Health check for a component. Returning a plain boolean is shorthand for `{ healthy: <boolean> }`. */
379
+ export type StatusCheckFn = () => boolean | StatusCheckResult | Promise<boolean | StatusCheckResult>;
380
+
381
+ /** Health of a component as reported on the status endpoint: its healthy flag flattened with its details. */
382
+ export type ComponentStatus = { healthy: boolean } & Record<string, unknown>;
383
+
384
+ /** Status of a server as reported on the status endpoint. */
385
+ export type ServerStatus = {
386
+ /** Whether every checked component reported healthy. */
387
+ ok: boolean;
388
+ /** Health of each component keyed by namespace, when the server has component-level health checks. */
389
+ components?: Record<string, ComponentStatus>;
390
+ };
391
+
392
+ /** Returns the status of a server. */
393
+ export type ServerStatusFn = () => ServerStatus | Promise<ServerStatus>;
394
+
395
+ function normalizeStatusCheckResult(result: boolean | StatusCheckResult): StatusCheckResult {
396
+ return typeof result === 'boolean' ? { healthy: result } : result;
397
+ }
278
398
 
279
399
  interface Proxy {
280
400
  hasMethod(methodName: string): boolean;
281
- call(methodName: string, jsonParams?: any[]): Promise<any>;
401
+ call(
402
+ methodName: string,
403
+ jsonParams?: any[],
404
+ onRequestValidated?: (durationMs: number, succeeded: boolean) => void,
405
+ ): Promise<any>;
282
406
  }
283
407
 
284
408
  /**
@@ -302,14 +426,26 @@ export class SafeJsonProxy<T extends object = any> implements Proxy {
302
426
  * @param jsonParams - The RPC parameters.
303
427
  * @returns The remote result.
304
428
  */
305
- public async call(methodName: string, jsonParams: any[] = []) {
429
+ public async call(
430
+ methodName: string,
431
+ jsonParams: any[] = [],
432
+ onRequestValidated?: (durationMs: number, succeeded: boolean) => void,
433
+ ) {
306
434
  this.log.debug(format(`request`, methodName, jsonParams));
307
435
 
308
- assert(Array.isArray(jsonParams), `Params to ${methodName} is not an array: ${jsonParams}`);
309
436
  assert(schemaHasMethod(this.schema, methodName), `Method ${methodName} not found in schema`);
310
437
  const method = this.handler[methodName as keyof T];
311
438
  assert(typeof method === 'function', `Method ${methodName} is not a function`);
312
- const args = await parseWithOptionals(jsonParams, this.schema[methodName].parameters());
439
+ const validationTimer = new Timer();
440
+ let args: any[];
441
+ try {
442
+ assert(Array.isArray(jsonParams), `Params to ${methodName} is not an array: ${jsonParams}`);
443
+ args = await parseWithOptionals(jsonParams, getSchemaParameters(this.schema[methodName]));
444
+ onRequestValidated?.(validationTimer.ms(), true);
445
+ } catch (error) {
446
+ onRequestValidated?.(validationTimer.ms(), false);
447
+ throw error;
448
+ }
313
449
  const ret = await method.apply(this.handler, args);
314
450
  this.log.debug(format('response', methodName, ret));
315
451
  return ret;
@@ -329,12 +465,16 @@ class NamespacedSafeJsonProxy implements Proxy {
329
465
  }
330
466
  }
331
467
 
332
- public call(namespacedMethodName: string, jsonParams: any[] = []) {
468
+ public call(
469
+ namespacedMethodName: string,
470
+ jsonParams: any[] = [],
471
+ onRequestValidated?: (durationMs: number, succeeded: boolean) => void,
472
+ ) {
333
473
  const [namespace, methodName] = namespacedMethodName.split('_', 2);
334
474
  assert(namespace && methodName, `Invalid namespaced method name: ${namespacedMethodName}`);
335
475
  const handler = this.proxies[namespace];
336
476
  assert(handler, `Namespace not found: ${namespace}`);
337
- return handler.call(methodName, jsonParams);
477
+ return handler.call(methodName, jsonParams, onRequestValidated);
338
478
  }
339
479
 
340
480
  public hasMethod(namespacedMethodName: string): boolean {
@@ -353,23 +493,23 @@ export function makeHandler<T extends object>(handler: T, schema: ApiSchemaFor<T
353
493
  }
354
494
 
355
495
  function makeAggregateHealthcheck(namedHandlers: NamespacedApiHandlers, log?: Logger): StatusCheckFn {
356
- return async () => {
496
+ return async (): Promise<StatusCheckResult> => {
357
497
  try {
358
- const results = await Promise.all(
359
- Object.entries(namedHandlers).map(async ([name, [, , healthCheck]]) => [
360
- name,
361
- healthCheck ? await healthCheck() : true,
362
- ]),
498
+ const entries = await Promise.all(
499
+ Object.entries(namedHandlers).map(async ([name, [, , healthCheck]]): Promise<[string, ComponentStatus]> => {
500
+ const { healthy, details } = normalizeStatusCheckResult(healthCheck ? await healthCheck() : true);
501
+ return [name, { healthy, ...details }];
502
+ }),
363
503
  );
364
- const failed = results.filter(([_, result]) => !result);
504
+ const components = Object.fromEntries(entries);
505
+ const failed = entries.filter(([, status]) => !status.healthy).map(([name]) => name);
365
506
  if (failed.length > 0) {
366
- log?.warn(`Health check failed for ${failed.map(([name]) => name).join(', ')}`);
367
- return false;
507
+ log?.warn(`Health check failed for ${failed.join(', ')}`, { components });
368
508
  }
369
- return true;
509
+ return { healthy: failed.length === 0, details: { components } };
370
510
  } catch (err) {
371
511
  log?.error(`Error during health check`, err);
372
- return false;
512
+ return { healthy: false };
373
513
  }
374
514
  };
375
515
  }
@@ -377,6 +517,7 @@ function makeAggregateHealthcheck(namedHandlers: NamespacedApiHandlers, log?: Lo
377
517
  export type SafeJsonRpcServerOptions = Partial<
378
518
  SafeJsonRpcServerConfig & {
379
519
  healthCheck: StatusCheckFn;
520
+ diagnostic: DiagnosticsMiddleware;
380
521
  log: Logger;
381
522
  middlewares: Application.Middleware[];
382
523
  }
@@ -391,10 +532,10 @@ export function createNamespacedSafeJsonRpcServer(
391
532
  handlers: NamespacedApiHandlers,
392
533
  options: Omit<SafeJsonRpcServerOptions, 'healthcheck'> = {},
393
534
  ): SafeJsonRpcServer {
394
- const { middlewares, log } = options;
535
+ const { diagnostic, middlewares, log } = options;
395
536
  const proxy = new NamespacedSafeJsonProxy(handlers);
396
537
  const healthCheck = makeAggregateHealthcheck(handlers, log);
397
- return new SafeJsonRpcServer(proxy, options, healthCheck, middlewares, log);
538
+ return new SafeJsonRpcServer(proxy, options, healthCheck, middlewares, diagnostic, log);
398
539
  }
399
540
 
400
541
  export function createSafeJsonRpcServer<T extends object = any>(
@@ -402,51 +543,61 @@ export function createSafeJsonRpcServer<T extends object = any>(
402
543
  schema: ApiSchemaFor<T>,
403
544
  options: SafeJsonRpcServerOptions = {},
404
545
  ) {
405
- const { log, healthCheck, middlewares: extraMiddlewares } = options;
546
+ const { diagnostic, log, healthCheck, middlewares: extraMiddlewares } = options;
406
547
  const proxy = new SafeJsonProxy(handler, schema);
407
- return new SafeJsonRpcServer(proxy, options, healthCheck, extraMiddlewares, log);
548
+ return new SafeJsonRpcServer(proxy, options, healthCheck, extraMiddlewares, diagnostic, log);
408
549
  }
409
550
 
410
551
  /**
411
- * Creates a router for handling a plain status request that will return 200 status when running.
412
- * @param getCurrentStatus - List of health check functions to run.
552
+ * Creates a router for handling a status request that returns a 200 status code when healthy and a 500 otherwise,
553
+ * along with the reported status as a JSON body.
554
+ * @param getCurrentStatus - Function returning the current status of the server.
413
555
  * @param apiPrefix - The prefix to use for all api requests
414
556
  * @returns - The router for handling status requests.
415
557
  */
416
- export function createStatusRouter(getCurrentStatus: StatusCheckFn, apiPrefix = '') {
558
+ export function createStatusRouter(getCurrentStatus: ServerStatusFn, apiPrefix = '') {
417
559
  const router = new Router({ prefix: `${apiPrefix}` });
418
560
  router.get('/status', async (ctx: Koa.Context) => {
419
- let ok: boolean;
561
+ let status: ServerStatus;
420
562
  try {
421
- ok = (await getCurrentStatus()) === true;
563
+ status = await getCurrentStatus();
422
564
  } catch {
423
- ok = false;
565
+ status = { ok: false };
424
566
  }
425
567
 
426
- ctx.status = ok ? 200 : 500;
568
+ ctx.status = status.ok === true ? 200 : 500;
569
+ ctx.type = 'application/json';
570
+ ctx.body = status;
427
571
  });
428
572
  return router;
429
573
  }
430
574
 
431
575
  /**
432
576
  * Wraps a JsonRpcServer in a nodejs http server and starts it.
433
- * Installs a status router that calls to the isHealthy method to the server.
577
+ * Installs a status router that reports the status of the server, using its getStatus method when available so that
578
+ * per-component health is included, and falling back to isHealthy otherwise.
434
579
  * Returns once starts listening unless noWait is set.
435
580
  * @returns A running http server.
436
581
  */
437
582
  export async function startHttpRpcServer(
438
- rpcServer: Pick<SafeJsonRpcServer, 'getApp' | 'isHealthy'>,
583
+ rpcServer: Pick<SafeJsonRpcServer, 'getApp' | 'isHealthy'> & Partial<Pick<SafeJsonRpcServer, 'getStatus'>>,
439
584
  options: {
440
585
  host?: string;
441
586
  port?: number | string;
442
587
  apiPrefix?: string;
443
588
  timeoutMs?: number;
589
+ keepAliveTimeoutMs?: number;
590
+ headersTimeoutMs?: number;
444
591
  noWait?: boolean;
445
592
  } = {},
446
593
  ): Promise<http.Server & { port: number }> {
447
594
  const app = rpcServer.getApp(options.apiPrefix);
448
595
 
449
- const statusRouter = createStatusRouter(rpcServer.isHealthy.bind(rpcServer), options.apiPrefix);
596
+ const getStatus: ServerStatusFn = rpcServer.getStatus
597
+ ? rpcServer.getStatus.bind(rpcServer)
598
+ : async () => ({ ok: (await rpcServer.isHealthy()) === true });
599
+
600
+ const statusRouter = createStatusRouter(getStatus, options.apiPrefix);
450
601
  app.use(statusRouter.routes()).use(statusRouter.allowedMethods());
451
602
 
452
603
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
@@ -454,6 +605,12 @@ export async function startHttpRpcServer(
454
605
  if (options.timeoutMs) {
455
606
  httpServer.timeout = options.timeoutMs;
456
607
  }
608
+ if (options.keepAliveTimeoutMs !== undefined) {
609
+ httpServer.keepAliveTimeout = options.keepAliveTimeoutMs;
610
+ }
611
+ if (options.headersTimeoutMs !== undefined) {
612
+ httpServer.headersTimeout = options.headersTimeoutMs;
613
+ }
457
614
 
458
615
  const { promise, resolve } = promiseWithResolvers<void>();
459
616
  const listenPort = options.port ? (typeof options.port === 'string' ? parseInt(options.port) : options.port) : 0;
@@ -0,0 +1,32 @@
1
+ import type { pino } from 'pino';
2
+
3
+ function getSeverity(label: string): { severityText: string; severityNumber: number } {
4
+ switch (label) {
5
+ case 'trace':
6
+ return { severityText: 'TRACE', severityNumber: 1 };
7
+ case 'debug':
8
+ return { severityText: 'DEBUG', severityNumber: 5 };
9
+ case 'verbose':
10
+ return { severityText: 'VERBOSE', severityNumber: 7 };
11
+ case 'info':
12
+ return { severityText: 'INFO', severityNumber: 9 };
13
+ case 'warn':
14
+ return { severityText: 'WARN', severityNumber: 13 };
15
+ case 'error':
16
+ return { severityText: 'ERROR', severityNumber: 17 };
17
+ case 'fatal':
18
+ return { severityText: 'FATAL', severityNumber: 21 };
19
+ default:
20
+ return { severityText: 'UNSPECIFIED', severityNumber: 0 };
21
+ }
22
+ }
23
+
24
+ /** Pino configuration that adds OpenTelemetry severity fields understood by CloudWatch Logs. */
25
+ export const AWSCloudLoggerConfig = {
26
+ messageKey: 'msg',
27
+ formatters: {
28
+ level(label: string, level: number): object {
29
+ return { level, ...getSeverity(label) };
30
+ },
31
+ },
32
+ } satisfies pino.LoggerOptions;
@@ -7,6 +7,7 @@ import { inspect } from 'util';
7
7
  import { compactArray } from '../collection/array.js';
8
8
  import type { EnvVar } from '../config/index.js';
9
9
  import { parseBooleanEnv } from '../config/parse-env.js';
10
+ import { AWSCloudLoggerConfig } from './aws-logger-config.js';
10
11
  import { convertBigintsToStrings } from './bigint-utils.js';
11
12
  import { GoogleCloudLoggerConfig } from './gcloud-logger-config.js';
12
13
  import { getLogLevelFromFilters, parseLogLevelEnvVar } from './log-filters.js';
@@ -134,8 +135,17 @@ export const [logLevel, logFilters] = parseLogLevelEnvVar(process.env.LOG_LEVEL,
134
135
  // Define custom logging levels for pino.
135
136
  const customLevels = { verbose: 25 };
136
137
 
137
- // Global pino options, tweaked for google cloud if running there.
138
+ // Global pino options, tweaked for the active cloud logging backend.
138
139
  const useGcloudLogging = parseBooleanEnv(process.env['USE_GCLOUD_LOGGING' satisfies EnvVar]);
140
+ const useAwsLogging = parseBooleanEnv(process.env['USE_AWS_LOGGING' satisfies EnvVar]);
141
+ const loggingConfig = useGcloudLogging
142
+ ? GoogleCloudLoggerConfig
143
+ : useAwsLogging
144
+ ? AWSCloudLoggerConfig
145
+ : {
146
+ formatters: {},
147
+ messageKey: 'msg',
148
+ };
139
149
 
140
150
  const redactedPaths = [
141
151
  'validatorPrivateKeys',
@@ -156,7 +166,7 @@ const redactedPaths = [
156
166
 
157
167
  const pinoOpts: pino.LoggerOptions<keyof typeof customLevels> = {
158
168
  customLevels,
159
- messageKey: 'msg',
169
+ messageKey: loggingConfig.messageKey,
160
170
  useOnlyCustomLevels: false,
161
171
  level: logLevel,
162
172
  redact: {
@@ -170,8 +180,8 @@ const pinoOpts: pino.LoggerOptions<keyof typeof customLevels> = {
170
180
  },
171
181
  formatters: {
172
182
  log: obj => convertBigintsToStrings(obj) as Record<string, unknown>,
183
+ ...loggingConfig.formatters,
173
184
  },
174
- ...(useGcloudLogging ? GoogleCloudLoggerConfig : {}),
175
185
  };
176
186
 
177
187
  export const levels = {
@@ -241,7 +251,7 @@ const pinoPrettyBaseOpts = {
241
251
  destination: 2,
242
252
  sync: true,
243
253
  colorize: useColor,
244
- ignore: 'module,actor,instanceId,pid,hostname,trace_id,span_id,trace_flags,severity',
254
+ ignore: 'module,actor,instanceId,pid,hostname,trace_id,span_id,trace_flags,severity,severityText,severityNumber',
245
255
  customLevels: 'fatal:60,error:50,warn:40,info:30,verbose:25,debug:20,trace:10',
246
256
  customColors: 'fatal:bgRed,error:red,warn:yellow,info:green,verbose:magenta,debug:blue,trace:gray',
247
257
  minimumLevel: 'trace' as const,
@@ -377,5 +387,20 @@ export type Logger = { [K in LogLevel]: LogFn } & { /** Error log function */ er
377
387
  * @returns A string with both the log message and the error message.
378
388
  */
379
389
  function formatErr(msg: string, err?: unknown): string {
380
- return err ? `${msg}: ${inspect(err)}` : msg;
390
+ if (!err) {
391
+ return msg;
392
+ }
393
+
394
+ try {
395
+ return `${msg}: ${inspect(err)}`;
396
+ } catch {
397
+ // inspect can crash on error objects with broken property descriptors
398
+ try {
399
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
400
+ return `${msg}: ${err instanceof Error ? `${err.name}: ${err.message}` : String(err)}`;
401
+ } catch {
402
+ // if even String(err) fails, return the original message with a note about the error
403
+ return `${msg}: [unserializable error]`;
404
+ }
405
+ }
381
406
  }
@@ -10,26 +10,6 @@ const noirLocalDependencySchema = z.object({
10
10
  path: z.string(),
11
11
  });
12
12
 
13
- const noirPackageConfigSchema = z.object({
14
- package: z.object({
15
- name: z.string().default(''),
16
- type: z.enum(['lib', 'contract', 'bin']).default('bin'),
17
- entry: z.string().optional(),
18
- description: z.string().optional(),
19
- authors: z.array(z.string()).optional(),
20
- // eslint-disable-next-line camelcase
21
- compiler_version: z.string().optional(),
22
- backend: z.string().optional(),
23
- license: z.string().optional(),
24
- }),
25
- dependencies: z.record(z.union([noirGitDependencySchema, noirLocalDependencySchema])).default({}),
26
- });
27
-
28
- /**
29
- * Noir package configuration.
30
- */
31
- export type NoirPackageConfig = z.infer<typeof noirPackageConfigSchema>;
32
-
33
13
  /**
34
14
  * A remote package dependency.
35
15
  */
@@ -45,6 +25,38 @@ export type NoirLocalDependencyConfig = z.infer<typeof noirLocalDependencySchema
45
25
  */
46
26
  export type NoirDependencyConfig = NoirGitDependencyConfig | NoirLocalDependencyConfig;
47
27
 
28
+ /**
29
+ * Noir package configuration.
30
+ */
31
+ export type NoirPackageConfig = {
32
+ package: {
33
+ name: string;
34
+ type: 'lib' | 'contract' | 'bin';
35
+ entry?: string | undefined;
36
+ description?: string | undefined;
37
+ authors?: string[] | undefined;
38
+ compiler_version?: string | undefined;
39
+ backend?: string | undefined;
40
+ license?: string | undefined;
41
+ };
42
+ dependencies: Record<string, NoirDependencyConfig>;
43
+ };
44
+
45
+ const noirPackageConfigSchema: z.ZodType<NoirPackageConfig> = z.object({
46
+ package: z.object({
47
+ name: z.string().default(''),
48
+ type: z.enum(['lib', 'contract', 'bin']).default('bin'),
49
+ entry: z.string().optional(),
50
+ description: z.string().optional(),
51
+ authors: z.array(z.string()).optional(),
52
+ // eslint-disable-next-line camelcase
53
+ compiler_version: z.string().optional(),
54
+ backend: z.string().optional(),
55
+ license: z.string().optional(),
56
+ }),
57
+ dependencies: z.record(z.string(), z.union([noirGitDependencySchema, noirLocalDependencySchema])).default({}),
58
+ });
59
+
48
60
  /**
49
61
  * Checks that an object is a package configuration.
50
62
  * @param config - Config to check