@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.
- package/README.md +27 -3
- package/dist/chainVersion/chainVersionProbe.d.ts +95 -0
- package/dist/chainVersion/chainVersionProbe.js +83 -0
- package/dist/chainVersion/index.d.ts +1 -0
- package/dist/chainVersion/index.js +13 -0
- package/dist/codecs/index.d.ts +1 -0
- package/dist/codecs/index.js +13 -0
- package/dist/codecs/ledgerParameters.d.ts +81 -0
- package/dist/codecs/ledgerParameters.js +63 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/pendingTransactions/pendingTransactions.d.ts +113 -9
- package/dist/pendingTransactions/pendingTransactions.js +147 -30
- package/dist/pendingTransactions/pendingTransactionsService.d.ts +25 -9
- package/dist/pendingTransactions/pendingTransactionsService.js +33 -21
- package/dist/proving/index.d.ts +2 -0
- package/dist/proving/index.js +2 -0
- package/dist/proving/provingService.d.ts +184 -15
- package/dist/proving/provingService.js +114 -12
- package/dist/proving/v8ProvingService.d.ts +53 -0
- package/dist/proving/v8ProvingService.js +71 -0
- package/dist/proving/versionedProving.d.ts +42 -0
- package/dist/proving/versionedProving.js +103 -0
- package/dist/signatures/index.d.ts +2 -0
- package/dist/signatures/index.js +14 -0
- package/dist/signatures/signing.d.ts +37 -0
- package/dist/signatures/signing.js +13 -0
- package/dist/signatures/v8Signatures.d.ts +54 -0
- package/dist/signatures/v8Signatures.js +62 -0
- package/dist/simulation/ForkSimulator.d.ts +114 -0
- package/dist/simulation/ForkSimulator.js +209 -0
- package/dist/simulation/LedgerTranslation.d.ts +53 -0
- package/dist/simulation/LedgerTranslation.js +56 -0
- package/dist/simulation/core/VersionTimeline.d.ts +54 -0
- package/dist/simulation/core/VersionTimeline.js +56 -0
- package/dist/simulation/core/blocks.d.ts +38 -0
- package/dist/simulation/core/blocks.js +52 -0
- package/dist/simulation/core/index.d.ts +13 -0
- package/dist/simulation/core/index.js +25 -0
- package/dist/simulation/core/strictness.d.ts +29 -0
- package/dist/simulation/core/strictness.js +39 -0
- package/dist/simulation/index.d.ts +22 -2
- package/dist/simulation/index.js +25 -14
- package/dist/simulation/v8/Simulator.d.ts +231 -0
- package/dist/simulation/v8/Simulator.js +503 -0
- package/dist/simulation/{SimulatorState.d.ts → v8/SimulatorState.d.ts} +31 -44
- package/dist/simulation/v8/SimulatorState.js +290 -0
- package/dist/simulation/v8/index.d.ts +2 -0
- package/dist/simulation/v8/index.js +26 -0
- package/dist/simulation/{Simulator.d.ts → v9/Simulator.d.ts} +57 -6
- package/dist/simulation/{Simulator.js → v9/Simulator.js} +68 -18
- package/dist/simulation/v9/SimulatorState.d.ts +336 -0
- package/dist/simulation/{SimulatorState.js → v9/SimulatorState.js} +34 -67
- package/dist/simulation/v9/index.d.ts +2 -0
- package/dist/simulation/v9/index.js +26 -0
- package/dist/submission/submissionService.d.ts +2 -1
- package/dist/submission/submissionService.js +1 -1
- package/dist/validation/blockData.d.ts +42 -2
- package/dist/validation/blockData.js +55 -8
- package/dist/validation/index.d.ts +2 -0
- package/dist/validation/index.js +2 -0
- package/dist/validation/v8ValidationService.d.ts +29 -0
- package/dist/validation/v8ValidationService.js +48 -0
- package/dist/validation/validationService.d.ts +132 -17
- package/dist/validation/validationService.js +88 -42
- package/dist/validation/versionedValidation.d.ts +46 -0
- package/dist/validation/versionedValidation.js +55 -0
- 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,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
|
+
}
|