@fizzyflow/endless-vector 0.0.7 → 0.0.10
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/EndlessVector.js +401 -142
- package/EndlessVectorArchive.js +31 -36
- package/EndlessVectorHistory.js +17 -13
- package/EndlessVectorItem.js +160 -0
- package/EndlessVectorSeal.js +192 -0
- package/EndlessVectorWalrus.js +467 -0
- package/README.md +253 -198
- package/ids.js +4 -4
- package/index.d.ts +181 -157
- package/index.js +5 -1
- package/package.json +16 -6
- package/test/base.test.js +388 -427
- package/test/base.test.txt +499 -0
- package/test/fixture.js +93 -0
- package/test/grpc_json_browser.test.js +22 -0
- package/test/helpers.js +2 -2
- package/test/helpers.txt +32 -0
- package/test/seal.test.js +319 -0
- package/test/walrus_blobs.test.js +178 -0
- package/test/walrus_blobs_extend.test.js +115 -0
- package/test/walrus_blobs_history.test.js +301 -0
- package/test/walrus_blobs_sdk.test.js +148 -0
- package/tsconfig.json +13 -0
- package/vitest.config.js +16 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import t, { end } from 'tap';
|
|
4
|
+
import { SuiMaster, SuiLocalTestValidator, SuiObject } from 'suidouble';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { equalUint8Arrays, formatBytes, randomBytesOfLength } from './helpers.js';
|
|
8
|
+
|
|
9
|
+
import { EndlessVector } from '../index.js';
|
|
10
|
+
// import { get } from 'http';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
|
|
15
|
+
const { test } = t;
|
|
16
|
+
|
|
17
|
+
let suiLocalTestValidator = null;
|
|
18
|
+
|
|
19
|
+
/** @type {?SuiMaster} */
|
|
20
|
+
let suiMaster = null;
|
|
21
|
+
let signAndExecuteTransaction = null;
|
|
22
|
+
|
|
23
|
+
let contract = null;
|
|
24
|
+
|
|
25
|
+
/** @type {?SuiObject} */
|
|
26
|
+
let endlessVectorRaw = null; // SuiObject instance
|
|
27
|
+
/** @type {?EndlessVector} */
|
|
28
|
+
let endlessVector = null; // EndlessVector instance
|
|
29
|
+
|
|
30
|
+
test('spawn local test node', async t => {
|
|
31
|
+
suiLocalTestValidator = await SuiLocalTestValidator.launch({ testFallbackEnabled: true, debug: true, });
|
|
32
|
+
t.ok(suiLocalTestValidator.active);
|
|
33
|
+
// SuiLocalTestValidator runs as signle instance. So you can't start it twice with static method
|
|
34
|
+
const suiLocalTestValidatorCopy = await SuiLocalTestValidator.launch();
|
|
35
|
+
t.equal(suiLocalTestValidator, suiLocalTestValidatorCopy);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('init suiMaster and connect it to local test validator', async t => {
|
|
39
|
+
suiMaster = new SuiMaster({client: 'local', as: 'noname_tester', debug: true});
|
|
40
|
+
await suiMaster.initialize();
|
|
41
|
+
t.ok(suiMaster.address); // there should be some address
|
|
42
|
+
t.ok(`${suiMaster.address}`.indexOf('0x') === 0); // adress is string starting with '0x'
|
|
43
|
+
|
|
44
|
+
signAndExecuteTransaction = async (tx) => {
|
|
45
|
+
// function accepting Transaction and returning signed and submitted transaction digest
|
|
46
|
+
const results = await suiMaster.signAndExecuteTransaction({
|
|
47
|
+
transaction: tx,
|
|
48
|
+
});
|
|
49
|
+
return results.digest;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('request sui from faucet', async t => {
|
|
54
|
+
const balanceBefore = await suiMaster.getBalance();
|
|
55
|
+
await suiMaster.requestSuiFromFaucet();
|
|
56
|
+
await suiMaster.requestSuiFromFaucet();
|
|
57
|
+
await suiMaster.requestSuiFromFaucet();
|
|
58
|
+
await suiMaster.requestSuiFromFaucet();
|
|
59
|
+
await suiMaster.requestSuiFromFaucet();
|
|
60
|
+
await suiMaster.requestSuiFromFaucet();
|
|
61
|
+
const balanceAfter = await suiMaster.getBalance();
|
|
62
|
+
t.ok(balanceAfter > balanceBefore);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('attach a local package', async t => {
|
|
66
|
+
contract = suiMaster.addPackage({
|
|
67
|
+
path: path.join(__dirname, '../../move'),
|
|
68
|
+
});
|
|
69
|
+
await contract.build({ withUnpublishedDependencies: true, });
|
|
70
|
+
await contract.publish();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
test('test raw create transaction', async t => {
|
|
75
|
+
const arr = randomBytesOfLength(120 * 1024); // 100KB
|
|
76
|
+
|
|
77
|
+
const tx = new suiMaster.Transaction();
|
|
78
|
+
const vectorTxInput = await EndlessVector.getCreateTransactionAndReturnVectorInput({
|
|
79
|
+
packageId: contract.id,
|
|
80
|
+
}, arr, tx);
|
|
81
|
+
tx.transferObjects([vectorTxInput], tx.pure.address(suiMaster.address));
|
|
82
|
+
|
|
83
|
+
t.ok(tx);
|
|
84
|
+
let digest = null;
|
|
85
|
+
try {
|
|
86
|
+
digest = await signAndExecuteTransaction(tx);
|
|
87
|
+
t.ok(digest);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
console.error('Error preparing transaction:', e);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const transactionBlockResponse = await suiMaster.client.waitForTransaction({
|
|
93
|
+
digest: digest,
|
|
94
|
+
options: { showObjectChanges: true, },
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Find the created EndlessVector object
|
|
98
|
+
const objectChanges = transactionBlockResponse.objectChanges || [];
|
|
99
|
+
const createdVector = objectChanges.find(
|
|
100
|
+
change => change.type === 'created' &&
|
|
101
|
+
change.objectType &&
|
|
102
|
+
change.objectType.includes('endless_vector::EndlessVector')
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const createdVectorId = createdVector ? createdVector.objectId : null;
|
|
106
|
+
t.ok(createdVectorId, 'Created EndlessVector object should have an ID');
|
|
107
|
+
|
|
108
|
+
const loadBack = new EndlessVector({
|
|
109
|
+
suiClient: suiMaster.client,
|
|
110
|
+
id: createdVectorId,
|
|
111
|
+
});
|
|
112
|
+
await loadBack.initialize();
|
|
113
|
+
|
|
114
|
+
t.ok(loadBack.length === 1, `Loaded vector should have length 1, got ${loadBack.length}`);
|
|
115
|
+
const getBack = await loadBack.at(0);
|
|
116
|
+
t.ok(equalUint8Arrays(getBack, arr), 'Data retrieved from loaded vector should match original data');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
test('make a test EndlessVector with few chunks in a single tx', async t => {
|
|
121
|
+
const data = [
|
|
122
|
+
randomBytesOfLength(1 * 1024),
|
|
123
|
+
randomBytesOfLength(2 * 1024),
|
|
124
|
+
randomBytesOfLength(3 * 1024),
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const testEndlessVector = await EndlessVector.create({
|
|
128
|
+
array: data,
|
|
129
|
+
suiClient: suiMaster.client, // instance of Sui SDK SuiClient
|
|
130
|
+
packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
|
|
131
|
+
signAndExecuteTransaction: signAndExecuteTransaction,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const getBack0 = await testEndlessVector.at(0);
|
|
135
|
+
const getBack1 = await testEndlessVector.at(1);
|
|
136
|
+
const getBack2 = await testEndlessVector.at(2);
|
|
137
|
+
|
|
138
|
+
t.ok(equalUint8Arrays(getBack0, data[0]));
|
|
139
|
+
t.ok(equalUint8Arrays(getBack1, data[1]));
|
|
140
|
+
t.ok(equalUint8Arrays(getBack2, data[2]));
|
|
141
|
+
|
|
142
|
+
t.ok(testEndlessVector.length === 3);
|
|
143
|
+
t.ok(testEndlessVector.binaryLength === 6 * 1024);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('make a test EndlessVector and push single Uint8Array to it', async t => {
|
|
147
|
+
endlessVector = await EndlessVector.create({
|
|
148
|
+
suiClient: suiMaster.client, // instance of Sui SDK SuiClient
|
|
149
|
+
packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
|
|
150
|
+
signAndExecuteTransaction: signAndExecuteTransaction,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
t.ok(endlessVector);
|
|
154
|
+
t.ok(endlessVector.id);
|
|
155
|
+
t.ok(endlessVector.isWritable); // we provided packageId and signAndExecuteTransaction
|
|
156
|
+
|
|
157
|
+
await endlessVector.push(new Uint8Array([1,2,3]));
|
|
158
|
+
|
|
159
|
+
const getBack = await endlessVector.at(0);
|
|
160
|
+
t.ok(equalUint8Arrays(getBack, new Uint8Array([1,2,3])));
|
|
161
|
+
|
|
162
|
+
await endlessVector.initialize(); // endlessVector.at(0) calles initialize() internally, but we call it again to keep code clear
|
|
163
|
+
|
|
164
|
+
t.ok(endlessVector.length === 1);
|
|
165
|
+
t.ok(endlessVector.binaryLength === 3);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('push Uint8Array larger than max_pure_argument_size to it', async t => {
|
|
169
|
+
const largeArray = randomBytesOfLength(30 * 1024); // 30KB
|
|
170
|
+
await endlessVector.push(largeArray);
|
|
171
|
+
|
|
172
|
+
const getBack = await endlessVector.at(1);
|
|
173
|
+
t.ok(equalUint8Arrays(getBack, largeArray));
|
|
174
|
+
t.ok(getBack.length === 30*1024);
|
|
175
|
+
|
|
176
|
+
t.ok(endlessVector.length === 2);
|
|
177
|
+
t.ok(endlessVector.binaryLength === 3 + 30*1024);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('push Uint8Array larger than max_pure_argument_size to it', async t => {
|
|
181
|
+
const largeArray = randomBytesOfLength(120 * 1024); // 120KB
|
|
182
|
+
await endlessVector.push(largeArray);
|
|
183
|
+
|
|
184
|
+
const getBack = await endlessVector.at(2);
|
|
185
|
+
t.ok(equalUint8Arrays(getBack, largeArray));
|
|
186
|
+
t.ok(getBack.length === 120*1024);
|
|
187
|
+
|
|
188
|
+
t.ok(endlessVector.length === 3);
|
|
189
|
+
t.ok(endlessVector.binaryLength === 3 + 30*1024 + 120*1024);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
test('throws an Error is trying to push too large Uint8Array ( split it on the higher level )', async t => {
|
|
194
|
+
const tooLargeSize = 120 * 1024 + 1; // 120KB + 1 byte
|
|
195
|
+
const largeArray = randomBytesOfLength(tooLargeSize);
|
|
196
|
+
await t.rejects(endlessVector.push(largeArray), Error, 'expected an Error to be thrown');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('push few Uint8Array in a single transaction block', async t => {
|
|
200
|
+
const singleItemSize = 40 * 1024;
|
|
201
|
+
const largeArray1 = randomBytesOfLength(singleItemSize);
|
|
202
|
+
const largeArray2 = randomBytesOfLength(singleItemSize);
|
|
203
|
+
const largeArray3 = randomBytesOfLength(singleItemSize);
|
|
204
|
+
const tx = new suiMaster.Transaction();
|
|
205
|
+
await endlessVector.getPushTransaction(largeArray1, tx);
|
|
206
|
+
await endlessVector.getPushTransaction(largeArray2, tx);
|
|
207
|
+
await endlessVector.getPushTransaction(largeArray3, tx);
|
|
208
|
+
|
|
209
|
+
await suiMaster.signAndExecuteTransaction({
|
|
210
|
+
transaction: tx,
|
|
211
|
+
requestType: 'WaitForLocalExecution',
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await endlessVector.reInitialize(); // force re-initialization to load new data
|
|
215
|
+
const getBack1 = await endlessVector.at(3);
|
|
216
|
+
const getBack2 = await endlessVector.at(4);
|
|
217
|
+
const getBack3 = await endlessVector.at(5);
|
|
218
|
+
|
|
219
|
+
t.ok(equalUint8Arrays(getBack1, largeArray1));
|
|
220
|
+
t.ok(getBack1.length === singleItemSize);
|
|
221
|
+
|
|
222
|
+
t.ok(equalUint8Arrays(getBack2, largeArray2));
|
|
223
|
+
t.ok(getBack2.length === singleItemSize);
|
|
224
|
+
|
|
225
|
+
t.ok(equalUint8Arrays(getBack3, largeArray3));
|
|
226
|
+
t.ok(getBack3.length === singleItemSize);
|
|
227
|
+
|
|
228
|
+
console.log(endlessVector.length);
|
|
229
|
+
console.log(endlessVector.binaryLength);
|
|
230
|
+
t.ok(endlessVector.length === 6);
|
|
231
|
+
t.ok(endlessVector.binaryLength === 3 + 30*1024 + 120*1024 + 3*singleItemSize);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
test('test concat functionality', async t => {
|
|
236
|
+
// Create a second EndlessVector
|
|
237
|
+
const endlessVector2 = await EndlessVector.create({
|
|
238
|
+
suiClient: suiMaster.client, // instance of Sui SDK SuiClient
|
|
239
|
+
packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
|
|
240
|
+
signAndExecuteTransaction: signAndExecuteTransaction,
|
|
241
|
+
});
|
|
242
|
+
t.ok(endlessVector2);
|
|
243
|
+
t.ok(endlessVector2.id);
|
|
244
|
+
|
|
245
|
+
// Push some data to the second vector
|
|
246
|
+
const data1 = new Uint8Array([10, 20, 30]);
|
|
247
|
+
const data2 = new Uint8Array([40, 50, 60]);
|
|
248
|
+
await endlessVector2.push(data1);
|
|
249
|
+
await endlessVector2.push(data2);
|
|
250
|
+
|
|
251
|
+
await endlessVector2.initialize();
|
|
252
|
+
t.ok(endlessVector2.length === 2);
|
|
253
|
+
t.ok(endlessVector2.binaryLength === 6);
|
|
254
|
+
|
|
255
|
+
// Get lengths before concat
|
|
256
|
+
await endlessVector.initialize();
|
|
257
|
+
const v1LengthBefore = endlessVector.length;
|
|
258
|
+
const v1BinaryLengthBefore = endlessVector.binaryLength;
|
|
259
|
+
const v2Length = endlessVector2.length;
|
|
260
|
+
const v2BinaryLength = endlessVector2.binaryLength;
|
|
261
|
+
|
|
262
|
+
console.log('v1 before concat:', { length: v1LengthBefore, binaryLength: v1BinaryLengthBefore });
|
|
263
|
+
console.log('v2 before concat:', { length: v2Length, binaryLength: v2BinaryLength });
|
|
264
|
+
|
|
265
|
+
// Concat the second vector into the first
|
|
266
|
+
// Both of these work - passing ID string or EndlessVector instance:
|
|
267
|
+
// await endlessVector.concat(endlessVector2.id);
|
|
268
|
+
await endlessVector.concat(endlessVector2); // Passing EndlessVector instance
|
|
269
|
+
|
|
270
|
+
// Verify the first vector now contains both vectors' data
|
|
271
|
+
await endlessVector.initialize();
|
|
272
|
+
console.log('v1 after concat:', { length: endlessVector.length, binaryLength: endlessVector.binaryLength });
|
|
273
|
+
|
|
274
|
+
t.ok(endlessVector.length === v1LengthBefore + v2Length, 'length should be sum of both vectors');
|
|
275
|
+
t.ok(endlessVector.binaryLength === v1BinaryLengthBefore + v2BinaryLength, 'binary length should be sum of both vectors');
|
|
276
|
+
|
|
277
|
+
// Verify we can access the concatenated data
|
|
278
|
+
const getBackFromV2_1 = await endlessVector.at(v1LengthBefore);
|
|
279
|
+
const getBackFromV2_2 = await endlessVector.at(v1LengthBefore + 1);
|
|
280
|
+
|
|
281
|
+
t.ok(equalUint8Arrays(getBackFromV2_1, data1), 'first item from v2 should be accessible in v1');
|
|
282
|
+
t.ok(equalUint8Arrays(getBackFromV2_2, data2), 'second item from v2 should be accessible in v1');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
test('test concat with array (append)', async t => {
|
|
287
|
+
// Create three new EndlessVectors
|
|
288
|
+
const endlessVector3 = await EndlessVector.create({
|
|
289
|
+
suiClient: suiMaster.client, // instance of Sui SDK SuiClient
|
|
290
|
+
packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
|
|
291
|
+
signAndExecuteTransaction: signAndExecuteTransaction,
|
|
292
|
+
});
|
|
293
|
+
const endlessVector4 = await EndlessVector.create({
|
|
294
|
+
suiClient: suiMaster.client, // instance of Sui SDK SuiClient
|
|
295
|
+
packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
|
|
296
|
+
signAndExecuteTransaction: signAndExecuteTransaction,
|
|
297
|
+
});
|
|
298
|
+
const endlessVector5 = await EndlessVector.create({
|
|
299
|
+
suiClient: suiMaster.client, // instance of Sui SDK SuiClient
|
|
300
|
+
packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
|
|
301
|
+
signAndExecuteTransaction: signAndExecuteTransaction,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Push data to all vectors
|
|
305
|
+
const data3 = new Uint8Array([100, 101, 102]);
|
|
306
|
+
const data4 = new Uint8Array([200, 201, 202]);
|
|
307
|
+
const data5 = new Uint8Array([300, 301, 302]);
|
|
308
|
+
|
|
309
|
+
await endlessVector3.push(data3);
|
|
310
|
+
await endlessVector4.push(data4);
|
|
311
|
+
await endlessVector5.push(data5);
|
|
312
|
+
|
|
313
|
+
await endlessVector3.initialize();
|
|
314
|
+
await endlessVector4.initialize();
|
|
315
|
+
await endlessVector5.initialize();
|
|
316
|
+
|
|
317
|
+
const v3Length = endlessVector3.length;
|
|
318
|
+
const v3BinaryLength = endlessVector3.binaryLength;
|
|
319
|
+
const v4Length = endlessVector4.length;
|
|
320
|
+
const v4BinaryLength = endlessVector4.binaryLength;
|
|
321
|
+
const v5Length = endlessVector5.length;
|
|
322
|
+
const v5BinaryLength = endlessVector5.binaryLength;
|
|
323
|
+
|
|
324
|
+
console.log('Before append:', { v3Length, v4Length, v5Length });
|
|
325
|
+
|
|
326
|
+
// Concat with array (append) - passing array of EndlessVector instances
|
|
327
|
+
await endlessVector3.concat([endlessVector4, endlessVector5]);
|
|
328
|
+
|
|
329
|
+
await endlessVector3.initialize();
|
|
330
|
+
console.log('After append:', { length: endlessVector3.length, binaryLength: endlessVector3.binaryLength });
|
|
331
|
+
|
|
332
|
+
t.ok(endlessVector3.length === v3Length + v4Length + v5Length, 'length should be sum of all vectors');
|
|
333
|
+
t.ok(endlessVector3.binaryLength === v3BinaryLength + v4BinaryLength + v5BinaryLength, 'binary length should be sum of all vectors');
|
|
334
|
+
|
|
335
|
+
// Verify data integrity
|
|
336
|
+
const retrieved3 = await endlessVector3.at(0);
|
|
337
|
+
const retrieved4 = await endlessVector3.at(1);
|
|
338
|
+
const retrieved5 = await endlessVector3.at(2);
|
|
339
|
+
|
|
340
|
+
t.ok(equalUint8Arrays(retrieved3, data3), 'v3 data should match');
|
|
341
|
+
t.ok(equalUint8Arrays(retrieved4, data4), 'v4 data should match');
|
|
342
|
+
t.ok(equalUint8Arrays(retrieved5, data5), 'v5 data should match');
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
test('test concat large EndlessVectors', async t => {
|
|
347
|
+
// Create three new EndlessVectors
|
|
348
|
+
const endlessVectorMain = await EndlessVector.create({
|
|
349
|
+
suiClient: suiMaster.client, // instance of Sui SDK SuiClient
|
|
350
|
+
packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
|
|
351
|
+
signAndExecuteTransaction: signAndExecuteTransaction,
|
|
352
|
+
});
|
|
353
|
+
const endlessVectorSecond = await EndlessVector.create({
|
|
354
|
+
suiClient: suiMaster.client, // instance of Sui SDK SuiClient
|
|
355
|
+
packageId: contract.id, // provide packageId and signAndExecuteTransaction to make EndlessVector writable
|
|
356
|
+
signAndExecuteTransaction: signAndExecuteTransaction,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const data = [
|
|
360
|
+
];
|
|
361
|
+
|
|
362
|
+
for (let i = 0; i < 33; i++) {
|
|
363
|
+
let toMain = randomBytesOfLength(0 + Math.floor(1024 + 50 * 1024 * Math.random())); // 1KB - 50KB
|
|
364
|
+
if (Math.random() < 0.2) {
|
|
365
|
+
toMain = new Uint8Array([]); // 20% chance to push empty array
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
data.push(toMain);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
for (const item of data) {
|
|
372
|
+
await endlessVectorMain.push(item);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
for (let i = 0; i < 33; i++) {
|
|
376
|
+
let toSecond = randomBytesOfLength(0 + Math.floor(1024 + 50 * 1024 * Math.random())); // 1KB - 50KB
|
|
377
|
+
if (Math.random() < 0.2) {
|
|
378
|
+
toSecond = new Uint8Array([]); // 20% chance to push empty array
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
await endlessVectorSecond.push(toSecond);
|
|
382
|
+
data.push(toSecond);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
await endlessVectorMain.concat(endlessVectorSecond);
|
|
386
|
+
|
|
387
|
+
await endlessVectorMain.initialize();
|
|
388
|
+
|
|
389
|
+
t.ok(endlessVectorMain.length === data.length, 'length should be sum of both vectors');
|
|
390
|
+
|
|
391
|
+
// Verify data integrity
|
|
392
|
+
for (let i = 0; i < data.length; i++) {
|
|
393
|
+
const retrieved = await endlessVectorMain.at(i);
|
|
394
|
+
t.ok(equalUint8Arrays(retrieved, data[i]), `data at index ${i} should match`);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
test('test parallel creation, push, and append of 6 vectors', async t => {
|
|
400
|
+
const vectorCount = 6;
|
|
401
|
+
|
|
402
|
+
// Split gas into at least N coins to use them in parallel transactions
|
|
403
|
+
const splitTx = new suiMaster.Transaction();
|
|
404
|
+
for (let i = 0; i < vectorCount; i++) {
|
|
405
|
+
let coin = splitTx.splitCoins(splitTx.gas, [splitTx.pure.u64(BigInt(1000000000))]);
|
|
406
|
+
splitTx.transferObjects([coin], splitTx.pure.address(suiMaster.address));
|
|
407
|
+
}
|
|
408
|
+
await suiMaster.signAndExecuteTransaction({
|
|
409
|
+
transaction: splitTx,
|
|
410
|
+
requestType: 'WaitForLocalExecution',
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// Get gas coins and prepare inputs for tx.setGasPayment(...);
|
|
414
|
+
const gasCoins = await suiMaster.client.getCoins({
|
|
415
|
+
owner: suiMaster.address,
|
|
416
|
+
coinType: '0x2::sui::SUI',
|
|
417
|
+
});
|
|
418
|
+
const gasCoinInputs = gasCoins.data.map((c) => {
|
|
419
|
+
return {
|
|
420
|
+
objectId: c.coinObjectId,
|
|
421
|
+
digest: c.digest,
|
|
422
|
+
version: c.version,
|
|
423
|
+
};
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
t.ok(gasCoinInputs.length >= vectorCount, `should have at least ${vectorCount} gas coins for parallel vector creation.`);
|
|
427
|
+
|
|
428
|
+
// Prepare test data for each vector
|
|
429
|
+
const testData = [];
|
|
430
|
+
for (let i = 0; i < vectorCount; i++) {
|
|
431
|
+
testData.push(randomBytesOfLength(1024));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Create N EndlessVectors in parallel using static create method
|
|
435
|
+
const createPromises = [];
|
|
436
|
+
for (let i = 0; i < vectorCount; i++) {
|
|
437
|
+
createPromises.push(
|
|
438
|
+
EndlessVector.create({
|
|
439
|
+
suiClient: suiMaster.client,
|
|
440
|
+
packageId: contract.id,
|
|
441
|
+
array: testData[i] ? testData[i] : null,
|
|
442
|
+
gasCoin: gasCoinInputs[i],
|
|
443
|
+
signAndExecuteTransaction: async (tx) => {
|
|
444
|
+
console.log(`Creating vector ${i}...`);
|
|
445
|
+
try {
|
|
446
|
+
const results = await suiMaster.signAndExecuteTransaction({
|
|
447
|
+
transaction: tx,
|
|
448
|
+
});
|
|
449
|
+
return results.digest;
|
|
450
|
+
} catch (e) {
|
|
451
|
+
console.error(`Error creating vector ${i}:`, e);
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
})
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const vectors = await Promise.all(createPromises);
|
|
459
|
+
vectors.forEach((v, i) => {
|
|
460
|
+
console.log(`Created vector ${i} with id: ${v.id}`);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
t.ok(vectors.length === vectorCount, `should have created ${vectorCount} vectors`);
|
|
464
|
+
|
|
465
|
+
const ids = {};
|
|
466
|
+
vectors.forEach((v, i) => {
|
|
467
|
+
t.ok(v.id, `vector ${i} should have an id`);
|
|
468
|
+
t.ok(!ids[v.id], `vector id ${v.id} should be unique`);
|
|
469
|
+
ids[v.id] = i;
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
const mainVector = vectors[0];
|
|
473
|
+
// append other vectors to the first one
|
|
474
|
+
const vectorsToAppend = vectors.slice(1);
|
|
475
|
+
await mainVector.concat(vectorsToAppend);
|
|
476
|
+
|
|
477
|
+
// Verify the data after concatenation
|
|
478
|
+
await mainVector.initialize();
|
|
479
|
+
|
|
480
|
+
const expectedLength = vectorCount;
|
|
481
|
+
t.ok(mainVector.length === expectedLength, `mainVector should have ${expectedLength} items, got ${mainVector.length}`);
|
|
482
|
+
|
|
483
|
+
console.log(`Verifying ${expectedLength} items in concatenated vector...`);
|
|
484
|
+
|
|
485
|
+
// Verify each item matches the original testData
|
|
486
|
+
for (let vectorIdx = 0; vectorIdx < vectorCount; vectorIdx++) {
|
|
487
|
+
const expected = testData[vectorIdx];
|
|
488
|
+
const retrieved = await mainVector.at(vectorIdx);
|
|
489
|
+
|
|
490
|
+
t.ok(
|
|
491
|
+
equalUint8Arrays(retrieved, expected),
|
|
492
|
+
`Item ${vectorIdx} (vector ${vectorIdx}, item ${vectorIdx}) should match`
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
test('stops local test node', async t => {
|
|
498
|
+
await SuiLocalTestValidator.stop();
|
|
499
|
+
});
|
package/test/fixture.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { SuiMaster, SuiLocalTestValidator } from 'suidouble';
|
|
4
|
+
import LocalnodeWalrusTestState from '../../../seal_walrus_localnet/includes/LocalnodeWalrusTestState.js';
|
|
5
|
+
import LocalnodeWalrusTestServer from '../../../seal_walrus_localnet/includes/LocalnodeWalrusTestServer.js';
|
|
6
|
+
import LocalnodeSealTestState from '../../../seal_walrus_localnet/includes/LocalnodeSealTestState.js';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const MOVE_PKG_PATH = path.join(__dirname, '../../move');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Process-scoped cache — shared across all test files in the same vitest worker
|
|
13
|
+
* (requires singleFork: true + isolate: false in vitest.config.js).
|
|
14
|
+
*
|
|
15
|
+
* @type {Promise<{ suiMaster, walrusState, walrusServer, walrusClient, packageId }> | null}
|
|
16
|
+
*/
|
|
17
|
+
let cached = null;
|
|
18
|
+
let exitHookInstalled = false;
|
|
19
|
+
|
|
20
|
+
function installExitHook() {
|
|
21
|
+
if (exitHookInstalled) return;
|
|
22
|
+
exitHookInstalled = true;
|
|
23
|
+
process.once('beforeExit', async () => {
|
|
24
|
+
try {
|
|
25
|
+
const { walrusServer } = await cached;
|
|
26
|
+
await walrusServer?.stop();
|
|
27
|
+
} catch { /* best-effort */ }
|
|
28
|
+
try { await SuiLocalTestValidator.stop(); } catch { /* best-effort */ }
|
|
29
|
+
});
|
|
30
|
+
for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
31
|
+
process.once(sig, () => {
|
|
32
|
+
(cached ? cached.then(({ walrusServer }) => walrusServer?.stop()).catch(() => {}) : Promise.resolve())
|
|
33
|
+
.then(() => SuiLocalTestValidator.stop())
|
|
34
|
+
.finally(() => process.exit(0));
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Boot the local validator, deploy the endless_vector package, and start a
|
|
41
|
+
* WalrusTestServer. Idempotent — the first call does the work, subsequent
|
|
42
|
+
* calls return the same handles.
|
|
43
|
+
*
|
|
44
|
+
* @param {{ debug?: boolean }} [opts]
|
|
45
|
+
* @returns {Promise<{ suiMaster: import('suidouble').SuiMaster, walrusState: import('../../../../seal_walrus_localnet/includes/LocalnodeWalrusTestState.js').default, walrusServer: import('../../../../seal_walrus_localnet/includes/LocalnodeWalrusTestServer.js').default, walrusClient: import('@mysten/walrus').WalrusClient, packageId: string }>}
|
|
46
|
+
*/
|
|
47
|
+
export function setupEndlessVectorLocalnet(opts = {}) {
|
|
48
|
+
if (cached) return cached;
|
|
49
|
+
installExitHook();
|
|
50
|
+
cached = (async () => {
|
|
51
|
+
const debug = !!opts.debug;
|
|
52
|
+
const validator = await SuiLocalTestValidator.launch({ debug });
|
|
53
|
+
if (!validator.active) throw new Error('local test validator failed to start');
|
|
54
|
+
|
|
55
|
+
const suiMaster = new SuiMaster({ client: validator, as: 'ev_tester', debug });
|
|
56
|
+
await suiMaster.initialize();
|
|
57
|
+
await suiMaster.requestSuiFromFaucet();
|
|
58
|
+
await suiMaster.requestSuiFromFaucet();
|
|
59
|
+
await suiMaster.requestSuiFromFaucet();
|
|
60
|
+
|
|
61
|
+
const walrusState = new LocalnodeWalrusTestState({
|
|
62
|
+
suiMaster,
|
|
63
|
+
packagePath: MOVE_PKG_PATH,
|
|
64
|
+
epochDuration: 30_000,
|
|
65
|
+
});
|
|
66
|
+
await walrusState.deploy();
|
|
67
|
+
|
|
68
|
+
const packageId = walrusState.walrusPackageId;
|
|
69
|
+
|
|
70
|
+
const walrusServer = new LocalnodeWalrusTestServer({ state: walrusState });
|
|
71
|
+
await walrusServer.start();
|
|
72
|
+
|
|
73
|
+
const walrusClient = await walrusServer.getWalrusClient({ suiMaster });
|
|
74
|
+
|
|
75
|
+
// Deploy Seal mock — lets sealed-vector tests share the same validator/walrus stack.
|
|
76
|
+
// Cheap if unused; sealed tests pick it up via `await sealState.getSealClient(...)`.
|
|
77
|
+
const sealState = new LocalnodeSealTestState({ suiMaster });
|
|
78
|
+
await sealState.deploy({ name: 'ev-seal', url: walrusServer.url });
|
|
79
|
+
walrusServer.seal = sealState;
|
|
80
|
+
const sealClient = await walrusServer.getSealClient({ suiMaster });
|
|
81
|
+
|
|
82
|
+
return { suiMaster, walrusState, walrusServer, walrusClient, sealState, sealClient, packageId };
|
|
83
|
+
})().catch((err) => {
|
|
84
|
+
cached = null;
|
|
85
|
+
throw err;
|
|
86
|
+
});
|
|
87
|
+
return cached;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** No-op per-suite — cleanup is handled by the beforeExit hook. */
|
|
91
|
+
export async function teardownEndlessVectorLocalnet() {
|
|
92
|
+
// intentionally empty
|
|
93
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import EndlessVectorItem from '../EndlessVectorItem.js';
|
|
3
|
+
|
|
4
|
+
describe('EndlessVectorItem.fromGrpcJson browser decoding', () => {
|
|
5
|
+
it('decodes base64 gRPC JSON without Node Buffer', async () => {
|
|
6
|
+
const originalBuffer = globalThis.Buffer;
|
|
7
|
+
try {
|
|
8
|
+
globalThis.Buffer = undefined;
|
|
9
|
+
|
|
10
|
+
const item = EndlessVectorItem.fromGrpcJson({
|
|
11
|
+
bytes: 'AQID',
|
|
12
|
+
meta: 'BAU=',
|
|
13
|
+
blob: null,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect([...(await item.bytes())]).toEqual([1, 2, 3]);
|
|
17
|
+
expect([...item.meta]).toEqual([4, 5]);
|
|
18
|
+
} finally {
|
|
19
|
+
globalThis.Buffer = originalBuffer;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
package/test/helpers.js
CHANGED
package/test/helpers.txt
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
function equalUint8Arrays(a, b) {
|
|
4
|
+
if (a.length !== b.length) return false;
|
|
5
|
+
for (let i = 0; i < a.length; i++) {
|
|
6
|
+
if (a[i] !== b[i]) return false;
|
|
7
|
+
}
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function formatBytes(bytes, decimals = 2) {
|
|
12
|
+
if (bytes === 0) return '0 Bytes';
|
|
13
|
+
|
|
14
|
+
const k = 1024;
|
|
15
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
16
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
|
17
|
+
|
|
18
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
19
|
+
const size = parseFloat((bytes / Math.pow(k, i)).toFixed(dm));
|
|
20
|
+
|
|
21
|
+
return `${size} ${sizes[i]}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function randomBytesOfLength(length) {
|
|
25
|
+
return new Uint8Array(crypto.randomBytes(length));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
equalUint8Arrays,
|
|
30
|
+
formatBytes,
|
|
31
|
+
randomBytesOfLength
|
|
32
|
+
};
|