@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
package/test/base.test.js
CHANGED
|
@@ -1,499 +1,460 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Integration test for EndlessVector SDK methods (create, push, at, concat)
|
|
3
|
+
* using the seal_walrus_localnet infrastructure and suidouble v2 / gRPC client.
|
|
4
|
+
*
|
|
5
|
+
* Run: pnpm test:base (or pnpm test to run all suites)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { beforeAll, afterAll, describe, it, expect } from 'vitest';
|
|
9
9
|
import { EndlessVector } from '../index.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
-
const __dirname = path.dirname(__filename);
|
|
10
|
+
import { equalUint8Arrays, randomBytesOfLength } from './helpers.js';
|
|
11
|
+
import { setupEndlessVectorLocalnet, teardownEndlessVectorLocalnet } from './fixture.js';
|
|
14
12
|
|
|
15
|
-
const
|
|
13
|
+
const TX_TIMEOUT = 60_000;
|
|
16
14
|
|
|
17
|
-
let
|
|
15
|
+
let suiMaster;
|
|
16
|
+
let walrusServer;
|
|
17
|
+
let walrusClient;
|
|
18
|
+
let packageId;
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
let suiMaster = null;
|
|
21
|
-
let signAndExecuteTransaction = null;
|
|
20
|
+
// ─── setup ────────────────────────────────────────────────────────────────────
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/** @type {?EndlessVector} */
|
|
28
|
-
let endlessVector = null; // EndlessVector instance
|
|
22
|
+
beforeAll(async () => {
|
|
23
|
+
({ suiMaster, walrusServer, walrusClient, packageId } = await setupEndlessVectorLocalnet());
|
|
24
|
+
console.log('package id:', packageId);
|
|
25
|
+
});
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
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);
|
|
27
|
+
afterAll(async () => {
|
|
28
|
+
await teardownEndlessVectorLocalnet();
|
|
36
29
|
});
|
|
37
30
|
|
|
38
|
-
|
|
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'
|
|
31
|
+
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
43
32
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
33
|
+
function makeSignAndExecute() {
|
|
34
|
+
return async (tx) => {
|
|
35
|
+
const result = await suiMaster.signAndExecuteTransaction({
|
|
47
36
|
transaction: tx,
|
|
37
|
+
// requestType: 'WaitForLocalExecution',
|
|
48
38
|
});
|
|
49
|
-
return
|
|
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
|
-
});
|
|
39
|
+
return result.digest;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
64
42
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
43
|
+
async function createEV(array = null) {
|
|
44
|
+
return EndlessVector.create({
|
|
45
|
+
suiClient: suiMaster.client,
|
|
46
|
+
packageId,
|
|
47
|
+
walrusClient,
|
|
48
|
+
aggregatorUrl: walrusServer?.url,
|
|
49
|
+
senderAddress: suiMaster.address,
|
|
50
|
+
signAndExecuteTransaction: makeSignAndExecute(),
|
|
51
|
+
...(array ? { array } : {}),
|
|
68
52
|
});
|
|
69
|
-
|
|
70
|
-
await contract.publish();
|
|
71
|
-
});
|
|
72
|
-
|
|
53
|
+
}
|
|
73
54
|
|
|
74
|
-
|
|
75
|
-
|
|
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({
|
|
55
|
+
function makeEV(id) {
|
|
56
|
+
return new EndlessVector({
|
|
109
57
|
suiClient: suiMaster.client,
|
|
110
|
-
id
|
|
58
|
+
id,
|
|
59
|
+
packageId,
|
|
60
|
+
signAndExecuteTransaction: makeSignAndExecute(),
|
|
111
61
|
});
|
|
112
|
-
|
|
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
|
-
});
|
|
62
|
+
}
|
|
118
63
|
|
|
64
|
+
// ─── tests ────────────────────────────────────────────────────────────────────
|
|
119
65
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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,
|
|
66
|
+
describe('deployment', () => {
|
|
67
|
+
it('publishes the combined package', () => {
|
|
68
|
+
expect(packageId).toMatch(/^0x[0-9a-f]+$/);
|
|
132
69
|
});
|
|
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
70
|
});
|
|
145
71
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
72
|
+
describe('raw create transaction', () => {
|
|
73
|
+
it('creates EndlessVector with 120KB payload via raw tx and reads it back', async () => {
|
|
74
|
+
const arr = randomBytesOfLength(120 * 1024);
|
|
156
75
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
76
|
+
const tx = new suiMaster.Transaction();
|
|
77
|
+
const vectorTxInput = await EndlessVector.getCreateTransactionAndReturnVectorInput(
|
|
78
|
+
{ packageId },
|
|
79
|
+
arr,
|
|
80
|
+
tx,
|
|
81
|
+
);
|
|
82
|
+
tx.transferObjects([vectorTxInput], tx.pure.address(suiMaster.address));
|
|
161
83
|
|
|
162
|
-
|
|
84
|
+
const signAndExecute = makeSignAndExecute();
|
|
85
|
+
const digest = await signAndExecute(tx);
|
|
163
86
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
87
|
+
const txResult = await suiMaster.client.waitForTransaction({
|
|
88
|
+
digest,
|
|
89
|
+
include: { effects: true, objectTypes: true },
|
|
90
|
+
});
|
|
91
|
+
const txData = txResult.Transaction ?? txResult.FailedTransaction;
|
|
92
|
+
expect(txData?.status?.success).toBe(true);
|
|
167
93
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
94
|
+
const objectTypes = txData.objectTypes ?? {};
|
|
95
|
+
const created = txData.effects?.changedObjects?.find(
|
|
96
|
+
c => c.idOperation === 'Created' &&
|
|
97
|
+
(objectTypes[c.objectId] ?? '').includes('EndlessWalrusVector'),
|
|
98
|
+
);
|
|
99
|
+
expect(created).toBeDefined();
|
|
171
100
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
t.ok(getBack.length === 30*1024);
|
|
101
|
+
const loadBack = makeEV(created.objectId);
|
|
102
|
+
await loadBack.initialize();
|
|
175
103
|
|
|
176
|
-
|
|
177
|
-
|
|
104
|
+
expect(loadBack.length).toBe(1);
|
|
105
|
+
const back = await loadBack.at(0);
|
|
106
|
+
expect(equalUint8Arrays(back, arr)).toBe(true);
|
|
107
|
+
}, TX_TIMEOUT);
|
|
178
108
|
});
|
|
179
109
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
110
|
+
describe('create with array of chunks', () => {
|
|
111
|
+
it('creates EndlessVector with 3 pre-loaded chunks and reads each back', async () => {
|
|
112
|
+
const data = [
|
|
113
|
+
randomBytesOfLength(1 * 1024),
|
|
114
|
+
randomBytesOfLength(2 * 1024),
|
|
115
|
+
randomBytesOfLength(3 * 1024),
|
|
116
|
+
];
|
|
187
117
|
|
|
188
|
-
|
|
189
|
-
t.ok(endlessVector.binaryLength === 3 + 30*1024 + 120*1024);
|
|
190
|
-
});
|
|
118
|
+
const ev = await createEV(data);
|
|
191
119
|
|
|
120
|
+
expect(equalUint8Arrays(await ev.at(0), data[0])).toBe(true);
|
|
121
|
+
expect(equalUint8Arrays(await ev.at(1), data[1])).toBe(true);
|
|
122
|
+
expect(equalUint8Arrays(await ev.at(2), data[2])).toBe(true);
|
|
192
123
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
await t.rejects(endlessVector.push(largeArray), Error, 'expected an Error to be thrown');
|
|
124
|
+
expect(ev.length).toBe(3);
|
|
125
|
+
expect(ev.binaryLength).toBe(6 * 1024);
|
|
126
|
+
}, TX_TIMEOUT);
|
|
197
127
|
});
|
|
198
128
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
129
|
+
describe('push and at', () => {
|
|
130
|
+
let ev;
|
|
131
|
+
|
|
132
|
+
it('creates empty EndlessVector', async () => {
|
|
133
|
+
ev = await createEV();
|
|
134
|
+
expect(ev).toBeDefined();
|
|
135
|
+
expect(ev.id).toMatch(/^0x[0-9a-f]+$/);
|
|
136
|
+
expect(ev.isWritable).toBe(true);
|
|
137
|
+
}, TX_TIMEOUT);
|
|
138
|
+
|
|
139
|
+
it('pushes small Uint8Array and reads it back', async () => {
|
|
140
|
+
await ev.push(new Uint8Array([1, 2, 3]));
|
|
141
|
+
const back = await ev.at(0);
|
|
142
|
+
expect(equalUint8Arrays(back, new Uint8Array([1, 2, 3]))).toBe(true);
|
|
143
|
+
await ev.initialize();
|
|
144
|
+
expect(ev.length).toBe(1);
|
|
145
|
+
expect(ev.binaryLength).toBe(3);
|
|
146
|
+
}, TX_TIMEOUT);
|
|
147
|
+
|
|
148
|
+
it('pushes 30KB array', async () => {
|
|
149
|
+
const large = randomBytesOfLength(30 * 1024);
|
|
150
|
+
await ev.push(large);
|
|
151
|
+
const back = await ev.at(1);
|
|
152
|
+
expect(equalUint8Arrays(back, large)).toBe(true);
|
|
153
|
+
expect(ev.length).toBe(2);
|
|
154
|
+
expect(ev.binaryLength).toBe(3 + 30 * 1024);
|
|
155
|
+
}, TX_TIMEOUT);
|
|
156
|
+
|
|
157
|
+
it('pushes 120KB array', async () => {
|
|
158
|
+
const large = randomBytesOfLength(120 * 1024);
|
|
159
|
+
await ev.push(large);
|
|
160
|
+
const back = await ev.at(2);
|
|
161
|
+
expect(equalUint8Arrays(back, large)).toBe(true);
|
|
162
|
+
expect(ev.length).toBe(3);
|
|
163
|
+
expect(ev.binaryLength).toBe(3 + 30 * 1024 + 120 * 1024);
|
|
164
|
+
}, TX_TIMEOUT);
|
|
165
|
+
|
|
166
|
+
it('rejects arrays larger than 120KB when no walrus is configured', async () => {
|
|
167
|
+
const evNoWalrus = new EndlessVector({
|
|
168
|
+
suiClient: suiMaster.client,
|
|
169
|
+
id: ev.id,
|
|
170
|
+
packageId,
|
|
171
|
+
signAndExecuteTransaction: makeSignAndExecute(),
|
|
212
172
|
});
|
|
213
|
-
|
|
214
|
-
|
|
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,
|
|
173
|
+
const tooLarge = randomBytesOfLength(120 * 1024 + 1);
|
|
174
|
+
await expect(evNoWalrus.push(tooLarge)).rejects.toThrow();
|
|
302
175
|
});
|
|
303
176
|
|
|
304
|
-
|
|
305
|
-
const data3 = new Uint8Array([100, 101, 102]);
|
|
306
|
-
const data4 = new Uint8Array([200, 201, 202]);
|
|
307
|
-
const data5 = new Uint8Array([300, 301, 302]);
|
|
177
|
+
let walrusLargeData;
|
|
308
178
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
179
|
+
it('auto-routes arrays > 120 KB to walrus when configured', async () => {
|
|
180
|
+
walrusLargeData = randomBytesOfLength(200 * 1024);
|
|
181
|
+
const ok = await ev.push(walrusLargeData);
|
|
182
|
+
expect(ok).toBe(true);
|
|
312
183
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
184
|
+
await ev.initialize();
|
|
185
|
+
expect(ev.length).toBe(4);
|
|
186
|
+
}, TX_TIMEOUT);
|
|
316
187
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const v5Length = endlessVector5.length;
|
|
322
|
-
const v5BinaryLength = endlessVector5.binaryLength;
|
|
188
|
+
it('reads back the walrus-stored item via at()', async () => {
|
|
189
|
+
const back = await ev.at(3);
|
|
190
|
+
expect(equalUint8Arrays(back, walrusLargeData)).toBe(true);
|
|
191
|
+
}, TX_TIMEOUT);
|
|
323
192
|
|
|
324
|
-
|
|
193
|
+
it('pushes 3 × 40KB in a single transaction block', async () => {
|
|
194
|
+
const size = 40 * 1024;
|
|
195
|
+
const a1 = randomBytesOfLength(size);
|
|
196
|
+
const a2 = randomBytesOfLength(size);
|
|
197
|
+
const a3 = randomBytesOfLength(size);
|
|
325
198
|
|
|
326
|
-
|
|
327
|
-
|
|
199
|
+
const tx = new suiMaster.Transaction();
|
|
200
|
+
ev.getPushTransaction(a1, tx);
|
|
201
|
+
ev.getPushTransaction(a2, tx);
|
|
202
|
+
ev.getPushTransaction(a3, tx);
|
|
328
203
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
t.ok(endlessVector3.binaryLength === v3BinaryLength + v4BinaryLength + v5BinaryLength, 'binary length should be sum of all vectors');
|
|
204
|
+
await suiMaster.signAndExecuteTransaction({
|
|
205
|
+
transaction: tx,
|
|
206
|
+
requestType: 'WaitForLocalExecution',
|
|
207
|
+
});
|
|
334
208
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const retrieved4 = await endlessVector3.at(1);
|
|
338
|
-
const retrieved5 = await endlessVector3.at(2);
|
|
209
|
+
await ev.reInitialize();
|
|
210
|
+
await ev.initialize();
|
|
339
211
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
212
|
+
expect(equalUint8Arrays(await ev.at(4), a1)).toBe(true);
|
|
213
|
+
expect(equalUint8Arrays(await ev.at(5), a2)).toBe(true);
|
|
214
|
+
expect(equalUint8Arrays(await ev.at(6), a3)).toBe(true);
|
|
215
|
+
expect(ev.length).toBe(7);
|
|
216
|
+
expect(ev.binaryLength).toBe(3 + 30 * 1024 + 120 * 1024 + 200 * 1024 + 3 * size);
|
|
217
|
+
}, TX_TIMEOUT);
|
|
343
218
|
});
|
|
344
219
|
|
|
220
|
+
describe('concat', () => {
|
|
221
|
+
it('appends a second vector into the first and verifies data', async () => {
|
|
222
|
+
const ev1 = await createEV();
|
|
223
|
+
const ev2 = await createEV();
|
|
345
224
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
});
|
|
225
|
+
const d1 = new Uint8Array([10, 20, 30]);
|
|
226
|
+
const d2 = new Uint8Array([40, 50, 60]);
|
|
227
|
+
await ev2.push(d1);
|
|
228
|
+
await ev2.push(d2);
|
|
358
229
|
|
|
359
|
-
|
|
360
|
-
|
|
230
|
+
await ev2.initialize();
|
|
231
|
+
expect(ev2.length).toBe(2);
|
|
361
232
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
toMain = new Uint8Array([]); // 20% chance to push empty array
|
|
366
|
-
}
|
|
233
|
+
await ev1.initialize();
|
|
234
|
+
const len1Before = ev1.length;
|
|
235
|
+
const binLen1Before = ev1.binaryLength;
|
|
367
236
|
|
|
368
|
-
|
|
369
|
-
|
|
237
|
+
await ev1.concat(ev2);
|
|
238
|
+
await ev1.initialize();
|
|
370
239
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
240
|
+
expect(ev1.length).toBe(len1Before + 2);
|
|
241
|
+
expect(ev1.binaryLength).toBe(binLen1Before + 6);
|
|
374
242
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
toSecond = new Uint8Array([]); // 20% chance to push empty array
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
await endlessVectorSecond.push(toSecond);
|
|
382
|
-
data.push(toSecond);
|
|
383
|
-
}
|
|
243
|
+
expect(equalUint8Arrays(await ev1.at(len1Before), d1)).toBe(true);
|
|
244
|
+
expect(equalUint8Arrays(await ev1.at(len1Before + 1), d2)).toBe(true);
|
|
245
|
+
}, TX_TIMEOUT);
|
|
384
246
|
|
|
385
|
-
|
|
247
|
+
it('appends array of vectors via concat([...])', async () => {
|
|
248
|
+
const ev3 = await createEV();
|
|
249
|
+
const ev4 = await createEV();
|
|
250
|
+
const ev5 = await createEV();
|
|
386
251
|
|
|
387
|
-
|
|
252
|
+
const d3 = new Uint8Array([100, 101, 102]);
|
|
253
|
+
const d4 = new Uint8Array([200, 201, 202]);
|
|
254
|
+
const d5 = new Uint8Array([44, 55, 66]);
|
|
388
255
|
|
|
389
|
-
|
|
256
|
+
await ev3.push(d3);
|
|
257
|
+
await ev4.push(d4);
|
|
258
|
+
await ev5.push(d5);
|
|
390
259
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
t.ok(equalUint8Arrays(retrieved, data[i]), `data at index ${i} should match`);
|
|
395
|
-
}
|
|
396
|
-
});
|
|
260
|
+
await ev3.initialize();
|
|
261
|
+
await ev4.initialize();
|
|
262
|
+
await ev5.initialize();
|
|
397
263
|
|
|
264
|
+
const len3 = ev3.length;
|
|
265
|
+
const bin3 = ev3.binaryLength;
|
|
398
266
|
|
|
399
|
-
|
|
400
|
-
|
|
267
|
+
await ev3.concat([ev4, ev5]);
|
|
268
|
+
await ev3.initialize();
|
|
401
269
|
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
});
|
|
270
|
+
expect(ev3.length).toBe(len3 + 2);
|
|
271
|
+
expect(ev3.binaryLength).toBe(bin3 + 6);
|
|
412
272
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
}
|
|
273
|
+
expect(equalUint8Arrays(await ev3.at(0), d3)).toBe(true);
|
|
274
|
+
expect(equalUint8Arrays(await ev3.at(1), d4)).toBe(true);
|
|
275
|
+
expect(equalUint8Arrays(await ev3.at(2), d5)).toBe(true);
|
|
276
|
+
}, TX_TIMEOUT);
|
|
277
|
+
});
|
|
457
278
|
|
|
458
|
-
|
|
459
|
-
vectors
|
|
460
|
-
|
|
461
|
-
});
|
|
279
|
+
describe('parallel creation and append', () => {
|
|
280
|
+
it('creates 6 vectors in parallel and concatenates them', async () => {
|
|
281
|
+
const vectorCount = 6;
|
|
462
282
|
|
|
463
|
-
|
|
283
|
+
const splitTx = new suiMaster.Transaction();
|
|
284
|
+
for (let i = 0; i < vectorCount; i++) {
|
|
285
|
+
let coin = splitTx.splitCoins(splitTx.gas, [splitTx.pure.u64(BigInt(1000000000))]);
|
|
286
|
+
splitTx.transferObjects([coin], splitTx.pure.address(suiMaster.address));
|
|
287
|
+
}
|
|
288
|
+
await suiMaster.signAndExecuteTransaction({
|
|
289
|
+
transaction: splitTx,
|
|
290
|
+
requestType: 'WaitForLocalExecution',
|
|
291
|
+
});
|
|
464
292
|
|
|
465
|
-
|
|
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
|
-
});
|
|
293
|
+
const testData = Array.from({ length: vectorCount }, () => randomBytesOfLength(1024));
|
|
471
294
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
295
|
+
const gasCoinsResponse = await suiMaster.client.listCoins({
|
|
296
|
+
owner: suiMaster.address,
|
|
297
|
+
coinType: '0x2::sui::SUI',
|
|
298
|
+
});
|
|
299
|
+
const gasCoinInputs = gasCoinsResponse.objects.map((c) => ({
|
|
300
|
+
objectId: c.objectId,
|
|
301
|
+
digest: c.digest,
|
|
302
|
+
version: c.version,
|
|
303
|
+
}));
|
|
304
|
+
|
|
305
|
+
const createPromises = [];
|
|
306
|
+
for (let i = 0; i < vectorCount; i++) {
|
|
307
|
+
createPromises.push(
|
|
308
|
+
EndlessVector.create({
|
|
309
|
+
suiClient: suiMaster.client,
|
|
310
|
+
packageId,
|
|
311
|
+
array: testData[i] ?? null,
|
|
312
|
+
gasCoin: gasCoinInputs[i],
|
|
313
|
+
signAndExecuteTransaction: makeSignAndExecute(),
|
|
314
|
+
})
|
|
315
|
+
);
|
|
316
|
+
}
|
|
476
317
|
|
|
477
|
-
|
|
478
|
-
await mainVector.initialize();
|
|
318
|
+
const vectors = await Promise.all(createPromises);
|
|
479
319
|
|
|
480
|
-
|
|
481
|
-
|
|
320
|
+
expect(vectors).toHaveLength(vectorCount);
|
|
321
|
+
vectors.forEach((v, i) => {
|
|
322
|
+
expect(v.id).toMatch(/^0x[0-9a-f]+$/);
|
|
323
|
+
console.log(`vector ${i}: ${v.id}`);
|
|
324
|
+
});
|
|
482
325
|
|
|
483
|
-
|
|
326
|
+
const main = vectors[0];
|
|
327
|
+
await main.concat(vectors.slice(1));
|
|
328
|
+
await main.initialize();
|
|
484
329
|
|
|
485
|
-
|
|
486
|
-
for (let vectorIdx = 0; vectorIdx < vectorCount; vectorIdx++) {
|
|
487
|
-
const expected = testData[vectorIdx];
|
|
488
|
-
const retrieved = await mainVector.at(vectorIdx);
|
|
330
|
+
expect(main.length).toBe(vectorCount);
|
|
489
331
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
332
|
+
for (let i = 0; i < vectorCount; i++) {
|
|
333
|
+
const back = await main.at(i);
|
|
334
|
+
expect(equalUint8Arrays(back, testData[i])).toBe(true);
|
|
335
|
+
}
|
|
336
|
+
}, 300_000);
|
|
495
337
|
});
|
|
496
338
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
339
|
+
describe('archive', () => {
|
|
340
|
+
it('archives history and data remains readable', async () => {
|
|
341
|
+
const data = [
|
|
342
|
+
randomBytesOfLength(1 * 1024),
|
|
343
|
+
randomBytesOfLength(2 * 1024),
|
|
344
|
+
randomBytesOfLength(3 * 1024),
|
|
345
|
+
];
|
|
346
|
+
|
|
347
|
+
const ev = await createEV(data);
|
|
348
|
+
await ev.initialize();
|
|
349
|
+
|
|
350
|
+
const lengthBefore = ev.length;
|
|
351
|
+
const binLengthBefore = ev.binaryLength;
|
|
352
|
+
expect(lengthBefore).toBe(3);
|
|
353
|
+
|
|
354
|
+
const extra = randomBytesOfLength(120 * 1024);
|
|
355
|
+
await ev.push(extra);
|
|
356
|
+
|
|
357
|
+
await ev.archive();
|
|
358
|
+
await ev.initialize();
|
|
359
|
+
|
|
360
|
+
expect(ev.length).toBe(4);
|
|
361
|
+
expect(ev.binaryLength).toBe(binLengthBefore + 120 * 1024);
|
|
362
|
+
expect(ev.archiveItemsCount).toBe(1);
|
|
363
|
+
expect(ev.archivedAtLength).toBe(4);
|
|
364
|
+
expect(ev.historyItemsCount).toBe(0);
|
|
365
|
+
expect(ev.burnedArchiveCount).toBe(0);
|
|
366
|
+
|
|
367
|
+
expect(equalUint8Arrays(await ev.at(0), data[0])).toBe(true);
|
|
368
|
+
expect(equalUint8Arrays(await ev.at(1), data[1])).toBe(true);
|
|
369
|
+
expect(equalUint8Arrays(await ev.at(2), data[2])).toBe(true);
|
|
370
|
+
expect(equalUint8Arrays(await ev.at(3), extra)).toBe(true);
|
|
371
|
+
|
|
372
|
+
const afterArchive = randomBytesOfLength(4 * 1024);
|
|
373
|
+
await ev.push(afterArchive);
|
|
374
|
+
await ev.initialize();
|
|
375
|
+
|
|
376
|
+
expect(ev.length).toBe(5);
|
|
377
|
+
expect(equalUint8Arrays(await ev.at(4), afterArchive)).toBe(true);
|
|
378
|
+
expect(equalUint8Arrays(await ev.at(0), data[0])).toBe(true);
|
|
379
|
+
expect(equalUint8Arrays(await ev.at(3), extra)).toBe(true);
|
|
380
|
+
|
|
381
|
+
await ev.burnArchive();
|
|
382
|
+
await ev.initialize();
|
|
383
|
+
|
|
384
|
+
expect(ev.burnedArchiveCount).toBe(1);
|
|
385
|
+
expect(ev.archivedFromLength).toBe(4);
|
|
386
|
+
expect(ev.length).toBe(5);
|
|
387
|
+
|
|
388
|
+
expect(equalUint8Arrays(await ev.at(4), afterArchive)).toBe(true);
|
|
389
|
+
|
|
390
|
+
await expect(ev.at(0)).rejects.toThrow();
|
|
391
|
+
await expect(ev.at(1)).rejects.toThrow();
|
|
392
|
+
await expect(ev.at(2)).rejects.toThrow();
|
|
393
|
+
await expect(ev.at(3)).rejects.toThrow();
|
|
394
|
+
}, TX_TIMEOUT);
|
|
395
|
+
|
|
396
|
+
it('multiple archive/burn cycles accumulate archivedFromLength correctly', async () => {
|
|
397
|
+
const ev = await createEV();
|
|
398
|
+
|
|
399
|
+
const batch1 = randomBytesOfLength(120 * 1024);
|
|
400
|
+
await ev.push(batch1);
|
|
401
|
+
await ev.archive();
|
|
402
|
+
await ev.initialize();
|
|
403
|
+
expect(ev.archiveItemsCount).toBe(1);
|
|
404
|
+
expect(ev.archivedAtLength).toBe(1);
|
|
405
|
+
|
|
406
|
+
const batch2 = randomBytesOfLength(120 * 1024);
|
|
407
|
+
await ev.push(batch2);
|
|
408
|
+
await ev.archive();
|
|
409
|
+
await ev.initialize();
|
|
410
|
+
expect(ev.archiveItemsCount).toBe(2);
|
|
411
|
+
expect(ev.archivedAtLength).toBe(2);
|
|
412
|
+
|
|
413
|
+
expect(equalUint8Arrays(await ev.at(0), batch1)).toBe(true);
|
|
414
|
+
expect(equalUint8Arrays(await ev.at(1), batch2)).toBe(true);
|
|
415
|
+
|
|
416
|
+
await ev.burnArchive();
|
|
417
|
+
await ev.initialize();
|
|
418
|
+
expect(ev.burnedArchiveCount).toBe(1);
|
|
419
|
+
expect(ev.archivedFromLength).toBe(1);
|
|
420
|
+
|
|
421
|
+
await expect(ev.at(0)).rejects.toThrow();
|
|
422
|
+
expect(equalUint8Arrays(await ev.at(1), batch2)).toBe(true);
|
|
423
|
+
|
|
424
|
+
await ev.burnArchive();
|
|
425
|
+
await ev.initialize();
|
|
426
|
+
expect(ev.burnedArchiveCount).toBe(2);
|
|
427
|
+
expect(ev.archivedFromLength).toBe(2);
|
|
428
|
+
|
|
429
|
+
await expect(ev.at(1)).rejects.toThrow();
|
|
430
|
+
}, TX_TIMEOUT);
|
|
431
|
+
|
|
432
|
+
it('re-reading vector by ID after archive and burn reflects correct state', async () => {
|
|
433
|
+
const ev = await createEV();
|
|
434
|
+
const item = randomBytesOfLength(120 * 1024);
|
|
435
|
+
await ev.push(item);
|
|
436
|
+
await ev.archive();
|
|
437
|
+
await ev.burnArchive();
|
|
438
|
+
|
|
439
|
+
const fresh = makeEV(ev.id);
|
|
440
|
+
await fresh.initialize();
|
|
441
|
+
|
|
442
|
+
expect(fresh.burnedArchiveCount).toBe(1);
|
|
443
|
+
expect(fresh.archivedFromLength).toBe(1);
|
|
444
|
+
expect(fresh.length).toBe(1);
|
|
445
|
+
await expect(fresh.at(0)).rejects.toThrow();
|
|
446
|
+
}, TX_TIMEOUT);
|
|
447
|
+
|
|
448
|
+
it('concat rejects a source vector that has archived items', async () => {
|
|
449
|
+
const src = await createEV();
|
|
450
|
+
const dst = await createEV();
|
|
451
|
+
|
|
452
|
+
const item = randomBytesOfLength(120 * 1024);
|
|
453
|
+
await src.push(item);
|
|
454
|
+
await src.archive();
|
|
455
|
+
await src.initialize();
|
|
456
|
+
expect(src.archiveItemsCount).toBeGreaterThan(0);
|
|
457
|
+
|
|
458
|
+
await expect(dst.concat(src)).rejects.toThrow();
|
|
459
|
+
}, TX_TIMEOUT);
|
|
460
|
+
});
|