solana-ts 0.1.0
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 +139 -0
- package/dist/engine.d.ts +62 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +302 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/persistence.d.ts +21 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/persistence.js +141 -0
- package/dist/persistence.js.map +1 -0
- package/dist/rpc/dispatcher.d.ts +47 -0
- package/dist/rpc/dispatcher.d.ts.map +1 -0
- package/dist/rpc/dispatcher.js +63 -0
- package/dist/rpc/dispatcher.js.map +1 -0
- package/dist/rpc/methods.d.ts +4 -0
- package/dist/rpc/methods.d.ts.map +1 -0
- package/dist/rpc/methods.js +263 -0
- package/dist/rpc/methods.js.map +1 -0
- package/dist/rpc/subscriptions.d.ts +14 -0
- package/dist/rpc/subscriptions.d.ts.map +1 -0
- package/dist/rpc/subscriptions.js +111 -0
- package/dist/rpc/subscriptions.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +104 -0
- package/dist/server.js.map +1 -0
- package/dist/test.d.ts +2 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +518 -0
- package/dist/test.js.map +1 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +29 -0
- package/dist/types.js.map +1 -0
- package/package.json +31 -0
- package/src/engine.ts +401 -0
- package/src/index.ts +72 -0
- package/src/persistence.ts +189 -0
- package/src/rpc/dispatcher.ts +94 -0
- package/src/rpc/methods.ts +312 -0
- package/src/rpc/subscriptions.ts +158 -0
- package/src/server.ts +127 -0
- package/src/test.ts +674 -0
- package/src/types.ts +68 -0
- package/tsconfig.json +19 -0
package/src/test.ts
ADDED
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Connection,
|
|
3
|
+
Keypair,
|
|
4
|
+
LAMPORTS_PER_SOL,
|
|
5
|
+
SystemProgram,
|
|
6
|
+
Transaction,
|
|
7
|
+
sendAndConfirmTransaction,
|
|
8
|
+
} from "@solana/web3.js";
|
|
9
|
+
import { createServer } from "./server.js";
|
|
10
|
+
import type { ServerConfig } from "./types.js";
|
|
11
|
+
import { setLogLevel } from "./types.js";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import os from "node:os";
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Helpers
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
let nextPort = 18899;
|
|
21
|
+
|
|
22
|
+
function tmpDb(): string {
|
|
23
|
+
return path.join(
|
|
24
|
+
os.tmpdir(),
|
|
25
|
+
`solana-ts-test-${Date.now()}-${Math.random().toString(36).slice(2)}.db`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function makeConfig(overrides: Partial<ServerConfig> = {}): ServerConfig {
|
|
30
|
+
const port = nextPort;
|
|
31
|
+
nextPort += 2; // reserve port and port+1 (WS)
|
|
32
|
+
return {
|
|
33
|
+
host: "127.0.0.1",
|
|
34
|
+
port,
|
|
35
|
+
ledgerPath: tmpDb(),
|
|
36
|
+
reset: true,
|
|
37
|
+
transactionHistory: 1000,
|
|
38
|
+
logLevel: "warn",
|
|
39
|
+
...overrides,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function startServer(config: ServerConfig) {
|
|
44
|
+
const server = createServer(config);
|
|
45
|
+
await server.start();
|
|
46
|
+
const url = `http://${config.host}:${config.port}`;
|
|
47
|
+
const connection = new Connection(url, "confirmed");
|
|
48
|
+
return { server, connection, url };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function cleanDb(dbPath: string) {
|
|
52
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
53
|
+
try {
|
|
54
|
+
fs.unlinkSync(dbPath + suffix);
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let passed = 0;
|
|
60
|
+
let failed = 0;
|
|
61
|
+
|
|
62
|
+
function assert(condition: boolean, msg: string): void {
|
|
63
|
+
if (!condition) throw new Error(`Assertion failed: ${msg}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function runTest(name: string, fn: () => Promise<void>) {
|
|
67
|
+
try {
|
|
68
|
+
await fn();
|
|
69
|
+
passed++;
|
|
70
|
+
console.log(` ✓ ${name}`);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
failed++;
|
|
73
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
74
|
+
console.error(` ✗ ${name}: ${msg}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Test suites
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
async function basicRpcTests() {
|
|
83
|
+
console.log("\n── Basic RPC ──");
|
|
84
|
+
const config = makeConfig();
|
|
85
|
+
const { server, connection } = await startServer(config);
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
await runTest("getVersion returns solana-core", async () => {
|
|
89
|
+
const v = await connection.getVersion();
|
|
90
|
+
assert(
|
|
91
|
+
typeof v["solana-core"] === "string",
|
|
92
|
+
`unexpected version: ${JSON.stringify(v)}`
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await runTest("getSlot returns a number >= 1", async () => {
|
|
97
|
+
const slot = await connection.getSlot();
|
|
98
|
+
assert(typeof slot === "number" && slot >= 1, `slot=${slot}`);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await runTest("getBlockHeight returns a number >= 1", async () => {
|
|
102
|
+
const h = await connection.getBlockHeight();
|
|
103
|
+
assert(typeof h === "number" && h >= 1, `height=${h}`);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await runTest("getLatestBlockhash returns blockhash string", async () => {
|
|
107
|
+
const { blockhash, lastValidBlockHeight } =
|
|
108
|
+
await connection.getLatestBlockhash();
|
|
109
|
+
assert(typeof blockhash === "string" && blockhash.length > 0, "empty bh");
|
|
110
|
+
assert(lastValidBlockHeight > 0, `lvbh=${lastValidBlockHeight}`);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await runTest("getBalance of unknown address is 0", async () => {
|
|
114
|
+
const bal = await connection.getBalance(Keypair.generate().publicKey);
|
|
115
|
+
assert(bal === 0, `bal=${bal}`);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await runTest("getAccountInfo of unknown address is null", async () => {
|
|
119
|
+
const info = await connection.getAccountInfo(
|
|
120
|
+
Keypair.generate().publicKey
|
|
121
|
+
);
|
|
122
|
+
assert(info === null, "expected null");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await runTest("getMinimumBalanceForRentExemption returns > 0", async () => {
|
|
126
|
+
const min = await connection.getMinimumBalanceForRentExemption(0);
|
|
127
|
+
assert(min > 0, `min=${min}`);
|
|
128
|
+
});
|
|
129
|
+
} finally {
|
|
130
|
+
await server.stop();
|
|
131
|
+
cleanDb(config.ledgerPath);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function airdropAndTransferTests() {
|
|
136
|
+
console.log("\n── Airdrop & Transfer ──");
|
|
137
|
+
const config = makeConfig();
|
|
138
|
+
const { server, connection } = await startServer(config);
|
|
139
|
+
|
|
140
|
+
const payer = Keypair.generate();
|
|
141
|
+
const receiver = Keypair.generate();
|
|
142
|
+
let transferSig = "";
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await runTest("requestAirdrop + confirmTransaction", async () => {
|
|
146
|
+
const sig = await connection.requestAirdrop(
|
|
147
|
+
payer.publicKey,
|
|
148
|
+
2 * LAMPORTS_PER_SOL
|
|
149
|
+
);
|
|
150
|
+
assert(typeof sig === "string" && sig.length > 0, "no sig");
|
|
151
|
+
const conf = await connection.confirmTransaction(sig);
|
|
152
|
+
assert(conf.value.err === null, `confirm err: ${conf.value.err}`);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await runTest("payer balance equals airdropped amount", async () => {
|
|
156
|
+
const bal = await connection.getBalance(payer.publicKey);
|
|
157
|
+
assert(bal === 2 * LAMPORTS_PER_SOL, `bal=${bal}`);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await runTest("sendAndConfirmTransaction (SOL transfer)", async () => {
|
|
161
|
+
const tx = new Transaction().add(
|
|
162
|
+
SystemProgram.transfer({
|
|
163
|
+
fromPubkey: payer.publicKey,
|
|
164
|
+
toPubkey: receiver.publicKey,
|
|
165
|
+
lamports: LAMPORTS_PER_SOL / 2,
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
transferSig = await sendAndConfirmTransaction(connection, tx, [payer]);
|
|
169
|
+
assert(transferSig.length > 0, "no sig");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await runTest("receiver got 0.5 SOL", async () => {
|
|
173
|
+
const bal = await connection.getBalance(receiver.publicKey);
|
|
174
|
+
assert(bal === LAMPORTS_PER_SOL / 2, `bal=${bal}`);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await runTest("payer balance decreased by 0.5 SOL + fee", async () => {
|
|
178
|
+
const bal = await connection.getBalance(payer.publicKey);
|
|
179
|
+
assert(bal < 2 * LAMPORTS_PER_SOL, `bal=${bal}`);
|
|
180
|
+
assert(bal > LAMPORTS_PER_SOL, `bal too low: ${bal}`);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await runTest("getTransaction returns metadata", async () => {
|
|
184
|
+
const info = await connection.getTransaction(transferSig, {
|
|
185
|
+
maxSupportedTransactionVersion: 0,
|
|
186
|
+
});
|
|
187
|
+
assert(info !== null, "tx not found");
|
|
188
|
+
assert(typeof info!.slot === "number", "no slot");
|
|
189
|
+
assert(info!.meta?.err === null, `tx err: ${info!.meta?.err}`);
|
|
190
|
+
assert((info!.meta?.fee ?? 0) > 0, "fee should be > 0");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
await runTest("getSignatureStatuses returns confirmed", async () => {
|
|
194
|
+
const res = await connection.getSignatureStatuses([transferSig]);
|
|
195
|
+
const s = res.value[0];
|
|
196
|
+
assert(s !== null, "status null");
|
|
197
|
+
assert(s!.confirmationStatus === "confirmed", `status=${s!.confirmationStatus}`);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
await runTest("slot advanced after transactions", async () => {
|
|
201
|
+
const slot = await connection.getSlot();
|
|
202
|
+
assert(slot > 1, `slot should have advanced, got ${slot}`);
|
|
203
|
+
});
|
|
204
|
+
} finally {
|
|
205
|
+
await server.stop();
|
|
206
|
+
cleanDb(config.ledgerPath);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function multipleAirdropTransferTests() {
|
|
211
|
+
console.log("\n── Multiple transfers ──");
|
|
212
|
+
const config = makeConfig();
|
|
213
|
+
const { server, connection } = await startServer(config);
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const alice = Keypair.generate();
|
|
217
|
+
const bob = Keypair.generate();
|
|
218
|
+
const carol = Keypair.generate();
|
|
219
|
+
|
|
220
|
+
// fund alice
|
|
221
|
+
const sig1 = await connection.requestAirdrop(
|
|
222
|
+
alice.publicKey,
|
|
223
|
+
5 * LAMPORTS_PER_SOL
|
|
224
|
+
);
|
|
225
|
+
await connection.confirmTransaction(sig1);
|
|
226
|
+
|
|
227
|
+
// alice -> bob 1 SOL
|
|
228
|
+
const tx1 = new Transaction().add(
|
|
229
|
+
SystemProgram.transfer({
|
|
230
|
+
fromPubkey: alice.publicKey,
|
|
231
|
+
toPubkey: bob.publicKey,
|
|
232
|
+
lamports: LAMPORTS_PER_SOL,
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
await sendAndConfirmTransaction(connection, tx1, [alice]);
|
|
236
|
+
|
|
237
|
+
// alice -> carol 2 SOL
|
|
238
|
+
const tx2 = new Transaction().add(
|
|
239
|
+
SystemProgram.transfer({
|
|
240
|
+
fromPubkey: alice.publicKey,
|
|
241
|
+
toPubkey: carol.publicKey,
|
|
242
|
+
lamports: 2 * LAMPORTS_PER_SOL,
|
|
243
|
+
})
|
|
244
|
+
);
|
|
245
|
+
await sendAndConfirmTransaction(connection, tx2, [alice]);
|
|
246
|
+
|
|
247
|
+
await runTest("bob has 1 SOL", async () => {
|
|
248
|
+
const bal = await connection.getBalance(bob.publicKey);
|
|
249
|
+
assert(bal === LAMPORTS_PER_SOL, `bob bal=${bal}`);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
await runTest("carol has 2 SOL", async () => {
|
|
253
|
+
const bal = await connection.getBalance(carol.publicKey);
|
|
254
|
+
assert(bal === 2 * LAMPORTS_PER_SOL, `carol bal=${bal}`);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
await runTest("alice balance decreased correctly", async () => {
|
|
258
|
+
const bal = await connection.getBalance(alice.publicKey);
|
|
259
|
+
// 5 SOL - 1 SOL - 2 SOL - 2 * 5000 lamports fee
|
|
260
|
+
assert(bal < 2 * LAMPORTS_PER_SOL, `alice bal=${bal}`);
|
|
261
|
+
assert(bal > LAMPORTS_PER_SOL, `alice bal too low=${bal}`);
|
|
262
|
+
});
|
|
263
|
+
} finally {
|
|
264
|
+
await server.stop();
|
|
265
|
+
cleanDb(config.ledgerPath);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
// Persistence tests
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
async function persistenceBalanceSurvivesRestart() {
|
|
274
|
+
console.log("\n── Persistence: balance survives restart ──");
|
|
275
|
+
const dbPath = tmpDb();
|
|
276
|
+
const baseConfig = makeConfig({ ledgerPath: dbPath, reset: true });
|
|
277
|
+
|
|
278
|
+
const payer = Keypair.generate();
|
|
279
|
+
const payerAddr = payer.publicKey.toBase58();
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
// --- session 1: airdrop ---
|
|
283
|
+
{
|
|
284
|
+
const { server, connection } = await startServer(baseConfig);
|
|
285
|
+
const sig = await connection.requestAirdrop(
|
|
286
|
+
payer.publicKey,
|
|
287
|
+
3 * LAMPORTS_PER_SOL
|
|
288
|
+
);
|
|
289
|
+
await connection.confirmTransaction(sig);
|
|
290
|
+
|
|
291
|
+
await runTest("session 1: payer has 3 SOL", async () => {
|
|
292
|
+
const bal = await connection.getBalance(payer.publicKey);
|
|
293
|
+
assert(bal === 3 * LAMPORTS_PER_SOL, `bal=${bal}`);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
await server.stop();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// small delay so OS releases ports
|
|
300
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
301
|
+
|
|
302
|
+
// --- session 2: restore without reset ---
|
|
303
|
+
{
|
|
304
|
+
const config2 = makeConfig({
|
|
305
|
+
ledgerPath: dbPath,
|
|
306
|
+
reset: false,
|
|
307
|
+
port: baseConfig.port, // reuse same port
|
|
308
|
+
});
|
|
309
|
+
// un-bump the port counter since we manually set it
|
|
310
|
+
nextPort -= 2;
|
|
311
|
+
|
|
312
|
+
const { server, connection } = await startServer(config2);
|
|
313
|
+
|
|
314
|
+
await runTest("session 2: payer still has 3 SOL", async () => {
|
|
315
|
+
const bal = await connection.getBalance(payer.publicKey);
|
|
316
|
+
assert(bal === 3 * LAMPORTS_PER_SOL, `bal=${bal}`);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
await server.stop();
|
|
320
|
+
}
|
|
321
|
+
} finally {
|
|
322
|
+
cleanDb(dbPath);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function persistenceTransferSurvivesRestart() {
|
|
327
|
+
console.log("\n── Persistence: transfer balances survive restart ──");
|
|
328
|
+
const dbPath = tmpDb();
|
|
329
|
+
const baseConfig = makeConfig({ ledgerPath: dbPath, reset: true });
|
|
330
|
+
|
|
331
|
+
const alice = Keypair.generate();
|
|
332
|
+
const bob = Keypair.generate();
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
// --- session 1: airdrop alice, transfer to bob ---
|
|
336
|
+
{
|
|
337
|
+
const { server, connection } = await startServer(baseConfig);
|
|
338
|
+
|
|
339
|
+
const sig = await connection.requestAirdrop(
|
|
340
|
+
alice.publicKey,
|
|
341
|
+
5 * LAMPORTS_PER_SOL
|
|
342
|
+
);
|
|
343
|
+
await connection.confirmTransaction(sig);
|
|
344
|
+
|
|
345
|
+
const tx = new Transaction().add(
|
|
346
|
+
SystemProgram.transfer({
|
|
347
|
+
fromPubkey: alice.publicKey,
|
|
348
|
+
toPubkey: bob.publicKey,
|
|
349
|
+
lamports: 2 * LAMPORTS_PER_SOL,
|
|
350
|
+
})
|
|
351
|
+
);
|
|
352
|
+
await sendAndConfirmTransaction(connection, tx, [alice]);
|
|
353
|
+
|
|
354
|
+
await runTest("session 1: bob has 2 SOL", async () => {
|
|
355
|
+
const bal = await connection.getBalance(bob.publicKey);
|
|
356
|
+
assert(bal === 2 * LAMPORTS_PER_SOL, `bal=${bal}`);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const aliceBal = await connection.getBalance(alice.publicKey);
|
|
360
|
+
await runTest("session 1: alice balance < 3 SOL (fee deducted)", async () => {
|
|
361
|
+
assert(aliceBal < 3 * LAMPORTS_PER_SOL, `bal=${aliceBal}`);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
await server.stop();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
368
|
+
|
|
369
|
+
// --- session 2: verify both balances ---
|
|
370
|
+
{
|
|
371
|
+
const config2 = makeConfig({
|
|
372
|
+
ledgerPath: dbPath,
|
|
373
|
+
reset: false,
|
|
374
|
+
port: baseConfig.port,
|
|
375
|
+
});
|
|
376
|
+
nextPort -= 2;
|
|
377
|
+
|
|
378
|
+
const { server, connection } = await startServer(config2);
|
|
379
|
+
|
|
380
|
+
await runTest("session 2: bob still has 2 SOL", async () => {
|
|
381
|
+
const bal = await connection.getBalance(bob.publicKey);
|
|
382
|
+
assert(bal === 2 * LAMPORTS_PER_SOL, `bal=${bal}`);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
await runTest("session 2: alice balance persisted correctly", async () => {
|
|
386
|
+
const bal = await connection.getBalance(alice.publicKey);
|
|
387
|
+
assert(bal < 3 * LAMPORTS_PER_SOL, `bal=${bal}`);
|
|
388
|
+
assert(bal > 2 * LAMPORTS_PER_SOL, `bal too low=${bal}`);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
await server.stop();
|
|
392
|
+
}
|
|
393
|
+
} finally {
|
|
394
|
+
cleanDb(dbPath);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async function persistenceSlotSurvivesRestart() {
|
|
399
|
+
console.log("\n── Persistence: slot counter survives restart ──");
|
|
400
|
+
const dbPath = tmpDb();
|
|
401
|
+
const baseConfig = makeConfig({ ledgerPath: dbPath, reset: true });
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
let slotAfterTx: number;
|
|
405
|
+
|
|
406
|
+
// --- session 1: do some work to advance slot ---
|
|
407
|
+
{
|
|
408
|
+
const { server, connection } = await startServer(baseConfig);
|
|
409
|
+
const kp = Keypair.generate();
|
|
410
|
+
const sig = await connection.requestAirdrop(
|
|
411
|
+
kp.publicKey,
|
|
412
|
+
LAMPORTS_PER_SOL
|
|
413
|
+
);
|
|
414
|
+
await connection.confirmTransaction(sig);
|
|
415
|
+
slotAfterTx = await connection.getSlot();
|
|
416
|
+
|
|
417
|
+
await runTest("session 1: slot advanced past 1", async () => {
|
|
418
|
+
assert(slotAfterTx > 1, `slot=${slotAfterTx}`);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
await server.stop();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
425
|
+
|
|
426
|
+
// --- session 2: slot should resume ---
|
|
427
|
+
{
|
|
428
|
+
const config2 = makeConfig({
|
|
429
|
+
ledgerPath: dbPath,
|
|
430
|
+
reset: false,
|
|
431
|
+
port: baseConfig.port,
|
|
432
|
+
});
|
|
433
|
+
nextPort -= 2;
|
|
434
|
+
|
|
435
|
+
const { server, connection } = await startServer(config2);
|
|
436
|
+
|
|
437
|
+
await runTest("session 2: slot >= session 1 slot", async () => {
|
|
438
|
+
const slot = await connection.getSlot();
|
|
439
|
+
assert(slot >= slotAfterTx!, `slot=${slot} < expected=${slotAfterTx}`);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
await server.stop();
|
|
443
|
+
}
|
|
444
|
+
} finally {
|
|
445
|
+
cleanDb(dbPath);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async function persistenceTransactionHistorySurvivesRestart() {
|
|
450
|
+
console.log("\n── Persistence: transaction history survives restart ──");
|
|
451
|
+
const dbPath = tmpDb();
|
|
452
|
+
const baseConfig = makeConfig({ ledgerPath: dbPath, reset: true });
|
|
453
|
+
|
|
454
|
+
const payer = Keypair.generate();
|
|
455
|
+
const receiver = Keypair.generate();
|
|
456
|
+
let txSig = "";
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
// --- session 1: airdrop + transfer ---
|
|
460
|
+
{
|
|
461
|
+
const { server, connection } = await startServer(baseConfig);
|
|
462
|
+
|
|
463
|
+
const airdropSig = await connection.requestAirdrop(
|
|
464
|
+
payer.publicKey,
|
|
465
|
+
2 * LAMPORTS_PER_SOL
|
|
466
|
+
);
|
|
467
|
+
await connection.confirmTransaction(airdropSig);
|
|
468
|
+
|
|
469
|
+
const tx = new Transaction().add(
|
|
470
|
+
SystemProgram.transfer({
|
|
471
|
+
fromPubkey: payer.publicKey,
|
|
472
|
+
toPubkey: receiver.publicKey,
|
|
473
|
+
lamports: LAMPORTS_PER_SOL,
|
|
474
|
+
})
|
|
475
|
+
);
|
|
476
|
+
txSig = await sendAndConfirmTransaction(connection, tx, [payer]);
|
|
477
|
+
|
|
478
|
+
await runTest("session 1: getTransaction works", async () => {
|
|
479
|
+
const info = await connection.getTransaction(txSig, {
|
|
480
|
+
maxSupportedTransactionVersion: 0,
|
|
481
|
+
});
|
|
482
|
+
assert(info !== null, "tx not found");
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
await runTest("session 1: getSignatureStatuses works", async () => {
|
|
486
|
+
const res = await connection.getSignatureStatuses([txSig]);
|
|
487
|
+
assert(res.value[0]?.confirmationStatus === "confirmed", "not confirmed");
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
await server.stop();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
494
|
+
|
|
495
|
+
// --- session 2: tx history should still be there ---
|
|
496
|
+
{
|
|
497
|
+
const config2 = makeConfig({
|
|
498
|
+
ledgerPath: dbPath,
|
|
499
|
+
reset: false,
|
|
500
|
+
port: baseConfig.port,
|
|
501
|
+
});
|
|
502
|
+
nextPort -= 2;
|
|
503
|
+
|
|
504
|
+
const { server, connection } = await startServer(config2);
|
|
505
|
+
|
|
506
|
+
await runTest("session 2: getTransaction still returns the tx", async () => {
|
|
507
|
+
const info = await connection.getTransaction(txSig, {
|
|
508
|
+
maxSupportedTransactionVersion: 0,
|
|
509
|
+
});
|
|
510
|
+
assert(info !== null, "tx not found after restart");
|
|
511
|
+
assert(info!.meta?.err === null, `err: ${info!.meta?.err}`);
|
|
512
|
+
assert((info!.meta?.fee ?? 0) > 0, "fee should persist");
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
await runTest("session 2: getSignatureStatuses still confirmed", async () => {
|
|
516
|
+
const res = await connection.getSignatureStatuses([txSig]);
|
|
517
|
+
assert(res.value[0] !== null, "status null");
|
|
518
|
+
assert(
|
|
519
|
+
res.value[0]!.confirmationStatus === "confirmed",
|
|
520
|
+
`status: ${res.value[0]!.confirmationStatus}`
|
|
521
|
+
);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
await server.stop();
|
|
525
|
+
}
|
|
526
|
+
} finally {
|
|
527
|
+
cleanDb(dbPath);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async function persistenceResetClearsState() {
|
|
532
|
+
console.log("\n── Persistence: --reset clears everything ──");
|
|
533
|
+
const dbPath = tmpDb();
|
|
534
|
+
const baseConfig = makeConfig({ ledgerPath: dbPath, reset: true });
|
|
535
|
+
|
|
536
|
+
const kp = Keypair.generate();
|
|
537
|
+
|
|
538
|
+
try {
|
|
539
|
+
// --- session 1: put some state ---
|
|
540
|
+
{
|
|
541
|
+
const { server, connection } = await startServer(baseConfig);
|
|
542
|
+
const sig = await connection.requestAirdrop(
|
|
543
|
+
kp.publicKey,
|
|
544
|
+
LAMPORTS_PER_SOL
|
|
545
|
+
);
|
|
546
|
+
await connection.confirmTransaction(sig);
|
|
547
|
+
|
|
548
|
+
await runTest("session 1: balance is 1 SOL", async () => {
|
|
549
|
+
const bal = await connection.getBalance(kp.publicKey);
|
|
550
|
+
assert(bal === LAMPORTS_PER_SOL, `bal=${bal}`);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
await server.stop();
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
557
|
+
|
|
558
|
+
// --- session 2: start with --reset ---
|
|
559
|
+
{
|
|
560
|
+
const config2 = makeConfig({
|
|
561
|
+
ledgerPath: dbPath,
|
|
562
|
+
reset: true,
|
|
563
|
+
port: baseConfig.port,
|
|
564
|
+
});
|
|
565
|
+
nextPort -= 2;
|
|
566
|
+
|
|
567
|
+
const { server, connection } = await startServer(config2);
|
|
568
|
+
|
|
569
|
+
await runTest("session 2 (reset): balance is 0", async () => {
|
|
570
|
+
const bal = await connection.getBalance(kp.publicKey);
|
|
571
|
+
assert(bal === 0, `bal=${bal}, expected 0 after reset`);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
await runTest("session 2 (reset): slot is back to 1", async () => {
|
|
575
|
+
const slot = await connection.getSlot();
|
|
576
|
+
assert(slot === 1, `slot=${slot}, expected 1 after reset`);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
await server.stop();
|
|
580
|
+
}
|
|
581
|
+
} finally {
|
|
582
|
+
cleanDb(dbPath);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async function persistenceCanTransactAfterRestore() {
|
|
587
|
+
console.log("\n── Persistence: can transact on restored state ──");
|
|
588
|
+
const dbPath = tmpDb();
|
|
589
|
+
const baseConfig = makeConfig({ ledgerPath: dbPath, reset: true });
|
|
590
|
+
|
|
591
|
+
const alice = Keypair.generate();
|
|
592
|
+
const bob = Keypair.generate();
|
|
593
|
+
|
|
594
|
+
try {
|
|
595
|
+
// --- session 1: airdrop alice ---
|
|
596
|
+
{
|
|
597
|
+
const { server, connection } = await startServer(baseConfig);
|
|
598
|
+
const sig = await connection.requestAirdrop(
|
|
599
|
+
alice.publicKey,
|
|
600
|
+
5 * LAMPORTS_PER_SOL
|
|
601
|
+
);
|
|
602
|
+
await connection.confirmTransaction(sig);
|
|
603
|
+
await server.stop();
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
607
|
+
|
|
608
|
+
// --- session 2: transfer from restored alice -> bob ---
|
|
609
|
+
{
|
|
610
|
+
const config2 = makeConfig({
|
|
611
|
+
ledgerPath: dbPath,
|
|
612
|
+
reset: false,
|
|
613
|
+
port: baseConfig.port,
|
|
614
|
+
});
|
|
615
|
+
nextPort -= 2;
|
|
616
|
+
|
|
617
|
+
const { server, connection } = await startServer(config2);
|
|
618
|
+
|
|
619
|
+
await runTest("session 2: alice can still send SOL", async () => {
|
|
620
|
+
const tx = new Transaction().add(
|
|
621
|
+
SystemProgram.transfer({
|
|
622
|
+
fromPubkey: alice.publicKey,
|
|
623
|
+
toPubkey: bob.publicKey,
|
|
624
|
+
lamports: LAMPORTS_PER_SOL,
|
|
625
|
+
})
|
|
626
|
+
);
|
|
627
|
+
const sig = await sendAndConfirmTransaction(connection, tx, [alice]);
|
|
628
|
+
assert(sig.length > 0, "no sig");
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
await runTest("session 2: bob received 1 SOL", async () => {
|
|
632
|
+
const bal = await connection.getBalance(bob.publicKey);
|
|
633
|
+
assert(bal === LAMPORTS_PER_SOL, `bal=${bal}`);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
await runTest("session 2: alice balance updated", async () => {
|
|
637
|
+
const bal = await connection.getBalance(alice.publicKey);
|
|
638
|
+
assert(bal < 4 * LAMPORTS_PER_SOL, `bal=${bal}`);
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
await server.stop();
|
|
642
|
+
}
|
|
643
|
+
} finally {
|
|
644
|
+
cleanDb(dbPath);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// ---------------------------------------------------------------------------
|
|
649
|
+
// Main
|
|
650
|
+
// ---------------------------------------------------------------------------
|
|
651
|
+
|
|
652
|
+
async function main() {
|
|
653
|
+
setLogLevel("error");
|
|
654
|
+
console.log("Solana-TS Test Suite\n");
|
|
655
|
+
|
|
656
|
+
await basicRpcTests();
|
|
657
|
+
await airdropAndTransferTests();
|
|
658
|
+
await multipleAirdropTransferTests();
|
|
659
|
+
|
|
660
|
+
await persistenceBalanceSurvivesRestart();
|
|
661
|
+
await persistenceTransferSurvivesRestart();
|
|
662
|
+
await persistenceSlotSurvivesRestart();
|
|
663
|
+
await persistenceTransactionHistorySurvivesRestart();
|
|
664
|
+
await persistenceResetClearsState();
|
|
665
|
+
await persistenceCanTransactAfterRestore();
|
|
666
|
+
|
|
667
|
+
console.log(`\n${"═".repeat(40)}`);
|
|
668
|
+
console.log(` ${passed} passed, ${failed} failed`);
|
|
669
|
+
console.log(`${"═".repeat(40)}\n`);
|
|
670
|
+
|
|
671
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
main();
|