@openzeppelin/miden-multisig-client 0.16.0 → 0.16.2
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 +58 -2
- package/dist/client.d.ts +12 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +12 -2
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +7 -2
- package/dist/client.test.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/multisig.d.ts +65 -2
- package/dist/multisig.d.ts.map +1 -1
- package/dist/multisig.js +148 -15
- package/dist/multisig.js.map +1 -1
- package/dist/multisig.test.js +217 -13
- package/dist/multisig.test.js.map +1 -1
- package/dist/proposal/factory.d.ts.map +1 -1
- package/dist/proposal/factory.js +10 -0
- package/dist/proposal/factory.js.map +1 -1
- package/dist/proposal/factory.test.d.ts +2 -0
- package/dist/proposal/factory.test.d.ts.map +1 -0
- package/dist/proposal/factory.test.js +32 -0
- package/dist/proposal/factory.test.js.map +1 -0
- package/dist/prover/config.d.ts +16 -0
- package/dist/prover/config.d.ts.map +1 -0
- package/dist/prover/config.js +54 -0
- package/dist/prover/config.js.map +1 -0
- package/dist/prover/config.test.d.ts +2 -0
- package/dist/prover/config.test.d.ts.map +1 -0
- package/dist/prover/config.test.js +54 -0
- package/dist/prover/config.test.js.map +1 -0
- package/dist/prover/errors.d.ts +7 -0
- package/dist/prover/errors.d.ts.map +1 -0
- package/dist/prover/errors.js +10 -0
- package/dist/prover/errors.js.map +1 -0
- package/dist/prover/errors.test.d.ts +2 -0
- package/dist/prover/errors.test.d.ts.map +1 -0
- package/dist/prover/errors.test.js +29 -0
- package/dist/prover/errors.test.js.map +1 -0
- package/dist/prover/retry.d.ts +5 -0
- package/dist/prover/retry.d.ts.map +1 -0
- package/dist/prover/retry.js +9 -0
- package/dist/prover/retry.js.map +1 -0
- package/dist/prover/retry.test.d.ts +2 -0
- package/dist/prover/retry.test.d.ts.map +1 -0
- package/dist/prover/retry.test.js +20 -0
- package/dist/prover/retry.test.js.map +1 -0
- package/dist/prover/workflow.d.ts +11 -0
- package/dist/prover/workflow.d.ts.map +1 -0
- package/dist/prover/workflow.js +18 -0
- package/dist/prover/workflow.js.map +1 -0
- package/dist/prover/workflow.test.d.ts +2 -0
- package/dist/prover/workflow.test.d.ts.map +1 -0
- package/dist/prover/workflow.test.js +103 -0
- package/dist/prover/workflow.test.js.map +1 -0
- package/dist/retry/classify.d.ts +16 -0
- package/dist/retry/classify.d.ts.map +1 -0
- package/dist/retry/classify.js +187 -0
- package/dist/retry/classify.js.map +1 -0
- package/dist/retry/runtime.d.ts +13 -0
- package/dist/retry/runtime.d.ts.map +1 -0
- package/dist/retry/runtime.js +33 -0
- package/dist/retry/runtime.js.map +1 -0
- package/dist/rpc/config.d.ts +11 -0
- package/dist/rpc/config.d.ts.map +1 -0
- package/dist/rpc/config.js +17 -0
- package/dist/rpc/config.js.map +1 -0
- package/dist/rpc/config.test.d.ts +2 -0
- package/dist/rpc/config.test.d.ts.map +1 -0
- package/dist/rpc/config.test.js +24 -0
- package/dist/rpc/config.test.js.map +1 -0
- package/dist/rpc/errors.d.ts +2 -0
- package/dist/rpc/errors.d.ts.map +1 -0
- package/dist/rpc/errors.js +11 -0
- package/dist/rpc/errors.js.map +1 -0
- package/dist/rpc/errors.test.d.ts +2 -0
- package/dist/rpc/errors.test.d.ts.map +1 -0
- package/dist/rpc/errors.test.js +34 -0
- package/dist/rpc/errors.test.js.map +1 -0
- package/dist/rpc/retry.d.ts +4 -0
- package/dist/rpc/retry.d.ts.map +1 -0
- package/dist/rpc/retry.js +6 -0
- package/dist/rpc/retry.js.map +1 -0
- package/dist/rpc/retry.test.d.ts +2 -0
- package/dist/rpc/retry.test.d.ts.map +1 -0
- package/dist/rpc/retry.test.js +98 -0
- package/dist/rpc/retry.test.js.map +1 -0
- package/dist/transaction/p2id.d.ts +8 -1
- package/dist/transaction/p2id.d.ts.map +1 -1
- package/dist/transaction/p2id.js +12 -3
- package/dist/transaction/p2id.js.map +1 -1
- package/dist/transaction.d.ts +1 -1
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +1 -1
- package/dist/transaction.js.map +1 -1
- package/package.json +2 -2
- package/src/client.test.ts +8 -2
- package/src/client.ts +28 -2
- package/src/index.ts +6 -0
- package/src/multisig.test.ts +263 -17
- package/src/multisig.ts +201 -14
- package/src/proposal/factory.test.ts +40 -0
- package/src/proposal/factory.ts +11 -1
- package/src/prover/config.test.ts +90 -0
- package/src/prover/config.ts +78 -0
- package/src/prover/errors.test.ts +61 -0
- package/src/prover/errors.ts +10 -0
- package/src/prover/retry.test.ts +38 -0
- package/src/prover/retry.ts +21 -0
- package/src/prover/test-node.d.ts +6 -0
- package/src/prover/workflow.test.ts +140 -0
- package/src/prover/workflow.ts +19 -0
- package/src/retry/classify.ts +220 -0
- package/src/retry/runtime.ts +45 -0
- package/src/rpc/config.test.ts +45 -0
- package/src/rpc/config.ts +30 -0
- package/src/rpc/errors.test.ts +69 -0
- package/src/rpc/errors.ts +12 -0
- package/src/rpc/retry.test.ts +144 -0
- package/src/rpc/retry.ts +12 -0
- package/src/transaction/p2id.ts +29 -10
- package/src/transaction.ts +1 -0
package/dist/multisig.test.js
CHANGED
|
@@ -2,10 +2,11 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
2
2
|
import { Multisig } from './multisig.js';
|
|
3
3
|
import { GuardianHttpClient } from '@openzeppelin/guardian-client';
|
|
4
4
|
import { buildUpdateProcedureThresholdTransactionRequest, buildUpdateGuardianTransactionRequest, buildUpdateSignersTransactionRequest, executeForSummary, } from './transaction.js';
|
|
5
|
-
const { mockRpcGetAccountDetails, mockAccountDeserialize, mockDetectConfig } = vi.hoisted(() => ({
|
|
5
|
+
const { mockRpcGetAccountDetails, mockAccountDeserialize, mockDetectConfig, mockNoteFileDeserialize } = vi.hoisted(() => ({
|
|
6
6
|
mockRpcGetAccountDetails: vi.fn(),
|
|
7
7
|
mockAccountDeserialize: vi.fn(),
|
|
8
8
|
mockDetectConfig: vi.fn(),
|
|
9
|
+
mockNoteFileDeserialize: vi.fn(),
|
|
9
10
|
}));
|
|
10
11
|
// Mock the Miden SDK
|
|
11
12
|
vi.mock('@miden-sdk/miden-sdk', () => ({
|
|
@@ -19,6 +20,14 @@ vi.mock('@miden-sdk/miden-sdk', () => ({
|
|
|
19
20
|
Private: 0,
|
|
20
21
|
Public: 1,
|
|
21
22
|
},
|
|
23
|
+
NoteExportFormat: {
|
|
24
|
+
Id: 0,
|
|
25
|
+
Full: 1,
|
|
26
|
+
Details: 2,
|
|
27
|
+
},
|
|
28
|
+
NoteFile: {
|
|
29
|
+
deserialize: mockNoteFileDeserialize,
|
|
30
|
+
},
|
|
22
31
|
TransactionSummary: {
|
|
23
32
|
deserialize: vi.fn().mockReturnValue({
|
|
24
33
|
toCommitment: () => ({
|
|
@@ -83,6 +92,9 @@ vi.mock('./transaction.js', () => ({
|
|
|
83
92
|
request: {},
|
|
84
93
|
salt: { toHex: () => '0x' + 'd'.repeat(64) },
|
|
85
94
|
}),
|
|
95
|
+
buildP2idNoteFromMetadata: vi.fn().mockReturnValue({
|
|
96
|
+
id: () => ({ toString: () => '0x' + 'ab'.repeat(32) }),
|
|
97
|
+
}),
|
|
86
98
|
// Mirrors the real implementations against the mocked NoteType values
|
|
87
99
|
// (Private = 0, Public = 1).
|
|
88
100
|
parseP2idNoteType: vi.fn((value) => {
|
|
@@ -139,8 +151,8 @@ describe('Multisig', () => {
|
|
|
139
151
|
let mockSigner;
|
|
140
152
|
let mockAccount;
|
|
141
153
|
let mockWebClient;
|
|
142
|
-
function createTestMultisig(config, signer = mockSigner, accountId) {
|
|
143
|
-
return new Multisig(mockAccount, config, guardian, signer, mockWebClient, accountId, MIDEN_RPC_ENDPOINT);
|
|
154
|
+
function createTestMultisig(config, signer = mockSigner, accountId, proverConfig) {
|
|
155
|
+
return new Multisig(mockAccount, config, guardian, signer, mockWebClient, accountId, MIDEN_RPC_ENDPOINT, proverConfig);
|
|
144
156
|
}
|
|
145
157
|
beforeEach(() => {
|
|
146
158
|
mockFetch.mockReset();
|
|
@@ -194,13 +206,36 @@ describe('Multisig', () => {
|
|
|
194
206
|
submitNewTransaction: vi.fn(),
|
|
195
207
|
submitNewTransactionWithProver: vi.fn(),
|
|
196
208
|
transactions: {
|
|
197
|
-
|
|
209
|
+
executeRequest: vi.fn(),
|
|
198
210
|
},
|
|
199
211
|
getConsumableNotes: vi.fn().mockResolvedValue([]),
|
|
200
212
|
syncState: vi.fn(),
|
|
201
213
|
getAccount: vi.fn().mockResolvedValue(null),
|
|
202
214
|
newAccount: vi.fn(),
|
|
203
215
|
};
|
|
216
|
+
mockWebClient.transactions.executeRequest.mockImplementation(async (accountId, request) => {
|
|
217
|
+
const result = await mockWebClient.executeTransaction(accountId, request);
|
|
218
|
+
return {
|
|
219
|
+
result,
|
|
220
|
+
prove: async (options) => {
|
|
221
|
+
const proof = options?.prover === undefined
|
|
222
|
+
? await mockWebClient.proveTransaction(result)
|
|
223
|
+
: await mockWebClient.proveTransaction(result, options.prover);
|
|
224
|
+
return {
|
|
225
|
+
proof,
|
|
226
|
+
result,
|
|
227
|
+
submit: async () => {
|
|
228
|
+
const blockNumber = await mockWebClient.submitProvenTransaction(proof, result);
|
|
229
|
+
return {
|
|
230
|
+
blockNumber,
|
|
231
|
+
result,
|
|
232
|
+
apply: () => mockWebClient.applyTransaction(result, blockNumber),
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
});
|
|
204
239
|
});
|
|
205
240
|
describe('constructor', () => {
|
|
206
241
|
it('should create Multisig with account', () => {
|
|
@@ -1090,6 +1125,118 @@ describe('Multisig', () => {
|
|
|
1090
1125
|
expect(proposal.metadata.noteType).toBe('private');
|
|
1091
1126
|
});
|
|
1092
1127
|
});
|
|
1128
|
+
describe('exportNoteToBytes / importNoteFromBytes (issue #356)', () => {
|
|
1129
|
+
const config = {
|
|
1130
|
+
threshold: 1,
|
|
1131
|
+
signerCommitments: ['0x' + '1'.repeat(64)],
|
|
1132
|
+
guardianCommitment: '0x' + '3'.repeat(64),
|
|
1133
|
+
};
|
|
1134
|
+
it('exports the full note with proof when the inclusion proof is known', async () => {
|
|
1135
|
+
const noteFile = { serialize: () => new Uint8Array([9, 9, 9]) };
|
|
1136
|
+
mockWebClient.getOutputNote = vi.fn().mockResolvedValue({
|
|
1137
|
+
inclusionProof: () => ({}),
|
|
1138
|
+
});
|
|
1139
|
+
mockWebClient.exportNoteFile = vi.fn().mockResolvedValue(noteFile);
|
|
1140
|
+
const multisig = createTestMultisig(config);
|
|
1141
|
+
const bytes = await multisig.exportNoteToBytes('0x' + 'ab'.repeat(32));
|
|
1142
|
+
expect(bytes).toEqual(new Uint8Array([9, 9, 9]));
|
|
1143
|
+
// NoteExportFormat.Full = 1 in the SDK mock
|
|
1144
|
+
expect(mockWebClient.exportNoteFile).toHaveBeenCalledWith('0x' + 'ab'.repeat(32), 1);
|
|
1145
|
+
});
|
|
1146
|
+
it('falls back to a details-only export before the note commits on chain', async () => {
|
|
1147
|
+
const noteFile = { serialize: () => new Uint8Array([7]) };
|
|
1148
|
+
mockWebClient.getOutputNote = vi.fn().mockResolvedValue({
|
|
1149
|
+
inclusionProof: () => undefined,
|
|
1150
|
+
});
|
|
1151
|
+
mockWebClient.exportNoteFile = vi.fn().mockResolvedValue(noteFile);
|
|
1152
|
+
const multisig = createTestMultisig(config);
|
|
1153
|
+
await multisig.exportNoteToBytes(' 0x' + 'ab'.repeat(32) + ' ');
|
|
1154
|
+
// NoteExportFormat.Details = 2 in the SDK mock; the id is trimmed
|
|
1155
|
+
expect(mockWebClient.exportNoteFile).toHaveBeenCalledWith('0x' + 'ab'.repeat(32), 2);
|
|
1156
|
+
});
|
|
1157
|
+
it('rejects exporting a note the local store does not know', async () => {
|
|
1158
|
+
mockWebClient.getOutputNote = vi.fn().mockRejectedValue(new Error('no such note'));
|
|
1159
|
+
mockWebClient.exportNoteFile = vi.fn();
|
|
1160
|
+
const multisig = createTestMultisig(config);
|
|
1161
|
+
await expect(multisig.exportNoteToBytes('0x' + 'ab'.repeat(32))).rejects.toThrow(/not found in the local store/);
|
|
1162
|
+
expect(mockWebClient.exportNoteFile).not.toHaveBeenCalled();
|
|
1163
|
+
});
|
|
1164
|
+
it('rejects exporting when the store resolves no record', async () => {
|
|
1165
|
+
mockWebClient.getOutputNote = vi.fn().mockResolvedValue(undefined);
|
|
1166
|
+
mockWebClient.exportNoteFile = vi.fn();
|
|
1167
|
+
const multisig = createTestMultisig(config);
|
|
1168
|
+
await expect(multisig.exportNoteToBytes('0x' + 'ab'.repeat(32))).rejects.toThrow(/not found in the local store/);
|
|
1169
|
+
expect(mockWebClient.exportNoteFile).not.toHaveBeenCalled();
|
|
1170
|
+
});
|
|
1171
|
+
it('imports note file bytes and returns the resolved identifier', async () => {
|
|
1172
|
+
const decoded = { marker: 'note-file' };
|
|
1173
|
+
mockNoteFileDeserialize.mockReturnValue(decoded);
|
|
1174
|
+
mockWebClient.importNoteFile = vi.fn().mockResolvedValue('0x' + 'cd'.repeat(32));
|
|
1175
|
+
const multisig = createTestMultisig(config);
|
|
1176
|
+
const noteId = await multisig.importNoteFromBytes(new Uint8Array([1, 2, 3]));
|
|
1177
|
+
expect(mockNoteFileDeserialize).toHaveBeenCalledWith(new Uint8Array([1, 2, 3]));
|
|
1178
|
+
expect(mockWebClient.importNoteFile).toHaveBeenCalledWith(decoded);
|
|
1179
|
+
expect(noteId).toBe('0x' + 'cd'.repeat(32));
|
|
1180
|
+
});
|
|
1181
|
+
it('rejects bytes that do not decode as a note file', async () => {
|
|
1182
|
+
mockNoteFileDeserialize.mockImplementation(() => {
|
|
1183
|
+
throw new Error('bad bytes');
|
|
1184
|
+
});
|
|
1185
|
+
mockWebClient.importNoteFile = vi.fn();
|
|
1186
|
+
const multisig = createTestMultisig(config);
|
|
1187
|
+
await expect(multisig.importNoteFromBytes(new Uint8Array([0]))).rejects.toThrow(/failed to decode note file: bad bytes/);
|
|
1188
|
+
expect(mockWebClient.importNoteFile).not.toHaveBeenCalled();
|
|
1189
|
+
});
|
|
1190
|
+
});
|
|
1191
|
+
describe('exportNoteToFile / importNoteFromFile (issue #356)', () => {
|
|
1192
|
+
const config = {
|
|
1193
|
+
threshold: 1,
|
|
1194
|
+
signerCommitments: ['0x' + '1'.repeat(64)],
|
|
1195
|
+
guardianCommitment: '0x' + '3'.repeat(64),
|
|
1196
|
+
};
|
|
1197
|
+
it('rejects exportNoteToFile outside a browser environment', async () => {
|
|
1198
|
+
const multisig = createTestMultisig(config);
|
|
1199
|
+
await expect(multisig.exportNoteToFile('0x' + 'ab'.repeat(32))).rejects.toThrow(/requires a browser environment/);
|
|
1200
|
+
});
|
|
1201
|
+
it('imports from a File/Blob by delegating to importNoteFromBytes', async () => {
|
|
1202
|
+
const decoded = { marker: 'note-file' };
|
|
1203
|
+
mockNoteFileDeserialize.mockReturnValue(decoded);
|
|
1204
|
+
mockWebClient.importNoteFile = vi.fn().mockResolvedValue('0x' + 'cd'.repeat(32));
|
|
1205
|
+
const multisig = createTestMultisig(config);
|
|
1206
|
+
const noteId = await multisig.importNoteFromFile(new Blob([new Uint8Array([1, 2, 3])]));
|
|
1207
|
+
expect(mockNoteFileDeserialize).toHaveBeenCalledWith(new Uint8Array([1, 2, 3]));
|
|
1208
|
+
expect(noteId).toBe('0x' + 'cd'.repeat(32));
|
|
1209
|
+
});
|
|
1210
|
+
});
|
|
1211
|
+
describe('getP2idNoteId (issue #356)', () => {
|
|
1212
|
+
const config = {
|
|
1213
|
+
threshold: 1,
|
|
1214
|
+
signerCommitments: ['0x' + '1'.repeat(64)],
|
|
1215
|
+
guardianCommitment: '0x' + '3'.repeat(64),
|
|
1216
|
+
};
|
|
1217
|
+
it('computes the deterministic note ID from p2id proposal metadata', async () => {
|
|
1218
|
+
const multisig = createTestMultisig(config);
|
|
1219
|
+
const proposal = {
|
|
1220
|
+
metadata: {
|
|
1221
|
+
proposalType: 'p2id',
|
|
1222
|
+
recipientId: '0x' + 'b'.repeat(30),
|
|
1223
|
+
faucetId: '0x' + 'c'.repeat(30),
|
|
1224
|
+
amount: '100',
|
|
1225
|
+
saltHex: '0x' + 'd'.repeat(64),
|
|
1226
|
+
noteType: 'private',
|
|
1227
|
+
},
|
|
1228
|
+
};
|
|
1229
|
+
const noteId = await multisig.getP2idNoteId(proposal);
|
|
1230
|
+
expect(noteId).toBe('0x' + 'ab'.repeat(32));
|
|
1231
|
+
});
|
|
1232
|
+
it('rejects non-p2id proposals', async () => {
|
|
1233
|
+
const multisig = createTestMultisig(config);
|
|
1234
|
+
const proposal = {
|
|
1235
|
+
metadata: { proposalType: 'consume_notes' },
|
|
1236
|
+
};
|
|
1237
|
+
await expect(multisig.getP2idNoteId(proposal)).rejects.toThrow(/requires a P2ID proposal/);
|
|
1238
|
+
});
|
|
1239
|
+
});
|
|
1093
1240
|
describe('createChangeThresholdProposal', () => {
|
|
1094
1241
|
it('passes the signer scheme to update-signers requests', async () => {
|
|
1095
1242
|
vi.mocked(executeForSummary).mockResolvedValue({
|
|
@@ -1675,6 +1822,7 @@ describe('Multisig', () => {
|
|
|
1675
1822
|
ok: false,
|
|
1676
1823
|
status: 404,
|
|
1677
1824
|
statusText: 'Not Found',
|
|
1825
|
+
headers: new Headers(),
|
|
1678
1826
|
// Feature 009: only a conforming { code, message, meta } envelope is
|
|
1679
1827
|
// folded into the error message; raw text bodies are dropped.
|
|
1680
1828
|
text: async () => JSON.stringify({
|
|
@@ -2393,7 +2541,11 @@ describe('Multisig', () => {
|
|
|
2393
2541
|
scheme: 'ecdsa',
|
|
2394
2542
|
publicKey: '0x' + '2'.repeat(66),
|
|
2395
2543
|
};
|
|
2396
|
-
const multisig = createTestMultisig(config, ecdsaSigner
|
|
2544
|
+
const multisig = createTestMultisig(config, ecdsaSigner, undefined, {
|
|
2545
|
+
kind: 'remote',
|
|
2546
|
+
maxAttempts: 2,
|
|
2547
|
+
createProver: () => ({}),
|
|
2548
|
+
});
|
|
2397
2549
|
const proposalId = '0x' + 'c'.repeat(64);
|
|
2398
2550
|
const cosignerPubkey = '0x' + '3'.repeat(66);
|
|
2399
2551
|
const ackPubkey = '0x' + '4'.repeat(66);
|
|
@@ -2456,9 +2608,22 @@ describe('Multisig', () => {
|
|
|
2456
2608
|
ack_scheme: 'ecdsa',
|
|
2457
2609
|
}),
|
|
2458
2610
|
});
|
|
2459
|
-
mockWebClient.
|
|
2460
|
-
|
|
2461
|
-
|
|
2611
|
+
mockWebClient.proveTransaction
|
|
2612
|
+
.mockRejectedValueOnce(Object.assign(new Error('unavailable'), { code: 'Unavailable' }))
|
|
2613
|
+
.mockResolvedValueOnce({});
|
|
2614
|
+
vi.useFakeTimers();
|
|
2615
|
+
try {
|
|
2616
|
+
const execution = multisig.executeProposal(proposalId);
|
|
2617
|
+
await vi.runAllTimersAsync();
|
|
2618
|
+
await expect(execution).resolves.toBeUndefined();
|
|
2619
|
+
}
|
|
2620
|
+
finally {
|
|
2621
|
+
vi.useRealTimers();
|
|
2622
|
+
}
|
|
2623
|
+
expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
|
|
2624
|
+
expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(2);
|
|
2625
|
+
expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
|
|
2626
|
+
expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
|
|
2462
2627
|
expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(1, cosignerSignature, 'ecdsa');
|
|
2463
2628
|
expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(2, ackSignature, 'ecdsa');
|
|
2464
2629
|
expect(vi.mocked(buildSignatureAdviceEntry)).toHaveBeenNthCalledWith(1, expect.anything(), expect.anything(), expect.anything(), cosignerPubkey, cosignerSignature);
|
|
@@ -2539,8 +2704,11 @@ describe('Multisig', () => {
|
|
|
2539
2704
|
ack_scheme: 'ecdsa',
|
|
2540
2705
|
}),
|
|
2541
2706
|
});
|
|
2542
|
-
mockWebClient.transactions.submit.mockResolvedValueOnce({});
|
|
2543
2707
|
await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
|
|
2708
|
+
expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
|
|
2709
|
+
expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(1);
|
|
2710
|
+
expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
|
|
2711
|
+
expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
|
|
2544
2712
|
expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(1, cosignerSignature, 'ecdsa');
|
|
2545
2713
|
expect(vi.mocked(signatureHexToBytes)).toHaveBeenNthCalledWith(2, ackSignature, 'ecdsa');
|
|
2546
2714
|
expect(vi.mocked(buildSignatureAdviceEntry)).toHaveBeenNthCalledWith(1, expect.anything(), expect.anything(), expect.anything(), cosignerPubkey, cosignerSignature);
|
|
@@ -2620,9 +2788,11 @@ describe('Multisig', () => {
|
|
|
2620
2788
|
ok: true,
|
|
2621
2789
|
json: async () => ({ success: true, message: 'ok', ack_pubkey: '0x' + 'f'.repeat(64) }),
|
|
2622
2790
|
});
|
|
2623
|
-
mockWebClient.transactions.submit.mockResolvedValueOnce({});
|
|
2624
2791
|
await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
|
|
2625
|
-
expect(mockWebClient.
|
|
2792
|
+
expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
|
|
2793
|
+
expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(1);
|
|
2794
|
+
expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
|
|
2795
|
+
expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
|
|
2626
2796
|
});
|
|
2627
2797
|
it('should still switch GUARDIAN when the pre-switch canonicalization push fails', async () => {
|
|
2628
2798
|
const config = {
|
|
@@ -2666,9 +2836,11 @@ describe('Multisig', () => {
|
|
|
2666
2836
|
ok: true,
|
|
2667
2837
|
json: async () => ({ success: true, message: 'ok', ack_pubkey: '0x' + 'f'.repeat(64) }),
|
|
2668
2838
|
});
|
|
2669
|
-
mockWebClient.transactions.submit.mockResolvedValueOnce({});
|
|
2670
2839
|
await expect(multisig.executeProposal(proposalId)).resolves.toBeUndefined();
|
|
2671
|
-
expect(mockWebClient.
|
|
2840
|
+
expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
|
|
2841
|
+
expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(1);
|
|
2842
|
+
expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
|
|
2843
|
+
expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
|
|
2672
2844
|
});
|
|
2673
2845
|
it('should reject switch_guardian execution when endpoint commitment mismatches', async () => {
|
|
2674
2846
|
const config = {
|
|
@@ -2786,6 +2958,38 @@ describe('Multisig', () => {
|
|
|
2786
2958
|
await expect(multisig.executeProposal(proposalId)).rejects.toThrow('Duplicate advice-map key detected');
|
|
2787
2959
|
});
|
|
2788
2960
|
});
|
|
2961
|
+
describe('submitTransaction', () => {
|
|
2962
|
+
it('uses the configured total proof-attempt budget without repeating other stages', async () => {
|
|
2963
|
+
const multisig = createTestMultisig({
|
|
2964
|
+
threshold: 1,
|
|
2965
|
+
signerCommitments: ['0x' + 'a'.repeat(64)],
|
|
2966
|
+
guardianCommitment: '0x' + 'c'.repeat(64),
|
|
2967
|
+
}, mockSigner, undefined, {
|
|
2968
|
+
kind: 'remote',
|
|
2969
|
+
maxAttempts: 4,
|
|
2970
|
+
createProver: () => ({}),
|
|
2971
|
+
});
|
|
2972
|
+
const transient = Object.assign(new Error('unavailable'), { code: 'Unavailable' });
|
|
2973
|
+
mockWebClient.proveTransaction
|
|
2974
|
+
.mockRejectedValueOnce(transient)
|
|
2975
|
+
.mockRejectedValueOnce(transient)
|
|
2976
|
+
.mockRejectedValueOnce(transient)
|
|
2977
|
+
.mockResolvedValueOnce({});
|
|
2978
|
+
vi.useFakeTimers();
|
|
2979
|
+
try {
|
|
2980
|
+
const submission = multisig.submitTransaction({});
|
|
2981
|
+
await vi.runAllTimersAsync();
|
|
2982
|
+
await submission;
|
|
2983
|
+
}
|
|
2984
|
+
finally {
|
|
2985
|
+
vi.useRealTimers();
|
|
2986
|
+
}
|
|
2987
|
+
expect(mockWebClient.executeTransaction).toHaveBeenCalledTimes(1);
|
|
2988
|
+
expect(mockWebClient.proveTransaction).toHaveBeenCalledTimes(4);
|
|
2989
|
+
expect(mockWebClient.submitProvenTransaction).toHaveBeenCalledTimes(1);
|
|
2990
|
+
expect(mockWebClient.applyTransaction).toHaveBeenCalledTimes(1);
|
|
2991
|
+
});
|
|
2992
|
+
});
|
|
2789
2993
|
describe('prepareCustomExecution', () => {
|
|
2790
2994
|
const requestBytes = new Uint8Array([9, 8, 7]);
|
|
2791
2995
|
function customDelta(proposalType, cosignerSigs) {
|