@midnight-ntwrk/wallet-sdk-capabilities 4.0.0-beta.2 → 4.0.0-beta.4

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 (68) hide show
  1. package/README.md +27 -3
  2. package/dist/chainVersion/chainVersionProbe.d.ts +95 -0
  3. package/dist/chainVersion/chainVersionProbe.js +83 -0
  4. package/dist/chainVersion/index.d.ts +1 -0
  5. package/dist/chainVersion/index.js +13 -0
  6. package/dist/codecs/index.d.ts +1 -0
  7. package/dist/codecs/index.js +13 -0
  8. package/dist/codecs/ledgerParameters.d.ts +81 -0
  9. package/dist/codecs/ledgerParameters.js +63 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.js +3 -0
  12. package/dist/pendingTransactions/pendingTransactions.d.ts +113 -9
  13. package/dist/pendingTransactions/pendingTransactions.js +147 -30
  14. package/dist/pendingTransactions/pendingTransactionsService.d.ts +25 -9
  15. package/dist/pendingTransactions/pendingTransactionsService.js +33 -21
  16. package/dist/proving/index.d.ts +2 -0
  17. package/dist/proving/index.js +2 -0
  18. package/dist/proving/provingService.d.ts +184 -15
  19. package/dist/proving/provingService.js +114 -12
  20. package/dist/proving/v8ProvingService.d.ts +53 -0
  21. package/dist/proving/v8ProvingService.js +71 -0
  22. package/dist/proving/versionedProving.d.ts +42 -0
  23. package/dist/proving/versionedProving.js +103 -0
  24. package/dist/signatures/index.d.ts +2 -0
  25. package/dist/signatures/index.js +14 -0
  26. package/dist/signatures/signing.d.ts +37 -0
  27. package/dist/signatures/signing.js +13 -0
  28. package/dist/signatures/v8Signatures.d.ts +54 -0
  29. package/dist/signatures/v8Signatures.js +62 -0
  30. package/dist/simulation/ForkSimulator.d.ts +114 -0
  31. package/dist/simulation/ForkSimulator.js +209 -0
  32. package/dist/simulation/LedgerTranslation.d.ts +53 -0
  33. package/dist/simulation/LedgerTranslation.js +56 -0
  34. package/dist/simulation/core/VersionTimeline.d.ts +54 -0
  35. package/dist/simulation/core/VersionTimeline.js +56 -0
  36. package/dist/simulation/core/blocks.d.ts +38 -0
  37. package/dist/simulation/core/blocks.js +52 -0
  38. package/dist/simulation/core/index.d.ts +13 -0
  39. package/dist/simulation/core/index.js +25 -0
  40. package/dist/simulation/core/strictness.d.ts +29 -0
  41. package/dist/simulation/core/strictness.js +39 -0
  42. package/dist/simulation/index.d.ts +22 -2
  43. package/dist/simulation/index.js +25 -14
  44. package/dist/simulation/v8/Simulator.d.ts +231 -0
  45. package/dist/simulation/v8/Simulator.js +503 -0
  46. package/dist/simulation/{SimulatorState.d.ts → v8/SimulatorState.d.ts} +31 -44
  47. package/dist/simulation/v8/SimulatorState.js +290 -0
  48. package/dist/simulation/v8/index.d.ts +2 -0
  49. package/dist/simulation/v8/index.js +26 -0
  50. package/dist/simulation/{Simulator.d.ts → v9/Simulator.d.ts} +57 -6
  51. package/dist/simulation/{Simulator.js → v9/Simulator.js} +68 -18
  52. package/dist/simulation/v9/SimulatorState.d.ts +336 -0
  53. package/dist/simulation/{SimulatorState.js → v9/SimulatorState.js} +34 -67
  54. package/dist/simulation/v9/index.d.ts +2 -0
  55. package/dist/simulation/v9/index.js +26 -0
  56. package/dist/submission/submissionService.d.ts +2 -1
  57. package/dist/submission/submissionService.js +1 -1
  58. package/dist/validation/blockData.d.ts +42 -2
  59. package/dist/validation/blockData.js +55 -8
  60. package/dist/validation/index.d.ts +2 -0
  61. package/dist/validation/index.js +2 -0
  62. package/dist/validation/v8ValidationService.d.ts +29 -0
  63. package/dist/validation/v8ValidationService.js +48 -0
  64. package/dist/validation/validationService.d.ts +132 -17
  65. package/dist/validation/validationService.js +88 -42
  66. package/dist/validation/versionedValidation.d.ts +46 -0
  67. package/dist/validation/versionedValidation.js +55 -0
  68. package/package.json +28 -14
@@ -0,0 +1,103 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ /**
14
+ * Proving for an SDK that spans a protocol boundary: one backend per ledger version, chosen by the version a
15
+ * transaction was built for.
16
+ *
17
+ * @remarks
18
+ * The routing already existed and the backends already existed; what this module supplies is the only thing neither
19
+ * could: the registration that says which range of protocol versions each backend answers for, taken from the same
20
+ * fork schedule the wallets are built with. Written the same way validation's `versionedValidation.ts` is, because it
21
+ * is the same problem.
22
+ */
23
+ import * as ledgerV8 from '@midnight-ntwrk/ledger-v8';
24
+ import * as ledgerV9 from '@midnightntwrk/ledger-v9';
25
+ import { ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
26
+ import { Effect, Either, Option } from 'effect';
27
+ import { makeV8ServerProvingServiceEffect, makeV8WasmProvingServiceEffect, } from './v8ProvingService.js';
28
+ import { makeV9ServerProvingServiceEffect, makeVersionedProvingServiceEffect, makeV9WasmProvingServiceEffect, ProvingEpochMismatchError, resolveProvingBackends, wrapVersionedEffectService, } from './provingService.js';
29
+ /**
30
+ * Narrows a backend written against one ledger version to the union the registry is keyed by.
31
+ *
32
+ * @remarks
33
+ * The router has already chosen this backend by the version stamped on the transaction, so the check is a restatement
34
+ * of that choice rather than a second one — but it is where a transaction that does not belong is refused rather than
35
+ * handed to a ledger that cannot read it. What comes back from a wasm-bindgen boundary handed a foreign object is not
36
+ * an error anyone can act on; this is.
37
+ * @param service The backend, written against one ledger version.
38
+ * @param isOwn Whether a transaction is that ledger version's.
39
+ * @param epoch The range of protocol versions this backend answers for.
40
+ * @returns The same backend, in terms of the union.
41
+ */
42
+ const onlyFrom = (service, isOwn, epoch) => ({
43
+ prove: (transaction) => isOwn(transaction)
44
+ ? service.prove(transaction)
45
+ : Effect.fail(new ProvingEpochMismatchError({
46
+ message: `The proving backend registered for protocol versions [${epoch[0]}, ${epoch[1]}) was handed a transaction built by the other ledger version.`,
47
+ epoch,
48
+ })),
49
+ });
50
+ const isV8Transaction = (transaction) => transaction instanceof ledgerV8.Transaction;
51
+ const isV9Transaction = (transaction) => transaction instanceof ledgerV9.Transaction;
52
+ /** The in-process backend's configuration, with a key material override only when one was named. */
53
+ const wasmConfigurationOf = (backend) => backend.keyMaterialProvider === undefined ? {} : { keyMaterialProvider: backend.keyMaterialProvider };
54
+ /** Builds the backend a description names, driven by ledger-v8, for the epoch below `forks.v9`. */
55
+ const makeV8Backend = (backend, epoch) => onlyFrom(backend.kind === 'server'
56
+ ? makeV8ServerProvingServiceEffect({ provingServerUrl: backend.url })
57
+ : makeV8WasmProvingServiceEffect(wasmConfigurationOf(backend)), isV8Transaction, epoch);
58
+ /** Builds the backend a description names, driven by ledger-v9, for the epoch from `forks.v9`. */
59
+ const makeV9Backend = (backend, epoch) => onlyFrom(backend.kind === 'server'
60
+ ? makeV9ServerProvingServiceEffect({ provingServerUrl: backend.url })
61
+ : makeV9WasmProvingServiceEffect(wasmConfigurationOf(backend)), isV9Transaction, epoch);
62
+ /**
63
+ * The range of protocol versions each ledger version reads on a chain, from where the chain says the hand-over is.
64
+ *
65
+ * @remarks
66
+ * A chain whose boundary is at or below the minimum supported version has no history ledger-v8 authored, so there is no
67
+ * ledger-v8 epoch on it and a ledger-v8 backend has nothing to serve.
68
+ */
69
+ const epochsOf = (forks) => ({
70
+ v8: forks.v9 > ProtocolVersion.MinSupportedVersion
71
+ ? Option.some(ProtocolVersion.epochOf(ProtocolVersion.MinSupportedVersion, forks.v9))
72
+ : Option.none(),
73
+ v9: ProtocolVersion.epochOf(forks.v9, forks.v9),
74
+ });
75
+ /**
76
+ * Registers a proving backend either side of the protocol boundary.
77
+ *
78
+ * @remarks
79
+ * The configuration names a backend per ledger version and the fork schedule says where each ledger version begins; the
80
+ * range a backend serves is the meeting of the two, computed here and nowhere else, so the wallets and their provers
81
+ * cannot place the boundary differently.
82
+ * @param configuration The proving configuration.
83
+ * @param forks Where each ledger version begins on the chain.
84
+ * @returns The backends and the version ranges they serve, or the reason the configuration names none.
85
+ */
86
+ export const makeDefaultProvingServices = (configuration, forks) => resolveProvingBackends(configuration).pipe(Either.map((backends) => {
87
+ const epochs = epochsOf(forks);
88
+ const v8Entry = Option.all([Option.fromNullable(backends.v8), epochs.v8]).pipe(Option.map(([backend, range]) => ({ range, value: makeV8Backend(backend, range) })));
89
+ return {
90
+ entries: [...Option.toArray(v8Entry), { range: epochs.v9, value: makeV9Backend(backends.v9, epochs.v9) }],
91
+ };
92
+ }));
93
+ /**
94
+ * Builds the version-routed proving service an SDK spanning a protocol boundary proves with.
95
+ *
96
+ * @param configuration The proving configuration.
97
+ * @param forks Where each ledger version begins on the chain.
98
+ * @returns A proving service that routes on the version a transaction was built for, or the reason the configuration
99
+ * names no backend.
100
+ */
101
+ export const makeDefaultVersionedProvingServiceEffect = (configuration, forks) => makeDefaultProvingServices(configuration, forks).pipe(Either.map(makeVersionedProvingServiceEffect));
102
+ /** The promise-facing surface of {@link makeDefaultVersionedProvingServiceEffect}, for the facade to expose. */
103
+ export const makeDefaultVersionedProvingService = (configuration, forks) => makeDefaultVersionedProvingServiceEffect(configuration, forks).pipe(Either.map(wrapVersionedEffectService));
@@ -0,0 +1,2 @@
1
+ export * as Signing from './signing.js';
2
+ export * from './v8Signatures.js';
@@ -0,0 +1,14 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ export * as Signing from './signing.js';
14
+ export * from './v8Signatures.js';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * How the SDK writes a signature, whichever ledger version is underneath.
3
+ *
4
+ * @remarks
5
+ * The one scalar that genuinely changed shape at the protocol boundary. Ledger-v8 has a single signature scheme and
6
+ * writes a signature as bare hexadecimal; ledger-v9 has more than one and names the scheme alongside the bytes.
7
+ * Everything else an application reads — token types, addresses, nullifiers, transaction identifiers — is
8
+ * byte-identical across the two.
9
+ *
10
+ * The SDK speaks the ledger-v9 shape everywhere and lowers it for the V1 variant, rather than the reverse. Lifting is
11
+ * total — ledger-v8 has exactly one scheme, so naming it is never a guess — while lowering is partial, and a scheme
12
+ * ledger-v8 has never heard of is refused rather than handed over as bytes it would misread. Speaking the ledger-v8
13
+ * shape would have made the common case lossy instead.
14
+ *
15
+ * Kept beside that lifting and lowering rather than among the abstractions a variant implements, because this is the
16
+ * vocabulary those two functions are stated in and nothing else in the SDK is defined against it. It names no ledger
17
+ * version itself, so an application can annotate a signer without importing one, and nothing on the way to it loads
18
+ * either ledger's WebAssembly. These are structurally ledger-v9's own types: a signer already written against them
19
+ * compiles unchanged.
20
+ */
21
+ /** The signature schemes the chain knows. Only `schnorr` exists before the protocol boundary. */
22
+ export type SignatureKind = 'schnorr' | 'ecdsa';
23
+ /** A signature: the scheme that produced it, and its bytes as hexadecimal. */
24
+ export type Signature = Readonly<{
25
+ tag: SignatureKind;
26
+ value: string;
27
+ }>;
28
+ /** The public half of a signing key, named with the scheme it belongs to. */
29
+ export type SignatureVerifyingKey = Readonly<{
30
+ tag: SignatureKind;
31
+ value: string;
32
+ }>;
33
+ /** A signing key, named with the scheme it belongs to. */
34
+ export type SigningKey = Readonly<{
35
+ tag: SignatureKind;
36
+ value: string;
37
+ }>;
@@ -0,0 +1,13 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ export {};
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Signatures and verifying keys across a protocol boundary.
3
+ *
4
+ * @remarks
5
+ * The one scalar that genuinely changed shape at the fork. Ledger-v8 has a single signature scheme and writes a
6
+ * signature as bare hex; ledger-v9 names the scheme alongside the bytes, because it has more than one. So the SDK
7
+ * speaks the ledger-v9 shape everywhere and lowers it for the V1 variant, which is a total operation in one direction
8
+ * and a partial one in the other: a signature of a scheme ledger-v8 has never heard of cannot be lowered at all, and
9
+ * says so rather than being handed over as bytes it would misread.
10
+ */
11
+ import type * as ledgerV8 from '@midnight-ntwrk/ledger-v8';
12
+ import { Either } from 'effect';
13
+ import type * as Signing from './signing.js';
14
+ declare const UnsupportedSignatureKindError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
15
+ readonly _tag: "@midnight-ntwrk/wallet-sdk-capabilities/signatures/v8Signatures/UnsupportedSignatureKindError";
16
+ } & Readonly<A>;
17
+ /** Raised when a signature or verifying key names a scheme ledger-v8 does not have. */
18
+ export declare class UnsupportedSignatureKindError extends UnsupportedSignatureKindError_base<{
19
+ readonly message: string;
20
+ /** The scheme that was named. */
21
+ readonly kind: Signing.SignatureKind;
22
+ }> {
23
+ }
24
+ /**
25
+ * Lowers a signature to ledger-v8's shape.
26
+ *
27
+ * @param signature The signature, as ledger-v9 writes it.
28
+ * @returns The bare hex ledger-v8 reads, or the reason it cannot be expressed there.
29
+ */
30
+ export declare const lowerSignature: (signature: Signing.Signature) => Either.Either<ledgerV8.Signature, UnsupportedSignatureKindError>;
31
+ /**
32
+ * Lowers a signature verifying key to ledger-v8's shape.
33
+ *
34
+ * @param key The verifying key, as ledger-v9 writes it.
35
+ * @returns The bare hex ledger-v8 reads, or the reason it cannot be expressed there.
36
+ */
37
+ export declare const lowerSignatureVerifyingKey: (key: Signing.SignatureVerifyingKey) => Either.Either<ledgerV8.SignatureVerifyingKey, UnsupportedSignatureKindError>;
38
+ /**
39
+ * Lifts a ledger-v8 signature into ledger-v9's shape.
40
+ *
41
+ * @remarks
42
+ * Total, unlike lowering: ledger-v8 has exactly one scheme, so naming it is never a guess.
43
+ * @param signature The signature, as ledger-v8 writes it.
44
+ * @returns The same signature, with the scheme it necessarily used named.
45
+ */
46
+ export declare const liftSignature: (signature: ledgerV8.Signature) => Signing.Signature;
47
+ /**
48
+ * Lifts a ledger-v8 verifying key into ledger-v9's shape.
49
+ *
50
+ * @param key The verifying key, as ledger-v8 writes it.
51
+ * @returns The same key, with the scheme it necessarily used named.
52
+ */
53
+ export declare const liftSignatureVerifyingKey: (key: ledgerV8.SignatureVerifyingKey) => Signing.SignatureVerifyingKey;
54
+ export {};
@@ -0,0 +1,62 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { Data, Either } from 'effect';
14
+ /** The signature scheme ledger-v8 has, and the only one a lowered value can name. */
15
+ const V8_SIGNATURE_KIND = 'schnorr';
16
+ /** Raised when a signature or verifying key names a scheme ledger-v8 does not have. */
17
+ export class UnsupportedSignatureKindError extends Data.TaggedError('@midnight-ntwrk/wallet-sdk-capabilities/signatures/v8Signatures/UnsupportedSignatureKindError') {
18
+ }
19
+ const lower = (value, what) => value.tag === V8_SIGNATURE_KIND
20
+ ? Either.right(value.value)
21
+ : Either.left(new UnsupportedSignatureKindError({
22
+ message: `This ${what} names the ${value.tag} signature scheme, which the ledger version before the protocol ` +
23
+ `boundary does not have: it has ${V8_SIGNATURE_KIND} and nothing else. Sign with ` +
24
+ `${V8_SIGNATURE_KIND} while the chain is still on ledger-v8.`,
25
+ kind: value.tag,
26
+ }));
27
+ /**
28
+ * Lowers a signature to ledger-v8's shape.
29
+ *
30
+ * @param signature The signature, as ledger-v9 writes it.
31
+ * @returns The bare hex ledger-v8 reads, or the reason it cannot be expressed there.
32
+ */
33
+ export const lowerSignature = (signature) => lower(signature, 'signature');
34
+ /**
35
+ * Lowers a signature verifying key to ledger-v8's shape.
36
+ *
37
+ * @param key The verifying key, as ledger-v9 writes it.
38
+ * @returns The bare hex ledger-v8 reads, or the reason it cannot be expressed there.
39
+ */
40
+ export const lowerSignatureVerifyingKey = (key) => lower(key, 'verifying key');
41
+ /**
42
+ * Lifts a ledger-v8 signature into ledger-v9's shape.
43
+ *
44
+ * @remarks
45
+ * Total, unlike lowering: ledger-v8 has exactly one scheme, so naming it is never a guess.
46
+ * @param signature The signature, as ledger-v8 writes it.
47
+ * @returns The same signature, with the scheme it necessarily used named.
48
+ */
49
+ export const liftSignature = (signature) => ({
50
+ tag: V8_SIGNATURE_KIND,
51
+ value: signature,
52
+ });
53
+ /**
54
+ * Lifts a ledger-v8 verifying key into ledger-v9's shape.
55
+ *
56
+ * @param key The verifying key, as ledger-v8 writes it.
57
+ * @returns The same key, with the scheme it necessarily used named.
58
+ */
59
+ export const liftSignatureVerifyingKey = (key) => ({
60
+ tag: V8_SIGNATURE_KIND,
61
+ value: key,
62
+ });
@@ -0,0 +1,114 @@
1
+ /**
2
+ * A simulated chain that crosses a hard fork.
3
+ *
4
+ * Ledger-v8 the chain runs on ledger-v8, ledger-v9 on ledger-v9, with real ledger bytes on both sides — enough for a
5
+ * wallet under test to observe the version change, migrate, and go on transacting.
6
+ */
7
+ import { Effect, Option, Scope, type Array as Arr } from 'effect';
8
+ import { NetworkId, ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
9
+ import { type LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
10
+ import { LedgerTranslationError, type LedgerStateTranslator } from './LedgerTranslation.js';
11
+ import { Simulator } from './v9/Simulator.js';
12
+ import { type BlockProducer } from './v9/SimulatorState.js';
13
+ import * as V8 from './v8/index.js';
14
+ /** {@link ForkSimulator} initialization configuration. */
15
+ export type ForkSimulatorConfig = Readonly<{
16
+ /**
17
+ * Height at which the chain forks.
18
+ *
19
+ * The ledger-v8 chain's block at this height is a ledger-v8 block stamped with {@link forkVersion} — the signal a
20
+ * wallet's V1 variant migrates on, which it observes but does not apply. The ledger-v9 chain then begins at the same
21
+ * height, re-delivering it with ledger-v9 content, mirroring a wallet re-fetching the boundary with its new codec.
22
+ */
23
+ forkBlock: bigint;
24
+ /**
25
+ * Protocol version the fork activates.
26
+ *
27
+ * Required, and never defaulted: the protocol version of the real fork is not final, so every caller states which
28
+ * version it means.
29
+ */
30
+ forkVersion: ProtocolVersion.ProtocolVersion;
31
+ /**
32
+ * How the ledger-v8 chain's ledger state becomes the ledger-v9 chain's starting point.
33
+ *
34
+ * Receives the final ledger-v8 state, serialized, and returns the ledger-v9 one. See {@link LedgerStateTranslator} for
35
+ * why the seam is stated in bytes rather than state objects.
36
+ */
37
+ translator: LedgerStateTranslator;
38
+ /** Protocol version the ledger-v8 chain runs on. Defaults to {@link ProtocolVersion.MinSupportedVersion}. */
39
+ v8Version?: ProtocolVersion.ProtocolVersion;
40
+ /** Network identifier, shared by both chains. Defaults to Undeployed. */
41
+ networkId?: NetworkId.NetworkId;
42
+ /** Pre-funded accounts on the ledger-v8 chain. */
43
+ v8GenesisMints?: Arr.NonEmptyArray<V8.GenesisMint>;
44
+ /** Block producer for the ledger-v8 chain. */
45
+ v8BlockProducer?: V8.BlockProducer;
46
+ /** Block producer for the ledger-v9 chain. */
47
+ v9BlockProducer?: BlockProducer;
48
+ }>;
49
+ /**
50
+ * A simulated chain that crosses a hard fork, built from the two simulator twins.
51
+ *
52
+ * The ledger-v8 chain is available immediately and is driven like any other simulator. Once it reaches
53
+ * {@link ForkSimulatorConfig.forkBlock} the handover runs and the ledger-v9 chain appears; both remain alive for the
54
+ * simulator's scope, so assertions can read either side of the boundary.
55
+ *
56
+ * Nothing stops the ledger-v8 chain producing blocks past the boundary, which a real chain could not do — drive it only
57
+ * up to the fork.
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const fork = yield* ForkSimulator.init({
62
+ * forkBlock: 3n,
63
+ * forkVersion: ProtocolVersion.ProtocolVersion(7n),
64
+ * v8GenesisMints: [{ type: 'shielded', tokenType, amount: 1_000n, recipient: v8Keys }],
65
+ * translator: translatorFromAsync(translateLedgerState),
66
+ * });
67
+ *
68
+ * yield* fork.v8.submitTransaction(v8Transfer);
69
+ * const v9 = yield* fork.advanceToFork();
70
+ * yield* v9.submitTransaction(v9Transfer);
71
+ * ```;
72
+ */
73
+ export declare class ForkSimulator {
74
+ #private;
75
+ /**
76
+ * Initialize a forking chain. The ledger-v8 chain starts immediately with the fork already scheduled; the ledger-v9
77
+ * chain is constructed when the ledger-v8 chain reaches the fork block.
78
+ *
79
+ * @param config - Configuration options
80
+ * @returns Effect that produces a ForkSimulator
81
+ */
82
+ static init(config: ForkSimulatorConfig): Effect.Effect<ForkSimulator, never, Scope.Scope>;
83
+ /** The ledger-v8 chain. */
84
+ readonly v8: V8.Simulator;
85
+ /** Height at which the chain forks. */
86
+ readonly forkBlock: bigint;
87
+ /** Protocol version the fork activates. */
88
+ readonly forkVersion: ProtocolVersion.ProtocolVersion;
89
+ private constructor();
90
+ /**
91
+ * The ledger-v9 chain, if the fork has happened.
92
+ *
93
+ * @returns `Option.some` once the handover has completed, `Option.none` before
94
+ */
95
+ v9(): Effect.Effect<Option.Option<Simulator>>;
96
+ /**
97
+ * Wait until the fork has happened, however the ledger-v8 chain is driven to it.
98
+ *
99
+ * Fails with the handover's own failure if it could not produce a ledger-v9 chain, so a broken handover surfaces here
100
+ * rather than leaving waiters blocked forever.
101
+ *
102
+ * @returns The ledger-v9 chain
103
+ */
104
+ awaitV9(): Effect.Effect<Simulator, LedgerTranslationError>;
105
+ /**
106
+ * Drive the ledger-v8 chain to the fork block with empty blocks and wait for the handover.
107
+ *
108
+ * Blocks the chain has already produced count towards the boundary, so this composes with transaction-driven
109
+ * production: submit what the test needs, then advance the rest of the way.
110
+ *
111
+ * @returns The ledger-v9 chain
112
+ */
113
+ advanceToFork(): Effect.Effect<Simulator, LedgerOps.LedgerError | LedgerTranslationError>;
114
+ }
@@ -0,0 +1,209 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ /**
14
+ * A simulated chain that crosses a hard fork.
15
+ *
16
+ * Ledger-v8 the chain runs on ledger-v8, ledger-v9 on ledger-v9, with real ledger bytes on both sides — enough for a
17
+ * wallet under test to observe the version change, migrate, and go on transacting.
18
+ */
19
+ import { Deferred, Effect, Option, pipe, Scope, Stream, SubscriptionRef } from 'effect';
20
+ import { LedgerState } from '@midnightntwrk/ledger-v9';
21
+ import { NetworkId, ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
22
+ import { LedgerTranslationError } from './LedgerTranslation.js';
23
+ import { Simulator } from './v9/Simulator.js';
24
+ import { blankState, updateLedger } from './v9/SimulatorState.js';
25
+ import * as V8 from './v8/index.js';
26
+ // =============================================================================
27
+ // Fork Simulator
28
+ // =============================================================================
29
+ /**
30
+ * A simulated chain that crosses a hard fork, built from the two simulator twins.
31
+ *
32
+ * The ledger-v8 chain is available immediately and is driven like any other simulator. Once it reaches
33
+ * {@link ForkSimulatorConfig.forkBlock} the handover runs and the ledger-v9 chain appears; both remain alive for the
34
+ * simulator's scope, so assertions can read either side of the boundary.
35
+ *
36
+ * Nothing stops the ledger-v8 chain producing blocks past the boundary, which a real chain could not do — drive it only
37
+ * up to the fork.
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * const fork = yield* ForkSimulator.init({
42
+ * forkBlock: 3n,
43
+ * forkVersion: ProtocolVersion.ProtocolVersion(7n),
44
+ * v8GenesisMints: [{ type: 'shielded', tokenType, amount: 1_000n, recipient: v8Keys }],
45
+ * translator: translatorFromAsync(translateLedgerState),
46
+ * });
47
+ *
48
+ * yield* fork.v8.submitTransaction(v8Transfer);
49
+ * const v9 = yield* fork.advanceToFork();
50
+ * yield* v9.submitTransaction(v9Transfer);
51
+ * ```;
52
+ */
53
+ export class ForkSimulator {
54
+ // ===========================================================================
55
+ // Static Methods
56
+ // ===========================================================================
57
+ /**
58
+ * Initialize a forking chain. The ledger-v8 chain starts immediately with the fork already scheduled; the ledger-v9
59
+ * chain is constructed when the ledger-v8 chain reaches the fork block.
60
+ *
61
+ * @param config - Configuration options
62
+ * @returns Effect that produces a ForkSimulator
63
+ */
64
+ static init(config) {
65
+ return Effect.gen(function* () {
66
+ const networkId = config.networkId ?? NetworkId.NetworkId.Undeployed;
67
+ const v8Version = config.v8Version ?? ProtocolVersion.MinSupportedVersion;
68
+ const v8 = yield* V8.Simulator.init({
69
+ networkId,
70
+ protocolVersion: v8Version,
71
+ ...(config.v8GenesisMints !== undefined ? { genesisMints: config.v8GenesisMints } : {}),
72
+ ...(config.v8BlockProducer !== undefined ? { blockProducer: config.v8BlockProducer } : {}),
73
+ });
74
+ // Scheduled up front, so the boundary block carries the fork version however the chain is driven to it.
75
+ yield* v8.scheduleFork(config.forkBlock, config.forkVersion);
76
+ const v9Ref = yield* SubscriptionRef.make(Option.none());
77
+ const v9Ready = yield* Deferred.make();
78
+ const forkSimulator = new ForkSimulator(config, networkId, v8, v9Ref, v9Ready);
79
+ // The handover must outlive the watcher fiber, so it is built in the simulator's own scope.
80
+ const scope = yield* Effect.scope;
81
+ const runHandover = Effect.gen(function* () {
82
+ const atFork = yield* forkSimulator.#awaitForkBlock();
83
+ const v9 = yield* Scope.extend(
84
+ // Suspended so that a handover built from a throwing caller-supplied callback fails this effect rather than
85
+ // escaping as a synchronous exception.
86
+ Effect.suspend(() => forkSimulator.#handover(atFork)), scope);
87
+ // The reference is set first: anything woken by the deferred must already see the ledger-v9 chain.
88
+ yield* SubscriptionRef.set(v9Ref, Option.some(v9));
89
+ return v9;
90
+ });
91
+ // The handover runs detached, so its outcome has to be handed to whoever is waiting for the fork — including when
92
+ // it fails or dies. `intoDeferred` transfers the whole exit; completing the deferred by hand on the success path
93
+ // only would leave every waiter blocked forever on a broken handover, which reads as a hang rather than an error.
94
+ yield* Effect.forkScoped(Effect.intoDeferred(runHandover, v9Ready));
95
+ return forkSimulator;
96
+ });
97
+ }
98
+ // ===========================================================================
99
+ // Instance Properties
100
+ // ===========================================================================
101
+ #config;
102
+ #networkId;
103
+ #v9Ref;
104
+ #v9Ready;
105
+ /** The ledger-v8 chain. */
106
+ v8;
107
+ /** Height at which the chain forks. */
108
+ forkBlock;
109
+ /** Protocol version the fork activates. */
110
+ forkVersion;
111
+ constructor(config, networkId, v8, v9Ref, v9Ready) {
112
+ this.#config = config;
113
+ this.#networkId = networkId;
114
+ this.#v9Ref = v9Ref;
115
+ this.#v9Ready = v9Ready;
116
+ this.v8 = v8;
117
+ this.forkBlock = config.forkBlock;
118
+ this.forkVersion = config.forkVersion;
119
+ }
120
+ // ===========================================================================
121
+ // Instance Methods
122
+ // ===========================================================================
123
+ /**
124
+ * The ledger-v9 chain, if the fork has happened.
125
+ *
126
+ * @returns `Option.some` once the handover has completed, `Option.none` before
127
+ */
128
+ v9() {
129
+ return SubscriptionRef.get(this.#v9Ref);
130
+ }
131
+ /**
132
+ * Wait until the fork has happened, however the ledger-v8 chain is driven to it.
133
+ *
134
+ * Fails with the handover's own failure if it could not produce a ledger-v9 chain, so a broken handover surfaces here
135
+ * rather than leaving waiters blocked forever.
136
+ *
137
+ * @returns The ledger-v9 chain
138
+ */
139
+ awaitV9() {
140
+ return Deferred.await(this.#v9Ready);
141
+ }
142
+ /**
143
+ * Drive the ledger-v8 chain to the fork block with empty blocks and wait for the handover.
144
+ *
145
+ * Blocks the chain has already produced count towards the boundary, so this composes with transaction-driven
146
+ * production: submit what the test needs, then advance the rest of the way.
147
+ *
148
+ * @returns The ledger-v9 chain
149
+ */
150
+ advanceToFork() {
151
+ const v8 = this.v8;
152
+ const forkBlock = this.forkBlock;
153
+ const driveToForkBlock = () => Effect.gen(function* () {
154
+ const state = yield* v8.getLatestState();
155
+ if (V8.getCurrentBlockNumber(state) >= forkBlock)
156
+ return;
157
+ yield* v8.produceEmptyBlock();
158
+ yield* driveToForkBlock();
159
+ });
160
+ return pipe(driveToForkBlock(), Effect.flatMap(() => this.awaitV9()));
161
+ }
162
+ // ===========================================================================
163
+ // Internal
164
+ // ===========================================================================
165
+ /**
166
+ * Resolve once the ledger-v8 chain has reached the fork block, with the state at that point.
167
+ *
168
+ * The current state is checked before subscribing, and the ledger-v8 chain's shared state stream replays its latest
169
+ * value to new subscribers, so the boundary cannot slip past between the two.
170
+ */
171
+ #awaitForkBlock() {
172
+ const v8 = this.v8;
173
+ const forkBlock = this.forkBlock;
174
+ return Effect.gen(function* () {
175
+ const current = yield* v8.getLatestState();
176
+ if (V8.getCurrentBlockNumber(current) >= forkBlock)
177
+ return current;
178
+ const reached = yield* pipe(v8.state$, Stream.filter((state) => V8.getCurrentBlockNumber(state) >= forkBlock), Stream.take(1), Stream.runHead);
179
+ return yield* Option.match(reached, {
180
+ onNone: () => Effect.die(new Error('Ledger-v8 state stream ended before reaching the fork block')),
181
+ onSome: Effect.succeed,
182
+ });
183
+ });
184
+ }
185
+ /** Run the handover and construct the ledger-v9 chain, numbered and timed to continue from the fork point. */
186
+ #handover(v8State) {
187
+ const { forkVersion, forkBlock, v9BlockProducer } = this.#config;
188
+ const networkId = this.#networkId;
189
+ // The ledger-v9 chain resumes the ledger-v8 chain's height and clock, so the boundary is re-delivered rather than
190
+ // restarted. `blankState` takes the network id positionally, so it is not part of this.
191
+ const genesis = {
192
+ protocolVersion: forkVersion,
193
+ genesisBlockNumber: forkBlock,
194
+ genesisTime: v8State.currentTime,
195
+ };
196
+ /** A ledger-v9 chain whose genesis block already holds the given ledger, rather than one built by minting into it. */
197
+ const chainStartingFrom = (ledger) => pipe(Effect.promise(() => blankState(networkId, genesis)), Effect.map((state) => updateLedger(state, ledger)), Effect.flatMap((state) => Simulator.fromState(state, v9BlockProducer)));
198
+ return pipe(this.#config.translator(v8State.ledger.serialize()),
199
+ // Deserializing is the harness's job, not the translator's, so bytes ledger-v9 rejects are a
200
+ // translation failure rather than a crash out of the handover fiber.
201
+ Effect.flatMap((translated) => Effect.try({
202
+ try: () => LedgerState.deserialize(translated),
203
+ catch: (cause) => new LedgerTranslationError({
204
+ message: 'Translated bytes are not a valid ledger-v9 state',
205
+ cause,
206
+ }),
207
+ })), Effect.flatMap(chainStartingFrom));
208
+ }
209
+ }