@hardkas/localnet 0.7.3-alpha → 0.7.5-alpha
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.
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import fs from "fs/promises";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { existsSync } from "fs";
|
|
5
|
+
import {
|
|
6
|
+
ARTIFACT_SCHEMAS
|
|
7
|
+
} from "@hardkas/artifacts";
|
|
5
8
|
import { writeFileAtomic } from "@hardkas/core";
|
|
6
9
|
import { deterministicCompare } from "@hardkas/core";
|
|
7
10
|
function getDefaultReceiptsDir(cwd = process.cwd()) {
|
|
@@ -22,7 +25,9 @@ async function saveSimulatedReceipt(receipt, options) {
|
|
|
22
25
|
await fs.mkdir(dir, { recursive: true });
|
|
23
26
|
}
|
|
24
27
|
const filePath = getReceiptPath(receipt.txId, options?.cwd);
|
|
25
|
-
await writeFileAtomic(filePath, JSON.stringify(receipt, null, 2), {
|
|
28
|
+
await writeFileAtomic(filePath, JSON.stringify(receipt, null, 2), {
|
|
29
|
+
encoding: "utf-8"
|
|
30
|
+
});
|
|
26
31
|
return filePath;
|
|
27
32
|
}
|
|
28
33
|
async function loadSimulatedReceipt(txId, options) {
|
|
@@ -45,7 +50,9 @@ async function listSimulatedReceipts(options) {
|
|
|
45
50
|
try {
|
|
46
51
|
const txId = path.basename(file, ".json");
|
|
47
52
|
const receipt = await loadSimulatedReceipt(txId, options);
|
|
48
|
-
|
|
53
|
+
if (receipt.schema === ARTIFACT_SCHEMAS.TX_RECEIPT) {
|
|
54
|
+
receipts.push(receipt);
|
|
55
|
+
}
|
|
49
56
|
} catch (e) {
|
|
50
57
|
}
|
|
51
58
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -372,7 +372,7 @@ declare function moveSink(dag: SimulatedDag, newSinkId: string, txProvider: (txI
|
|
|
372
372
|
inputs: string[];
|
|
373
373
|
} | undefined): SimulatedDag;
|
|
374
374
|
/**
|
|
375
|
-
* Deterministic Conflict Resolution (Approximation for 0.7.
|
|
375
|
+
* Deterministic Conflict Resolution (Approximation for 0.7.5-alpha)
|
|
376
376
|
* Priority:
|
|
377
377
|
* 1. sink ancestry priority (is part of selectedPathToSink?)
|
|
378
378
|
* 2. deterministic block order (daaScore then block ID)
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
listSimulatedReceipts,
|
|
5
5
|
loadSimulatedReceipt,
|
|
6
6
|
saveSimulatedReceipt
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2YOFWE4E.js";
|
|
8
8
|
import {
|
|
9
9
|
getDefaultTracesDir,
|
|
10
10
|
getTracePath,
|
|
@@ -101,7 +101,7 @@ var SimulatedKaspaChain = class {
|
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
103
|
* Creates a snapshot of the current chain state.
|
|
104
|
-
* Snapshot IDs use Date.now() and are intended for session-based
|
|
104
|
+
* Snapshot IDs use Date.now() and are intended for session-based
|
|
105
105
|
* debugging and restore points, not for canonical identity.
|
|
106
106
|
*/
|
|
107
107
|
snapshot() {
|
|
@@ -135,7 +135,10 @@ async function startSimulatedDevnet(input) {
|
|
|
135
135
|
|
|
136
136
|
// src/state.ts
|
|
137
137
|
import { SOMPI_PER_KAS } from "@hardkas/core";
|
|
138
|
-
import {
|
|
138
|
+
import {
|
|
139
|
+
HARDKAS_VERSION,
|
|
140
|
+
ARTIFACT_SCHEMAS
|
|
141
|
+
} from "@hardkas/artifacts";
|
|
139
142
|
function createInitialLocalnetState(options = {}) {
|
|
140
143
|
const accountCount = options.accounts ?? 5;
|
|
141
144
|
const initialBalanceSompi = options.initialBalanceSompi ?? 1000n * SOMPI_PER_KAS;
|
|
@@ -206,7 +209,9 @@ async function saveLocalnetState(state, filePath) {
|
|
|
206
209
|
const targetPath = filePath ?? getDefaultLocalnetStatePath();
|
|
207
210
|
const dir = path.dirname(targetPath);
|
|
208
211
|
await fs.mkdir(dir, { recursive: true });
|
|
209
|
-
await writeFileAtomic(targetPath, JSON.stringify(state, null, 2), {
|
|
212
|
+
await writeFileAtomic(targetPath, JSON.stringify(state, null, 2), {
|
|
213
|
+
encoding: "utf-8"
|
|
214
|
+
});
|
|
210
215
|
}
|
|
211
216
|
async function loadLocalnetState(filePath) {
|
|
212
217
|
const targetPath = filePath ?? getDefaultLocalnetStatePath();
|
|
@@ -284,7 +289,9 @@ function calculateUtxoSetHash(utxos) {
|
|
|
284
289
|
return calculateContentHash(sorted);
|
|
285
290
|
}
|
|
286
291
|
function calculateAccountsHash(accounts) {
|
|
287
|
-
const sorted = [...accounts || []].sort(
|
|
292
|
+
const sorted = [...accounts || []].sort(
|
|
293
|
+
(a, b) => deterministicCompare(a.address, b.address)
|
|
294
|
+
);
|
|
288
295
|
return calculateContentHash(sorted);
|
|
289
296
|
}
|
|
290
297
|
function calculateStateHash(state) {
|
|
@@ -329,20 +336,32 @@ function verifySnapshot(snapshot) {
|
|
|
329
336
|
const errors = [];
|
|
330
337
|
const currentContentHash = calculateContentHash(snapshot);
|
|
331
338
|
const contentMatch = snapshot.contentHash === currentContentHash;
|
|
332
|
-
if (!contentMatch)
|
|
339
|
+
if (!contentMatch)
|
|
340
|
+
errors.push(
|
|
341
|
+
`Content hash mismatch: expected ${snapshot.contentHash}, got ${currentContentHash}`
|
|
342
|
+
);
|
|
333
343
|
const currentAccountsHash = calculateAccountsHash(snapshot.accounts);
|
|
334
344
|
const accountsMatch = snapshot.accountsHash === currentAccountsHash;
|
|
335
|
-
if (!accountsMatch)
|
|
345
|
+
if (!accountsMatch)
|
|
346
|
+
errors.push(
|
|
347
|
+
`Accounts hash mismatch: expected ${snapshot.accountsHash}, got ${currentAccountsHash}`
|
|
348
|
+
);
|
|
336
349
|
const currentUtxoSetHash = calculateUtxoSetHash(snapshot.utxos);
|
|
337
350
|
const utxoSetMatch = snapshot.utxoSetHash === currentUtxoSetHash;
|
|
338
|
-
if (!utxoSetMatch)
|
|
351
|
+
if (!utxoSetMatch)
|
|
352
|
+
errors.push(
|
|
353
|
+
`UTXO set hash mismatch: expected ${snapshot.utxoSetHash}, got ${currentUtxoSetHash}`
|
|
354
|
+
);
|
|
339
355
|
const currentStateHash = calculateContentHash({
|
|
340
356
|
daaScore: snapshot.daaScore,
|
|
341
357
|
accountsHash: currentAccountsHash,
|
|
342
358
|
utxoSetHash: currentUtxoSetHash
|
|
343
359
|
});
|
|
344
360
|
const stateMatch = snapshot.stateHash === currentStateHash;
|
|
345
|
-
if (!stateMatch)
|
|
361
|
+
if (!stateMatch)
|
|
362
|
+
errors.push(
|
|
363
|
+
`State hash mismatch: expected ${snapshot.stateHash}, got ${currentStateHash}`
|
|
364
|
+
);
|
|
346
365
|
return {
|
|
347
366
|
ok: errors.length === 0,
|
|
348
367
|
hashes: {
|
|
@@ -430,7 +449,7 @@ function applySimulatedPayment(state, input, ctx) {
|
|
|
430
449
|
}
|
|
431
450
|
const unspent = getSpendableUtxos(state, fromAddress);
|
|
432
451
|
if (unspent.length === 0) {
|
|
433
|
-
throw new Error(
|
|
452
|
+
throw new Error("insufficient simulated funds");
|
|
434
453
|
}
|
|
435
454
|
const availableUtxos = unspent.map((u) => {
|
|
436
455
|
const parts = u.id.split(":");
|
|
@@ -449,15 +468,17 @@ function applySimulatedPayment(state, input, ctx) {
|
|
|
449
468
|
availableUtxos,
|
|
450
469
|
feeRateSompiPerMass
|
|
451
470
|
});
|
|
452
|
-
const spentUtxoIds = plan.inputs.map(
|
|
471
|
+
const spentUtxoIds = plan.inputs.map(
|
|
472
|
+
(i) => `${i.outpoint.transactionId}:${i.outpoint.index}`
|
|
473
|
+
);
|
|
453
474
|
const uniqueSpentIds = new Set(spentUtxoIds);
|
|
454
475
|
if (uniqueSpentIds.size !== spentUtxoIds.length) {
|
|
455
476
|
throw new Error("Duplicate inputs detected in transaction plan");
|
|
456
477
|
}
|
|
457
478
|
for (const id of spentUtxoIds) {
|
|
458
479
|
const utxo = state.utxos.find((u) => u.id === id);
|
|
459
|
-
if (!utxo) throw new Error(
|
|
460
|
-
if (utxo.spent) throw new Error(
|
|
480
|
+
if (!utxo) throw new Error("missing simulated UTXO");
|
|
481
|
+
if (utxo.spent) throw new Error("invalid simulated input");
|
|
461
482
|
}
|
|
462
483
|
const planArtifact = createTxPlanArtifact({
|
|
463
484
|
networkId: state.networkId || "simnet",
|
|
@@ -558,11 +579,13 @@ function applySimulatedPlan(state, planArtifact, ctx, options) {
|
|
|
558
579
|
const errors = [];
|
|
559
580
|
const preStateHash = calculateStateHash(state);
|
|
560
581
|
try {
|
|
561
|
-
const spentUtxoIds = planArtifact.inputs.map(
|
|
582
|
+
const spentUtxoIds = planArtifact.inputs.map(
|
|
583
|
+
(i) => `${i.outpoint.transactionId}:${i.outpoint.index}`
|
|
584
|
+
);
|
|
562
585
|
for (const id of spentUtxoIds) {
|
|
563
586
|
const utxo = state.utxos.find((u) => u.id === id);
|
|
564
|
-
if (!utxo) throw new Error(
|
|
565
|
-
if (utxo.spent) throw new Error(
|
|
587
|
+
if (!utxo) throw new Error("missing simulated UTXO");
|
|
588
|
+
if (utxo.spent) throw new Error("invalid simulated input");
|
|
566
589
|
}
|
|
567
590
|
const nextDaaScore = (BigInt(state.daaScore) + 1n).toString();
|
|
568
591
|
const txId = options?.txId || generateDeterministicTxId(planArtifact, preStateHash, nextDaaScore);
|
|
@@ -595,7 +618,11 @@ function applySimulatedPlan(state, planArtifact, ctx, options) {
|
|
|
595
618
|
nextUtxos.push(changeUtxo);
|
|
596
619
|
createdUtxoIds.push(changeUtxo.id);
|
|
597
620
|
}
|
|
598
|
-
const nextState = {
|
|
621
|
+
const nextState = {
|
|
622
|
+
...state,
|
|
623
|
+
daaScore: nextDaaScore,
|
|
624
|
+
utxos: nextUtxos
|
|
625
|
+
};
|
|
599
626
|
const postStateHash = calculateStateHash(nextState);
|
|
600
627
|
const receipt = createSimulatedTxReceipt(planArtifact, txId, ctx, {
|
|
601
628
|
spentUtxoIds,
|
|
@@ -648,7 +675,11 @@ function verifyReplay(state, originalPlan, originalReceipt, ctx) {
|
|
|
648
675
|
planOk = false;
|
|
649
676
|
const errorMsg = `TxPlan contentHash mismatch: expected ${originalPlan.contentHash}, got ${currentPlanHash}`;
|
|
650
677
|
errors.push(errorMsg);
|
|
651
|
-
reportDivergences.push({
|
|
678
|
+
reportDivergences.push({
|
|
679
|
+
path: "plan.contentHash",
|
|
680
|
+
expected: originalPlan.contentHash,
|
|
681
|
+
actual: currentPlanHash
|
|
682
|
+
});
|
|
652
683
|
}
|
|
653
684
|
const originalPreState = originalReceipt.preStateHash;
|
|
654
685
|
if (originalPreState) {
|
|
@@ -670,7 +701,9 @@ function verifyReplay(state, originalPlan, originalReceipt, ctx) {
|
|
|
670
701
|
severity: "warning"
|
|
671
702
|
});
|
|
672
703
|
}
|
|
673
|
-
const result = applySimulatedPlan(state, originalPlan, ctx, {
|
|
704
|
+
const result = applySimulatedPlan(state, originalPlan, ctx, {
|
|
705
|
+
txId: originalReceipt.txId
|
|
706
|
+
});
|
|
674
707
|
const replayReceipt = result.receipt;
|
|
675
708
|
const diff = diffArtifacts(originalReceipt, replayReceipt);
|
|
676
709
|
if (!diff.identical) {
|
|
@@ -680,7 +713,9 @@ function verifyReplay(state, originalPlan, originalReceipt, ctx) {
|
|
|
680
713
|
expected: entry.left,
|
|
681
714
|
actual: entry.right
|
|
682
715
|
});
|
|
683
|
-
errors.push(
|
|
716
|
+
errors.push(
|
|
717
|
+
`Receipt divergence at ${entry.path}: expected ${JSON.stringify(entry.left)}, got ${JSON.stringify(entry.right)}`
|
|
718
|
+
);
|
|
684
719
|
}
|
|
685
720
|
}
|
|
686
721
|
for (const div of reportDivergences) {
|
|
@@ -717,7 +752,7 @@ function verifyReplay(state, originalPlan, originalReceipt, ctx) {
|
|
|
717
752
|
};
|
|
718
753
|
}
|
|
719
754
|
async function getSimulatedReplaySummary(txId, options = {}) {
|
|
720
|
-
const { loadSimulatedReceipt: loadSimulatedReceipt2 } = await import("./receipts-
|
|
755
|
+
const { loadSimulatedReceipt: loadSimulatedReceipt2 } = await import("./receipts-IEHNHM4Q.js");
|
|
721
756
|
const { loadSimulatedTrace: loadSimulatedTrace2 } = await import("./traces-ZUBWSXYB.js");
|
|
722
757
|
const receipt = await loadSimulatedReceipt2(txId, options);
|
|
723
758
|
const trace = await loadSimulatedTrace2(txId, options);
|
|
@@ -1072,7 +1107,9 @@ async function forkFromNetwork(rpc, opts) {
|
|
|
1072
1107
|
const networkId = info.networkId || opts.network;
|
|
1073
1108
|
const targetDaaScore = opts.atDaaScore;
|
|
1074
1109
|
if (!targetDaaScore) {
|
|
1075
|
-
throw new Error(
|
|
1110
|
+
throw new Error(
|
|
1111
|
+
`[CRITICAL SEMANTIC ERROR] Implicit 'latest' resolution forbidden. You must explicitly provide atDaaScore.`
|
|
1112
|
+
);
|
|
1076
1113
|
}
|
|
1077
1114
|
const utxos = [];
|
|
1078
1115
|
for (const address of opts.addresses) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hardkas/localnet",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5-alpha",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
".": "./dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@hardkas/artifacts": "0.7.
|
|
12
|
-
"@hardkas/
|
|
13
|
-
"@hardkas/
|
|
14
|
-
"@hardkas/
|
|
15
|
-
"@hardkas/
|
|
16
|
-
"@hardkas/
|
|
11
|
+
"@hardkas/artifacts": "0.7.5-alpha",
|
|
12
|
+
"@hardkas/core": "0.7.5-alpha",
|
|
13
|
+
"@hardkas/simulator": "0.7.5-alpha",
|
|
14
|
+
"@hardkas/kaspa-rpc": "0.7.5-alpha",
|
|
15
|
+
"@hardkas/query": "0.7.5-alpha",
|
|
16
|
+
"@hardkas/tx-builder": "0.7.5-alpha"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"fast-check": "^4.8.0",
|