@aztec/stdlib 5.0.0-nightly.20260701 → 5.0.0-nightly.20260702
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/block/l2_block_source.d.ts +22 -2
- package/dest/block/l2_block_source.d.ts.map +1 -1
- package/dest/block/l2_block_source.js +10 -0
- package/dest/block/l2_block_stream/event_driven_l2_block_stream.d.ts +39 -0
- package/dest/block/l2_block_stream/event_driven_l2_block_stream.d.ts.map +1 -0
- package/dest/block/l2_block_stream/event_driven_l2_block_stream.js +165 -0
- package/dest/block/l2_block_stream/index.d.ts +2 -1
- package/dest/block/l2_block_stream/index.d.ts.map +1 -1
- package/dest/block/l2_block_stream/index.js +1 -0
- package/dest/block/l2_block_stream/interfaces.d.ts +16 -2
- package/dest/block/l2_block_stream/interfaces.d.ts.map +1 -1
- package/dest/block/l2_block_stream/interfaces.js +25 -1
- package/dest/block/l2_block_stream/l2_block_stream.d.ts +21 -35
- package/dest/block/l2_block_stream/l2_block_stream.d.ts.map +1 -1
- package/dest/block/l2_block_stream/l2_block_stream.js +5 -30
- package/dest/contract/contract_address.d.ts +3 -2
- package/dest/contract/contract_address.d.ts.map +1 -1
- package/dest/contract/partial_address.d.ts +9 -3
- package/dest/contract/partial_address.d.ts.map +1 -1
- package/dest/contract/partial_address.js +8 -2
- package/dest/interfaces/proving-job.d.ts +70 -70
- package/dest/keys/derivation.d.ts +31 -1
- package/dest/keys/derivation.d.ts.map +1 -1
- package/dest/keys/derivation.js +19 -11
- package/dest/logs/app_tagging_secret.d.ts +11 -8
- package/dest/logs/app_tagging_secret.d.ts.map +1 -1
- package/dest/logs/app_tagging_secret.js +14 -14
- package/dest/logs/shared_secret_derivation.d.ts +14 -7
- package/dest/logs/shared_secret_derivation.d.ts.map +1 -1
- package/dest/logs/shared_secret_derivation.js +16 -8
- package/package.json +8 -8
- package/src/block/l2_block_source.ts +41 -0
- package/src/block/l2_block_stream/event_driven_l2_block_stream.ts +207 -0
- package/src/block/l2_block_stream/index.ts +1 -0
- package/src/block/l2_block_stream/interfaces.ts +35 -1
- package/src/block/l2_block_stream/l2_block_stream.ts +35 -62
- package/src/contract/contract_address.ts +2 -1
- package/src/contract/partial_address.ts +8 -2
- package/src/keys/derivation.ts +38 -9
- package/src/logs/app_tagging_secret.ts +14 -10
- package/src/logs/shared_secret_derivation.ts +17 -10
|
@@ -5,6 +5,7 @@ import { z } from 'zod';
|
|
|
5
5
|
import { AztecAddress } from '../aztec-address/index.js';
|
|
6
6
|
import { computeAddressSecret, computePreaddress } from '../keys/derivation.js';
|
|
7
7
|
import { AppTaggingSecretKind } from './app_tagging_secret_kind.js';
|
|
8
|
+
import { appSiloEcdhSharedSecretPoint } from './shared_secret_derivation.js';
|
|
8
9
|
const AppTaggingSecretKindSchema = z.union([
|
|
9
10
|
z.literal(AppTaggingSecretKind.UNCONSTRAINED),
|
|
10
11
|
z.literal(AppTaggingSecretKind.CONSTRAINED)
|
|
@@ -27,36 +28,35 @@ const AppTaggingSecretKindSchema = z.union([
|
|
|
27
28
|
/**
|
|
28
29
|
* Derives an app-siloed, recipient-directional tagging secret from a shared tagging secret point.
|
|
29
30
|
*
|
|
31
|
+
* App-silos the point via {@link appSiloEcdhSharedSecretPoint}, then directs the result to `recipient`:
|
|
32
|
+
* `h([s_app, recipient])`. The directional step stops a symmetric shared secret (ECDH against an address, or an
|
|
33
|
+
* arbitrary registered point) from colliding bidirectionally, so two parties never share a tag sequence.
|
|
34
|
+
*
|
|
30
35
|
* The point is obtained either via {@link computeSharedTaggingSecret} (an ECDH key exchange against a sender) or
|
|
31
|
-
* registered directly as a pre-shared secret.
|
|
32
|
-
* pair.
|
|
36
|
+
* registered directly as a pre-shared secret.
|
|
33
37
|
*
|
|
34
38
|
* @param taggingSecretPoint - The shared tagging secret point (ECDH output, or a directly registered pre-shared secret)
|
|
35
39
|
* @param app - Contract address to silo the secret to
|
|
36
40
|
* @param recipient - Recipient of the log. Defines the "direction of the secret".
|
|
37
41
|
* @returns The secret that can be used along with an index to compute a tag to be included in a log.
|
|
38
|
-
*/ static async
|
|
39
|
-
const
|
|
40
|
-
taggingSecretPoint.x,
|
|
41
|
-
taggingSecretPoint.y,
|
|
42
|
-
app
|
|
43
|
-
]);
|
|
42
|
+
*/ static async computeDirectional(taggingSecretPoint, app, recipient) {
|
|
43
|
+
const appSiloedSecret = await appSiloEcdhSharedSecretPoint(taggingSecretPoint, app);
|
|
44
44
|
const directionalAppTaggingSecret = await poseidon2Hash([
|
|
45
|
-
|
|
45
|
+
appSiloedSecret,
|
|
46
46
|
recipient
|
|
47
47
|
]);
|
|
48
48
|
return new AppTaggingSecret(directionalAppTaggingSecret, app);
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
-
* Derives the
|
|
52
|
-
*
|
|
53
|
-
* `externalAddress` is not a valid address.
|
|
54
|
-
*/ static async
|
|
51
|
+
* Derives the tagging secret for `(externalAddress, recipient, app)` by performing an ECDH key exchange against
|
|
52
|
+
* `externalAddress` to obtain the shared point, then siloing and directing it via {@link computeDirectional}.
|
|
53
|
+
* Returns undefined if `externalAddress` is not a valid address.
|
|
54
|
+
*/ static async computeViaEcdh(localAddress, localIvsk, externalAddress, app, recipient) {
|
|
55
55
|
const taggingSecretPoint = await computeSharedTaggingSecret(localAddress, localIvsk, externalAddress);
|
|
56
56
|
if (!taggingSecretPoint) {
|
|
57
57
|
return undefined;
|
|
58
58
|
}
|
|
59
|
-
return AppTaggingSecret.
|
|
59
|
+
return AppTaggingSecret.computeDirectional(taggingSecretPoint, app, recipient);
|
|
60
60
|
}
|
|
61
61
|
toString() {
|
|
62
62
|
// TODO(F-680): Migrate stored tagging keys and remove the legacy unconstrained format.
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
|
-
import type { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin';
|
|
2
|
+
import type { GrumpkinScalar, Point } from '@aztec/foundation/curves/grumpkin';
|
|
3
3
|
import type { AztecAddress } from '../aztec-address/index.js';
|
|
4
4
|
import type { PublicKey } from '../keys/public_key.js';
|
|
5
5
|
/**
|
|
6
|
-
* Derives an app-siloed ECDH shared secret
|
|
7
|
-
*
|
|
8
|
-
* Computes the raw ECDH shared secret `S = secretKey * publicKey`, then app-silos it:
|
|
9
|
-
* `s_app = h(DOM_SEP__APP_SILOED_ECDH_SHARED_SECRET, S.x, S.y, contractAddress)`
|
|
6
|
+
* Derives an app-siloed ECDH shared secret from keys: ECDHs `S = secretKey * publicKey`, then app-silos it via
|
|
7
|
+
* {@link appSiloEcdhSharedSecretPoint}.
|
|
10
8
|
*
|
|
11
9
|
* @param secretKey - The secret key used to derive shared secret.
|
|
12
10
|
* @param publicKey - The public key used to derive shared secret.
|
|
@@ -14,5 +12,14 @@ import type { PublicKey } from '../keys/public_key.js';
|
|
|
14
12
|
* @returns The app-siloed shared secret as a Field.
|
|
15
13
|
* @throws If the publicKey is zero.
|
|
16
14
|
*/
|
|
17
|
-
export declare function
|
|
18
|
-
|
|
15
|
+
export declare function appSiloEcdhSharedSecret(secretKey: GrumpkinScalar, publicKey: PublicKey, contractAddress: AztecAddress): Promise<Fr>;
|
|
16
|
+
/**
|
|
17
|
+
* App-silos a shared secret point: `s_app = h(DOM_SEP__APP_SILOED_ECDH_SHARED_SECRET, S.x, S.y, app)`.
|
|
18
|
+
*
|
|
19
|
+
* Mirrors `compute_app_siloed_shared_secret` in aztec-nr.
|
|
20
|
+
*
|
|
21
|
+
* @param point - The raw shared secret point `S`.
|
|
22
|
+
* @param app - The contract address to silo to.
|
|
23
|
+
*/
|
|
24
|
+
export declare function appSiloEcdhSharedSecretPoint(point: Point, app: AztecAddress): Promise<Fr>;
|
|
25
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2hhcmVkX3NlY3JldF9kZXJpdmF0aW9uLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbG9ncy9zaGFyZWRfc2VjcmV0X2Rlcml2YXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBR0EsT0FBTyxLQUFLLEVBQUUsRUFBRSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDekQsT0FBTyxLQUFLLEVBQUUsY0FBYyxFQUFFLEtBQUssRUFBRSxNQUFNLG1DQUFtQyxDQUFDO0FBRS9FLE9BQU8sS0FBSyxFQUFFLFlBQVksRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQzlELE9BQU8sS0FBSyxFQUFFLFNBQVMsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBRXZEOzs7Ozs7Ozs7R0FTRztBQUNILHdCQUFzQix1QkFBdUIsQ0FDM0MsU0FBUyxFQUFFLGNBQWMsRUFDekIsU0FBUyxFQUFFLFNBQVMsRUFDcEIsZUFBZSxFQUFFLFlBQVksR0FDNUIsT0FBTyxDQUFDLEVBQUUsQ0FBQyxDQVFiO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILHdCQUFnQiw0QkFBNEIsQ0FBQyxLQUFLLEVBQUUsS0FBSyxFQUFFLEdBQUcsRUFBRSxZQUFZLEdBQUcsT0FBTyxDQUFDLEVBQUUsQ0FBQyxDQUV6RiJ9
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared_secret_derivation.d.ts","sourceRoot":"","sources":["../../src/logs/shared_secret_derivation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"shared_secret_derivation.d.ts","sourceRoot":"","sources":["../../src/logs/shared_secret_derivation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,mCAAmC,CAAC;AAE/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAC3C,SAAS,EAAE,cAAc,EACzB,SAAS,EAAE,SAAS,EACpB,eAAe,EAAE,YAAY,GAC5B,OAAO,CAAC,EAAE,CAAC,CAQb;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,EAAE,CAAC,CAEzF"}
|
|
@@ -2,24 +2,32 @@ import { DomainSeparator } from '@aztec/constants';
|
|
|
2
2
|
import { Grumpkin } from '@aztec/foundation/crypto/grumpkin';
|
|
3
3
|
import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto/poseidon';
|
|
4
4
|
/**
|
|
5
|
-
* Derives an app-siloed ECDH shared secret
|
|
6
|
-
*
|
|
7
|
-
* Computes the raw ECDH shared secret `S = secretKey * publicKey`, then app-silos it:
|
|
8
|
-
* `s_app = h(DOM_SEP__APP_SILOED_ECDH_SHARED_SECRET, S.x, S.y, contractAddress)`
|
|
5
|
+
* Derives an app-siloed ECDH shared secret from keys: ECDHs `S = secretKey * publicKey`, then app-silos it via
|
|
6
|
+
* {@link appSiloEcdhSharedSecretPoint}.
|
|
9
7
|
*
|
|
10
8
|
* @param secretKey - The secret key used to derive shared secret.
|
|
11
9
|
* @param publicKey - The public key used to derive shared secret.
|
|
12
10
|
* @param contractAddress - The address of the calling contract, used for app-siloing.
|
|
13
11
|
* @returns The app-siloed shared secret as a Field.
|
|
14
12
|
* @throws If the publicKey is zero.
|
|
15
|
-
*/ export async function
|
|
13
|
+
*/ export async function appSiloEcdhSharedSecret(secretKey, publicKey, contractAddress) {
|
|
16
14
|
if (publicKey.isZero()) {
|
|
17
15
|
throw new Error(`Attempting to derive a shared secret with a zero public key. You have probably passed a zero public key in your Noir code somewhere thinking that the note won't be broadcast... but it was.`);
|
|
18
16
|
}
|
|
19
17
|
const rawSharedSecret = await Grumpkin.mul(publicKey, secretKey);
|
|
18
|
+
return appSiloEcdhSharedSecretPoint(rawSharedSecret, contractAddress);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* App-silos a shared secret point: `s_app = h(DOM_SEP__APP_SILOED_ECDH_SHARED_SECRET, S.x, S.y, app)`.
|
|
22
|
+
*
|
|
23
|
+
* Mirrors `compute_app_siloed_shared_secret` in aztec-nr.
|
|
24
|
+
*
|
|
25
|
+
* @param point - The raw shared secret point `S`.
|
|
26
|
+
* @param app - The contract address to silo to.
|
|
27
|
+
*/ export function appSiloEcdhSharedSecretPoint(point, app) {
|
|
20
28
|
return poseidon2HashWithSeparator([
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
point.x,
|
|
30
|
+
point.y,
|
|
31
|
+
app
|
|
24
32
|
], DomainSeparator.APP_SILOED_ECDH_SHARED_SECRET);
|
|
25
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/stdlib",
|
|
3
|
-
"version": "5.0.0-nightly.
|
|
3
|
+
"version": "5.0.0-nightly.20260702",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"inherits": [
|
|
6
6
|
"../package.common.json",
|
|
@@ -92,13 +92,13 @@
|
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
94
|
"@aws-sdk/client-s3": "^3.892.0",
|
|
95
|
-
"@aztec/bb.js": "5.0.0-nightly.
|
|
96
|
-
"@aztec/blob-lib": "5.0.0-nightly.
|
|
97
|
-
"@aztec/constants": "5.0.0-nightly.
|
|
98
|
-
"@aztec/ethereum": "5.0.0-nightly.
|
|
99
|
-
"@aztec/foundation": "5.0.0-nightly.
|
|
100
|
-
"@aztec/l1-artifacts": "5.0.0-nightly.
|
|
101
|
-
"@aztec/noir-noirc_abi": "5.0.0-nightly.
|
|
95
|
+
"@aztec/bb.js": "5.0.0-nightly.20260702",
|
|
96
|
+
"@aztec/blob-lib": "5.0.0-nightly.20260702",
|
|
97
|
+
"@aztec/constants": "5.0.0-nightly.20260702",
|
|
98
|
+
"@aztec/ethereum": "5.0.0-nightly.20260702",
|
|
99
|
+
"@aztec/foundation": "5.0.0-nightly.20260702",
|
|
100
|
+
"@aztec/l1-artifacts": "5.0.0-nightly.20260702",
|
|
101
|
+
"@aztec/noir-noirc_abi": "5.0.0-nightly.20260702",
|
|
102
102
|
"@google-cloud/storage": "^7.15.0",
|
|
103
103
|
"axios": "^1.15.1",
|
|
104
104
|
"json-stringify-deterministic": "1.0.12",
|
|
@@ -324,6 +324,7 @@ export type ArchiverEmitter = TypedEventEmitter<{
|
|
|
324
324
|
[L2BlockSourceEvents.DescendentOfInvalidAttestationsCheckpointDetected]: (
|
|
325
325
|
args: DescendentOfInvalidAttestationsCheckpointEvent,
|
|
326
326
|
) => void;
|
|
327
|
+
[L2BlockSourceEvents.L2BlockSourceUpdated]: (args: L2BlockSourceUpdatedEvent) => void;
|
|
327
328
|
}>;
|
|
328
329
|
export interface L2BlockSourceEventEmitter extends L2BlockSource {
|
|
329
330
|
events: ArchiverEmitter;
|
|
@@ -376,6 +377,28 @@ export function makeL2CheckpointId(number: CheckpointNumber, hash: string): Chec
|
|
|
376
377
|
return { number, hash };
|
|
377
378
|
}
|
|
378
379
|
|
|
380
|
+
function l2BlockIdEquals(a: L2BlockId, b: L2BlockId): boolean {
|
|
381
|
+
return a.number === b.number && a.hash === b.hash;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function l2TipIdEquals(a: L2TipId, b: L2TipId): boolean {
|
|
385
|
+
return (
|
|
386
|
+
l2BlockIdEquals(a.block, b.block) &&
|
|
387
|
+
a.checkpoint.number === b.checkpoint.number &&
|
|
388
|
+
a.checkpoint.hash === b.checkpoint.hash
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Returns whether two {@link L2Tips} snapshots agree on every tier (proposed, checkpointed, proven, finalized). */
|
|
393
|
+
export function l2TipsEqual(a: L2Tips, b: L2Tips): boolean {
|
|
394
|
+
return (
|
|
395
|
+
l2BlockIdEquals(a.proposed, b.proposed) &&
|
|
396
|
+
l2TipIdEquals(a.checkpointed, b.checkpointed) &&
|
|
397
|
+
l2TipIdEquals(a.proven, b.proven) &&
|
|
398
|
+
l2TipIdEquals(a.finalized, b.finalized)
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
379
402
|
const L2BlockIdSchema = z.object({
|
|
380
403
|
number: BlockNumberSchema,
|
|
381
404
|
hash: z.string(),
|
|
@@ -406,8 +429,26 @@ export enum L2BlockSourceEvents {
|
|
|
406
429
|
InvalidAttestationsCheckpointDetected = 'invalidCheckpointDetected',
|
|
407
430
|
CheckpointEquivocationDetected = 'checkpointEquivocationDetected',
|
|
408
431
|
DescendentOfInvalidAttestationsCheckpointDetected = 'descendentOfInvalidAttestationsCheckpointDetected',
|
|
432
|
+
L2BlockSourceUpdated = 'l2BlockSourceUpdated',
|
|
409
433
|
}
|
|
410
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Aggregate event emitted once per committed archiver sync pass that mutated local state. Carries the chain tips
|
|
437
|
+
* before and after the pass, and the blocks added during it. Consumers compare `fromTips` and `toTips` to learn what
|
|
438
|
+
* moved; there is no separate `changed` section.
|
|
439
|
+
*
|
|
440
|
+
* This is an optimization signal that lets a block stream reconcile immediately on an archiver update rather than
|
|
441
|
+
* waiting for its next poll. Polling remains the correctness fallback, so a missed event only affects latency.
|
|
442
|
+
* `blocksAdded` are hydrated blocks already in hand from the sync pass, so a triggered sync that is caught up to
|
|
443
|
+
* `fromTips` can reuse them (and `toTips`) instead of re-reading the store.
|
|
444
|
+
*/
|
|
445
|
+
export type L2BlockSourceUpdatedEvent = {
|
|
446
|
+
type: 'l2BlockSourceUpdated';
|
|
447
|
+
fromTips: L2Tips;
|
|
448
|
+
toTips: L2Tips;
|
|
449
|
+
blocksAdded: readonly L2Block[];
|
|
450
|
+
};
|
|
451
|
+
|
|
411
452
|
export type L2BlockProvenEvent = {
|
|
412
453
|
type: 'l2BlockProven';
|
|
413
454
|
blockNumber: BlockNumber;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
2
|
+
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
3
|
+
|
|
4
|
+
import type { BlockData } from '../block_data.js';
|
|
5
|
+
import type { L2Block } from '../l2_block.js';
|
|
6
|
+
import {
|
|
7
|
+
type ArchiverEmitter,
|
|
8
|
+
type BlockQuery,
|
|
9
|
+
type BlocksQuery,
|
|
10
|
+
type L2BlockSource,
|
|
11
|
+
type L2BlockSourceEventEmitter,
|
|
12
|
+
L2BlockSourceEvents,
|
|
13
|
+
type L2BlockSourceUpdatedEvent,
|
|
14
|
+
type L2Tips,
|
|
15
|
+
} from '../l2_block_source.js';
|
|
16
|
+
import { type L2BlockStreamEventHandler, type L2BlockStreamLocalDataProvider, localTipsMatch } from './interfaces.js';
|
|
17
|
+
import { L2BlockStream, type L2BlockStreamOptions, type L2BlockStreamSource } from './l2_block_stream.js';
|
|
18
|
+
|
|
19
|
+
/** Derives the metadata-only {@link BlockData} view of a hydrated {@link L2Block}. */
|
|
20
|
+
async function l2BlockToBlockData(block: L2Block): Promise<BlockData> {
|
|
21
|
+
return {
|
|
22
|
+
header: block.header,
|
|
23
|
+
archive: block.archive,
|
|
24
|
+
blockHash: await block.hash(),
|
|
25
|
+
checkpointNumber: block.checkpointNumber,
|
|
26
|
+
indexWithinCheckpoint: block.indexWithinCheckpoint,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Returns the event emitter of a source that exposes one, or undefined for plain (e.g. RPC-backed) sources. */
|
|
31
|
+
function getEmitter(source: L2BlockSource | L2BlockSourceEventEmitter): ArchiverEmitter | undefined {
|
|
32
|
+
return 'events' in source ? source.events : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Fast-path context for a single sync pass: blocks to serve by number, plus the tips to report as the source's. */
|
|
36
|
+
type ActiveUpdate = { byNumber: Map<number, L2Block>; toTips: L2Tips };
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Wraps a block source so a single sync pass can be served from blocks delivered by an aggregate update event,
|
|
40
|
+
* avoiding round-trips to archiver storage. The fast path is only armed (via {@link activate}) for a pass that is
|
|
41
|
+
* confirmed caught up to the event's pre-pass tips: in that case the event's `blocksAdded` contiguously cover the
|
|
42
|
+
* pass's download range and `toTips` is the exact post-pass tip, so `getL2Tips` can report it without querying the
|
|
43
|
+
* source. When the fast path is not armed, every read delegates to the source, so a stale or partial cache never
|
|
44
|
+
* changes the sync outcome.
|
|
45
|
+
*/
|
|
46
|
+
class HotBlockSourceAdapter implements L2BlockStreamSource {
|
|
47
|
+
/** Set for the duration of one fast-path pass; undefined when reads must delegate to the source. */
|
|
48
|
+
private active: ActiveUpdate | undefined;
|
|
49
|
+
|
|
50
|
+
constructor(
|
|
51
|
+
private readonly source: L2BlockStreamSource,
|
|
52
|
+
private readonly log: Logger,
|
|
53
|
+
) {}
|
|
54
|
+
|
|
55
|
+
/** Arms the fast path for the current pass: serve these blocks and report `toTips` as the source tips. */
|
|
56
|
+
public activate(blocks: readonly L2Block[], toTips: L2Tips): void {
|
|
57
|
+
const byNumber = new Map<number, L2Block>();
|
|
58
|
+
for (const block of blocks) {
|
|
59
|
+
byNumber.set(block.number, block);
|
|
60
|
+
}
|
|
61
|
+
this.active = { byNumber, toTips };
|
|
62
|
+
this.log.trace(`Armed hot-block fast path`, { blocks: byNumber.size, toTips });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Disarms the fast path so subsequent reads delegate to the source again. */
|
|
66
|
+
public deactivate(): void {
|
|
67
|
+
this.active = undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public getL2Tips(): Promise<L2Tips> {
|
|
71
|
+
return this.active ? Promise.resolve(this.active.toTips) : this.source.getL2Tips();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public getBlocks(query: BlocksQuery): Promise<L2Block[]> {
|
|
75
|
+
const served = this.active ? this.tryServeBlocksFromCache(query) : undefined;
|
|
76
|
+
return served ? Promise.resolve(served) : this.source.getBlocks(query);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public getBlockData(query: BlockQuery): Promise<BlockData | undefined> {
|
|
80
|
+
if (this.active && 'number' in query) {
|
|
81
|
+
const block = this.active.byNumber.get(query.number);
|
|
82
|
+
if (block) {
|
|
83
|
+
return l2BlockToBlockData(block);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return this.source.getBlockData(query);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Serves a block range from the cache only when it is fully covered by contiguous cached blocks. */
|
|
90
|
+
private tryServeBlocksFromCache(query: BlocksQuery): L2Block[] | undefined {
|
|
91
|
+
// Only the by-range form is cacheable, and only for the full (not checkpointed-only) chain: the cache may hold
|
|
92
|
+
// uncheckpointed blocks that an onlyCheckpointed query must not receive.
|
|
93
|
+
if (!this.active || !('from' in query) || query.onlyCheckpointed) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const from = query.from;
|
|
97
|
+
const to = from + query.limit - 1;
|
|
98
|
+
const blocks: L2Block[] = [];
|
|
99
|
+
for (let n = from; n <= to; n++) {
|
|
100
|
+
const block = this.active.byNumber.get(n);
|
|
101
|
+
if (!block) {
|
|
102
|
+
// A gap inside the requested range: bail out and let the source serve the whole range.
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
blocks.push(block);
|
|
106
|
+
}
|
|
107
|
+
return blocks;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Event-driven wrapper around {@link L2BlockStream}. Subscribes to the source's aggregate `l2BlockSourceUpdated`
|
|
113
|
+
* event (when the source exposes one) to trigger an immediate reconciliation, while keeping the periodic poll as
|
|
114
|
+
* the correctness fallback. Subsystems keep consuming the same {@link L2BlockStreamEvent}s; the archiver aggregate
|
|
115
|
+
* event is handled entirely here.
|
|
116
|
+
*
|
|
117
|
+
* The event is passed through to the sync pass via {@link RunningPromise.trigger}. If, at the time the pass runs,
|
|
118
|
+
* the stream's local tips match the event's `fromTips` (it is caught up to where the event began), the event's
|
|
119
|
+
* hydrated blocks are served back through a hot-block cache and the event's `toTips` is reported as the source tips
|
|
120
|
+
* — so the triggered sync re-reads neither block bodies nor tips from the archiver. Otherwise the pass delegates
|
|
121
|
+
* entirely to the source, and the periodic poll guarantees eventual catch-up.
|
|
122
|
+
*/
|
|
123
|
+
export class EventDrivenL2BlockStream {
|
|
124
|
+
private readonly adapter: HotBlockSourceAdapter;
|
|
125
|
+
private readonly blockStream: L2BlockStream;
|
|
126
|
+
private readonly runningPromise: RunningPromise<L2BlockSourceUpdatedEvent>;
|
|
127
|
+
private readonly emitter: ArchiverEmitter | undefined;
|
|
128
|
+
private started = false;
|
|
129
|
+
|
|
130
|
+
private readonly onSourceUpdated = (event: L2BlockSourceUpdatedEvent) => {
|
|
131
|
+
// Fire-and-forget: trigger coalesces with any in-flight or periodic pass (see RunningPromise.trigger), so a
|
|
132
|
+
// burst of events does not run passes concurrently. Errors are swallowed by the inner stream's own handler.
|
|
133
|
+
void this.runningPromise
|
|
134
|
+
.trigger(event)
|
|
135
|
+
.catch(err => this.log.error(`Error in event-triggered block stream sync`, err));
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
constructor(
|
|
139
|
+
source: L2BlockSource | L2BlockSourceEventEmitter,
|
|
140
|
+
private readonly localData: L2BlockStreamLocalDataProvider,
|
|
141
|
+
handler: L2BlockStreamEventHandler,
|
|
142
|
+
private readonly log = createLogger('types:event_driven_block_stream'),
|
|
143
|
+
opts: L2BlockStreamOptions = {},
|
|
144
|
+
) {
|
|
145
|
+
this.adapter = new HotBlockSourceAdapter(source, log);
|
|
146
|
+
// The inner stream's own RunningPromise is never started; this wrapper owns the polling loop and drives the
|
|
147
|
+
// stream through `sync()` (which runs `work()` directly when the inner loop is stopped).
|
|
148
|
+
this.blockStream = new L2BlockStream(this.adapter, localData, handler, log, opts);
|
|
149
|
+
this.runningPromise = new RunningPromise<L2BlockSourceUpdatedEvent>(
|
|
150
|
+
this.runPass.bind(this),
|
|
151
|
+
log,
|
|
152
|
+
opts.pollIntervalMS ?? 1000,
|
|
153
|
+
);
|
|
154
|
+
this.emitter = getEmitter(source);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public start(): void {
|
|
158
|
+
if (this.started) {
|
|
159
|
+
this.log.warn(`Attempted to start an already-started event-driven block stream`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.started = true;
|
|
163
|
+
this.emitter?.on(L2BlockSourceEvents.L2BlockSourceUpdated, this.onSourceUpdated);
|
|
164
|
+
this.runningPromise.start();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public async stop(): Promise<void> {
|
|
168
|
+
this.started = false;
|
|
169
|
+
this.emitter?.off(L2BlockSourceEvents.L2BlockSourceUpdated, this.onSourceUpdated);
|
|
170
|
+
await this.runningPromise.stop();
|
|
171
|
+
await this.blockStream.stop();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public isRunning(): boolean {
|
|
175
|
+
return this.runningPromise.isRunning();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Runs a synchronization pass now, bypassing the poll interval, and resolves once that pass completes. Concurrent
|
|
180
|
+
* callers and periodic ticks coalesce onto a single pass; a caller that coalesces onto an already in-flight pass
|
|
181
|
+
* can resolve against a pass that began just before it, so this guarantees freshness only up to that coalescing
|
|
182
|
+
* window. The periodic poll and per-pass reorg handling make the gap a latency effect, never a correctness one.
|
|
183
|
+
*/
|
|
184
|
+
public sync(): Promise<void> {
|
|
185
|
+
return this.runningPromise.trigger();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Runs a single pass over the underlying block stream. When triggered by an aggregate event and the stream is
|
|
190
|
+
* caught up to the event's pre-pass tips, the event's blocks and tips serve the pass directly; the fast path is
|
|
191
|
+
* always disarmed afterwards so a subsequent poll-driven pass reads from the source.
|
|
192
|
+
*/
|
|
193
|
+
private async runPass(event?: L2BlockSourceUpdatedEvent): Promise<void> {
|
|
194
|
+
if (event) {
|
|
195
|
+
const localTips = await this.localData.getL2Tips();
|
|
196
|
+
if (localTipsMatch(localTips, event.fromTips)) {
|
|
197
|
+
this.adapter.activate(event.blocksAdded, event.toTips);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
await this.blockStream.sync();
|
|
203
|
+
} finally {
|
|
204
|
+
this.adapter.deactivate();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BlockNumber } from '@aztec/foundation/branded-types';
|
|
2
2
|
|
|
3
3
|
import type { L2Block } from '../l2_block.js';
|
|
4
|
-
import type { CheckpointId, L2BlockId, L2TipId, LocalL2Tips } from '../l2_block_source.js';
|
|
4
|
+
import type { CheckpointId, L2BlockId, L2TipId, L2Tips, LocalL2Tips } from '../l2_block_source.js';
|
|
5
5
|
|
|
6
6
|
/** Provides the current chain tips. Implemented by world-state, l2-tips-store, and AztecNode. */
|
|
7
7
|
export interface L2TipsProvider {
|
|
@@ -35,6 +35,40 @@ export interface L2BlockStreamLocalDataProvider {
|
|
|
35
35
|
getL2BlockHash(number: number): Promise<string | undefined>;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Returns whether a local block id differs from a source block id. Compares block number and, when the local hash is
|
|
40
|
+
* known, block hash. The hash comparison is skipped when the local hash is undefined: world-state legitimately
|
|
41
|
+
* reports `undefined` hashes for tips ahead of its synced range, and comparing against an undefined hash would treat
|
|
42
|
+
* such a tip as different on every poll. An `undefined` local block (no local tip yet) always counts as differing.
|
|
43
|
+
*/
|
|
44
|
+
export function localBlockIdDiffers(localBlock: LocalL2BlockId | undefined, sourceBlock: L2BlockId): boolean {
|
|
45
|
+
if (localBlock === undefined) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (sourceBlock.number !== localBlock.number) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (localBlock.hash === undefined) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return sourceBlock.hash !== localBlock.hash;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns whether the local chain tips agree with the given source tips on every tier the local provider exposes.
|
|
59
|
+
* Each tier is compared at the block level via {@link localBlockIdDiffers} (so an unresolved local hash matches on
|
|
60
|
+
* number alone); checkpoint ids are not compared, mirroring the stream's own tier reconciliation. The optional
|
|
61
|
+
* `checkpointed` tier is only compared when present (it is absent when the stream ignores checkpoints).
|
|
62
|
+
*/
|
|
63
|
+
export function localTipsMatch(local: LocalChainTips, source: L2Tips): boolean {
|
|
64
|
+
return (
|
|
65
|
+
!localBlockIdDiffers(local.proposed, source.proposed) &&
|
|
66
|
+
(local.checkpointed === undefined || !localBlockIdDiffers(local.checkpointed.block, source.checkpointed.block)) &&
|
|
67
|
+
!localBlockIdDiffers(local.proven.block, source.proven.block) &&
|
|
68
|
+
!localBlockIdDiffers(local.finalized.block, source.finalized.block)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
38
72
|
/** Interface to a handler of events emitted. */
|
|
39
73
|
export interface L2BlockStreamEventHandler {
|
|
40
74
|
handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void>;
|
|
@@ -4,22 +4,34 @@ import { createLogger } from '@aztec/foundation/log';
|
|
|
4
4
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
5
5
|
|
|
6
6
|
import type { L2Block } from '../l2_block.js';
|
|
7
|
+
import { type L2BlockId, type L2BlockSource, type LocalL2Tips, makeL2BlockId } from '../l2_block_source.js';
|
|
7
8
|
import {
|
|
8
|
-
type
|
|
9
|
-
type
|
|
10
|
-
type
|
|
11
|
-
|
|
12
|
-
makeL2BlockId,
|
|
13
|
-
} from '../l2_block_source.js';
|
|
14
|
-
import type {
|
|
15
|
-
L2BlockStreamEvent,
|
|
16
|
-
L2BlockStreamEventHandler,
|
|
17
|
-
L2BlockStreamLocalDataProvider,
|
|
18
|
-
LocalL2BlockId,
|
|
9
|
+
type L2BlockStreamEvent,
|
|
10
|
+
type L2BlockStreamEventHandler,
|
|
11
|
+
type L2BlockStreamLocalDataProvider,
|
|
12
|
+
localBlockIdDiffers,
|
|
19
13
|
} from './interfaces.js';
|
|
20
14
|
|
|
21
15
|
/** Subset of the block source the stream depends on. Checkpoint payloads are no longer fetched here. */
|
|
22
|
-
type L2BlockStreamSource = Pick<L2BlockSource, 'getBlocks' | 'getBlockData' | 'getL2Tips'>;
|
|
16
|
+
export type L2BlockStreamSource = Pick<L2BlockSource, 'getBlocks' | 'getBlockData' | 'getL2Tips'>;
|
|
17
|
+
|
|
18
|
+
/** Options accepted by {@link L2BlockStream} and {@link EventDrivenL2BlockStream}. */
|
|
19
|
+
export type L2BlockStreamOptions = {
|
|
20
|
+
pollIntervalMS?: number;
|
|
21
|
+
batchSize?: number;
|
|
22
|
+
startingBlock?: number;
|
|
23
|
+
/** Instead of downloading all blocks, only fetch the smallest subset that results in reliable reorg detection. */
|
|
24
|
+
skipFinalized?: boolean;
|
|
25
|
+
/** When true, checkpoint events will not be emitted. Blocks are still fetched but only blocks-added events are emitted. */
|
|
26
|
+
ignoreCheckpoints?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* When true, the block download loop is skipped entirely: `getBlocks` is never called and `blocks-added` is
|
|
29
|
+
* never emitted. Only the tip events (`chain-proposed`/`chain-checkpointed`/`chain-proven`/`chain-finalized`)
|
|
30
|
+
* and `chain-pruned` are emitted, driven by the `getL2Tips` snapshot. For consumers that track tips but never
|
|
31
|
+
* consume block payloads.
|
|
32
|
+
*/
|
|
33
|
+
tipsOnly?: boolean;
|
|
34
|
+
};
|
|
23
35
|
|
|
24
36
|
/** Creates a stream of events for new blocks, chain tips updates, and reorgs, out of polling an archiver or a node. */
|
|
25
37
|
export class L2BlockStream {
|
|
@@ -32,22 +44,7 @@ export class L2BlockStream {
|
|
|
32
44
|
private localData: L2BlockStreamLocalDataProvider,
|
|
33
45
|
private handler: L2BlockStreamEventHandler,
|
|
34
46
|
private readonly log = createLogger('types:block_stream'),
|
|
35
|
-
private opts: {
|
|
36
|
-
pollIntervalMS?: number;
|
|
37
|
-
batchSize?: number;
|
|
38
|
-
startingBlock?: number;
|
|
39
|
-
/** Instead of downloading all blocks, only fetch the smallest subset that results in reliable reorg detection. */
|
|
40
|
-
skipFinalized?: boolean;
|
|
41
|
-
/** When true, checkpoint events will not be emitted. Blocks are still fetched but only blocks-added events are emitted. */
|
|
42
|
-
ignoreCheckpoints?: boolean;
|
|
43
|
-
/**
|
|
44
|
-
* When true, the block download loop is skipped entirely: `getBlocks` is never called and `blocks-added` is
|
|
45
|
-
* never emitted. Only the tip events (`chain-proposed`/`chain-checkpointed`/`chain-proven`/`chain-finalized`)
|
|
46
|
-
* and `chain-pruned` are emitted, driven by the `getL2Tips` snapshot. For consumers that track tips but never
|
|
47
|
-
* consume block payloads.
|
|
48
|
-
*/
|
|
49
|
-
tipsOnly?: boolean;
|
|
50
|
-
} = {},
|
|
47
|
+
private opts: L2BlockStreamOptions = {},
|
|
51
48
|
) {
|
|
52
49
|
if (opts.tipsOnly && (opts.startingBlock !== undefined || opts.batchSize !== undefined || opts.skipFinalized)) {
|
|
53
50
|
throw new Error(
|
|
@@ -181,19 +178,25 @@ export class L2BlockStream {
|
|
|
181
178
|
// End-of-pass reconciliation: chain-proposed fires against the pre-pass baseline (a post-prune re-read would
|
|
182
179
|
// equal the source tip and suppress it), then the tiers highest-to-lowest so the finalized <= proven <=
|
|
183
180
|
// checkpointed <= proposed invariant holds mid-pass.
|
|
184
|
-
if (
|
|
181
|
+
if (localBlockIdDiffers(prePassProposed, sourceTips.proposed)) {
|
|
185
182
|
await this.emitEvent({ type: 'chain-proposed', block: sourceTips.proposed });
|
|
186
183
|
}
|
|
187
184
|
|
|
188
185
|
const reconcileTips = pruned ? await this.localData.getL2Tips() : localTips;
|
|
189
|
-
if (
|
|
186
|
+
if (
|
|
187
|
+
!this.opts.ignoreCheckpoints &&
|
|
188
|
+
localBlockIdDiffers(reconcileTips.checkpointed?.block, sourceTips.checkpointed.block)
|
|
189
|
+
) {
|
|
190
190
|
await this.emitEvent({
|
|
191
191
|
type: 'chain-checkpointed',
|
|
192
192
|
block: sourceTips.checkpointed.block,
|
|
193
193
|
checkpoint: sourceTips.checkpointed.checkpoint,
|
|
194
194
|
});
|
|
195
195
|
}
|
|
196
|
-
if (
|
|
196
|
+
if (
|
|
197
|
+
reconcileTips.proven !== undefined &&
|
|
198
|
+
localBlockIdDiffers(reconcileTips.proven.block, sourceTips.proven.block)
|
|
199
|
+
) {
|
|
197
200
|
await this.emitEvent({
|
|
198
201
|
type: 'chain-proven',
|
|
199
202
|
block: sourceTips.proven.block,
|
|
@@ -202,7 +205,7 @@ export class L2BlockStream {
|
|
|
202
205
|
}
|
|
203
206
|
if (
|
|
204
207
|
reconcileTips.finalized !== undefined &&
|
|
205
|
-
|
|
208
|
+
localBlockIdDiffers(reconcileTips.finalized.block, sourceTips.finalized.block)
|
|
206
209
|
) {
|
|
207
210
|
await this.emitEvent({
|
|
208
211
|
type: 'chain-finalized',
|
|
@@ -286,36 +289,6 @@ export class L2BlockStream {
|
|
|
286
289
|
return true;
|
|
287
290
|
}
|
|
288
291
|
|
|
289
|
-
/**
|
|
290
|
-
* Returns whether the source tip differs from the local one and therefore warrants a tier event. Compares block
|
|
291
|
-
* number and, when both hashes are known, block hash. The hash comparison is skipped when the local hash is
|
|
292
|
-
* undefined or missing: world-state legitimately reports `undefined` hashes for tips ahead of its synced range,
|
|
293
|
-
* and comparing against an undefined hash would re-emit the event on every poll.
|
|
294
|
-
*/
|
|
295
|
-
private tipDiffers(localBlock: LocalL2BlockId | undefined, sourceTip: L2TipId): boolean {
|
|
296
|
-
return this.blockTipDiffers(localBlock, sourceTip.block);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Block-only variant of {@link tipDiffers} for the proposed tip (an {@link L2BlockId}, with no checkpoint). Compares
|
|
301
|
-
* block number and, when the local hash is known, block hash. The hash comparison is skipped when the local hash is
|
|
302
|
-
* undefined: world-state reports `undefined` for its proposed hash, and a strict comparison would re-emit
|
|
303
|
-
* `chain-proposed` on every poll for it. ({@link L2TipsStoreBase} consumers always carry a hash, so the leniency is
|
|
304
|
-
* inert for them.)
|
|
305
|
-
*/
|
|
306
|
-
private blockTipDiffers(localBlock: LocalL2BlockId | undefined, sourceBlock: L2BlockId): boolean {
|
|
307
|
-
if (localBlock === undefined) {
|
|
308
|
-
return true;
|
|
309
|
-
}
|
|
310
|
-
if (sourceBlock.number !== localBlock.number) {
|
|
311
|
-
return true;
|
|
312
|
-
}
|
|
313
|
-
if (localBlock.hash === undefined) {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
return sourceBlock.hash !== localBlock.hash;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
292
|
/**
|
|
320
293
|
* Returns whether the source and local agree on the block hash at a given height.
|
|
321
294
|
* @param blockNumber - The block number to test.
|
|
@@ -7,6 +7,7 @@ import type { AztecAddress } from '../aztec-address/index.js';
|
|
|
7
7
|
import { computeVarArgsHash } from '../hash/hash.js';
|
|
8
8
|
import { computeAddress } from '../keys/index.js';
|
|
9
9
|
import type { ContractInstance } from './interfaces/contract_instance.js';
|
|
10
|
+
import type { PartialAddress } from './partial_address.js';
|
|
10
11
|
|
|
11
12
|
// TODO(@spalladino): Review all generator indices in this file
|
|
12
13
|
|
|
@@ -36,7 +37,7 @@ export async function computePartialAddress(
|
|
|
36
37
|
instance:
|
|
37
38
|
| Pick<ContractInstance, 'originalContractClassId' | 'initializationHash' | 'salt' | 'deployer' | 'immutablesHash'>
|
|
38
39
|
| { originalContractClassId: Fr; saltedInitializationHash: Fr },
|
|
39
|
-
): Promise<
|
|
40
|
+
): Promise<PartialAddress> {
|
|
40
41
|
const saltedInitializationHash =
|
|
41
42
|
'saltedInitializationHash' in instance
|
|
42
43
|
? instance.saltedInitializationHash
|