@hardkas/simulator 0.8.20-alpha → 0.9.1-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.
- package/dist/index.d.ts +161 -1
- package/dist/index.js +432 -2
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -374,4 +374,164 @@ declare function profileAndCompare(opts: {
|
|
|
374
374
|
comparison?: MassComparison;
|
|
375
375
|
};
|
|
376
376
|
|
|
377
|
-
|
|
377
|
+
declare const SILVER_SIMULATOR_FEE_SOMPI = 2000n;
|
|
378
|
+
declare const SILVER_SIMULATOR_CREATED_AT = "1970-01-01T00:00:00.000Z";
|
|
379
|
+
declare const SILVER_SIMULATOR_VERSION = "1.0.0-alpha";
|
|
380
|
+
type SilverSimulationStatus = "SIMULATED_ACCEPTED";
|
|
381
|
+
type SilverSimulationErrorCode = "SILVERSCRIPT_REDEEM_HASH_MISMATCH" | "SILVERSCRIPT_LOCKING_SCRIPT_MISMATCH" | "SILVERSCRIPT_SIGNATURE_SCRIPT_NOT_PUSH_ONLY" | "SILVERSCRIPT_ARGS_HASH_MISMATCH" | "SILVERSCRIPT_UTXO_ALREADY_SPENT" | "SILVERSCRIPT_NETWORK_UNSUPPORTED" | "SILVERSCRIPT_AMOUNT_TOO_SMALL" | "SILVERSCRIPT_DEPLOY_RECEIPT_NOT_FOUND" | "SILVERSCRIPT_SCHEMA_INVALID" | "SILVERSCRIPT_INVALID_HEX" | "SILVERSCRIPT_EXPECTED_OUTPUTS_REQUIRED" | "SILVERSCRIPT_SIGNATURE_SCRIPT_MISMATCH";
|
|
382
|
+
declare class SilverSimulationError extends Error {
|
|
383
|
+
readonly code: SilverSimulationErrorCode;
|
|
384
|
+
constructor(code: SilverSimulationErrorCode, message: string);
|
|
385
|
+
}
|
|
386
|
+
interface SilverDeployPlanArtifactLike {
|
|
387
|
+
schema: "hardkas.silver.deployPlan";
|
|
388
|
+
hardkasVersion?: string;
|
|
389
|
+
version?: string;
|
|
390
|
+
hashVersion?: number | string;
|
|
391
|
+
networkId: string;
|
|
392
|
+
mode?: string;
|
|
393
|
+
createdAt?: string;
|
|
394
|
+
contentHash?: string;
|
|
395
|
+
compileArtifactHash: string;
|
|
396
|
+
compiledScriptHash: string;
|
|
397
|
+
redeemScriptHex: string;
|
|
398
|
+
redeemScriptHash: string;
|
|
399
|
+
lockingScriptHex: string;
|
|
400
|
+
scriptPublicKeyVersion: number;
|
|
401
|
+
amountSompi: string;
|
|
402
|
+
deployerAddress: string;
|
|
403
|
+
}
|
|
404
|
+
interface SilverArgArtifactLike {
|
|
405
|
+
type: "hex";
|
|
406
|
+
value: string;
|
|
407
|
+
}
|
|
408
|
+
interface SilverSpendPlanArtifactLike {
|
|
409
|
+
schema: "hardkas.silver.spendPlan";
|
|
410
|
+
hardkasVersion?: string;
|
|
411
|
+
version?: string;
|
|
412
|
+
hashVersion?: number | string;
|
|
413
|
+
networkId: string;
|
|
414
|
+
mode?: string;
|
|
415
|
+
createdAt?: string;
|
|
416
|
+
contentHash?: string;
|
|
417
|
+
deployArtifactHash: string;
|
|
418
|
+
compileArtifactHash: string;
|
|
419
|
+
redeemScriptHash: string;
|
|
420
|
+
lockingScriptHex: string;
|
|
421
|
+
contractUtxoRef: {
|
|
422
|
+
transactionId: string;
|
|
423
|
+
index: number;
|
|
424
|
+
};
|
|
425
|
+
args: SilverArgArtifactLike[];
|
|
426
|
+
argsHash: string;
|
|
427
|
+
signatureScriptHex: string;
|
|
428
|
+
expectedOutputs: SilverExpectedOutput[];
|
|
429
|
+
}
|
|
430
|
+
interface SilverExpectedOutput {
|
|
431
|
+
address: string;
|
|
432
|
+
amountSompi: string;
|
|
433
|
+
scriptHash?: string;
|
|
434
|
+
}
|
|
435
|
+
interface SilverSyntheticOutpoint {
|
|
436
|
+
transactionId: string;
|
|
437
|
+
index: number;
|
|
438
|
+
}
|
|
439
|
+
interface SilverDeploySimulationReceipt {
|
|
440
|
+
schema: "hardkas.silver.deploySimulation";
|
|
441
|
+
hardkasVersion: string;
|
|
442
|
+
version: "1.0.0-alpha";
|
|
443
|
+
hashVersion: number;
|
|
444
|
+
networkId: "simnet";
|
|
445
|
+
mode: "simulated";
|
|
446
|
+
createdAt: string;
|
|
447
|
+
deployPlanHash: string;
|
|
448
|
+
compileArtifactHash: string;
|
|
449
|
+
compiledScriptHash: string;
|
|
450
|
+
redeemScriptHex: string;
|
|
451
|
+
redeemScriptHash: string;
|
|
452
|
+
lockingScriptHex: string;
|
|
453
|
+
scriptPublicKeyVersion: 0;
|
|
454
|
+
simulatedDeployTxId: string;
|
|
455
|
+
syntheticOutpoint: SilverSyntheticOutpoint;
|
|
456
|
+
amountSompi: string;
|
|
457
|
+
feeSompi: string;
|
|
458
|
+
status: SilverSimulationStatus;
|
|
459
|
+
lineage: {
|
|
460
|
+
artifactId: string;
|
|
461
|
+
lineageId: string;
|
|
462
|
+
parentArtifactId: string;
|
|
463
|
+
rootArtifactId: string;
|
|
464
|
+
sequence: number;
|
|
465
|
+
};
|
|
466
|
+
contentHash: string;
|
|
467
|
+
artifactId: string;
|
|
468
|
+
}
|
|
469
|
+
interface SilverSpendSimulationReceipt {
|
|
470
|
+
schema: "hardkas.silver.spendSimulation";
|
|
471
|
+
hardkasVersion: string;
|
|
472
|
+
version: "1.0.0-alpha";
|
|
473
|
+
hashVersion: number;
|
|
474
|
+
networkId: "simnet";
|
|
475
|
+
mode: "simulated";
|
|
476
|
+
createdAt: string;
|
|
477
|
+
deploySimulationHash: string;
|
|
478
|
+
spendPlanHash: string;
|
|
479
|
+
redeemScriptHash: string;
|
|
480
|
+
lockingScriptHex: string;
|
|
481
|
+
signatureScriptHex: string;
|
|
482
|
+
simulatedSpendTxId: string;
|
|
483
|
+
spentOutpoint: SilverSyntheticOutpoint;
|
|
484
|
+
expectedOutputs: SilverExpectedOutput[];
|
|
485
|
+
feeSompi: string;
|
|
486
|
+
status: SilverSimulationStatus;
|
|
487
|
+
lineage: {
|
|
488
|
+
artifactId: string;
|
|
489
|
+
lineageId: string;
|
|
490
|
+
parentArtifactId: string;
|
|
491
|
+
rootArtifactId: string;
|
|
492
|
+
sequence: number;
|
|
493
|
+
};
|
|
494
|
+
contentHash: string;
|
|
495
|
+
artifactId: string;
|
|
496
|
+
}
|
|
497
|
+
interface SilverSimulatedUtxo {
|
|
498
|
+
outpoint: SilverSyntheticOutpoint;
|
|
499
|
+
deploySimulationHash: string;
|
|
500
|
+
deployPlanHash: string;
|
|
501
|
+
redeemScriptHex: string;
|
|
502
|
+
redeemScriptHash: string;
|
|
503
|
+
lockingScriptHex: string;
|
|
504
|
+
amountSompi: string;
|
|
505
|
+
networkId: "simnet";
|
|
506
|
+
spent: boolean;
|
|
507
|
+
spentByTxId?: string;
|
|
508
|
+
}
|
|
509
|
+
interface SilverSimulationState {
|
|
510
|
+
schema: "hardkas.silver.simulationState.v1";
|
|
511
|
+
version: "1.0.0-alpha";
|
|
512
|
+
networkId: "simnet";
|
|
513
|
+
mode: "simulated";
|
|
514
|
+
deployReceipts: Record<string, SilverDeploySimulationReceipt>;
|
|
515
|
+
utxos: Record<string, SilverSimulatedUtxo>;
|
|
516
|
+
spentOutpoints: string[];
|
|
517
|
+
}
|
|
518
|
+
interface SilverDeploySimulationResult {
|
|
519
|
+
receipt: SilverDeploySimulationReceipt;
|
|
520
|
+
state: SilverSimulationState;
|
|
521
|
+
}
|
|
522
|
+
interface SilverSpendSimulationResult {
|
|
523
|
+
receipt: SilverSpendSimulationReceipt;
|
|
524
|
+
state: SilverSimulationState;
|
|
525
|
+
}
|
|
526
|
+
interface SilverSimulationOptions {
|
|
527
|
+
hardkasVersion?: string;
|
|
528
|
+
createdAt?: string;
|
|
529
|
+
}
|
|
530
|
+
declare function createSilverSimulationState(): SilverSimulationState;
|
|
531
|
+
declare function simulateSilverDeploy(deployPlanArtifact: SilverDeployPlanArtifactLike, options?: SilverSimulationOptions): SilverDeploySimulationResult;
|
|
532
|
+
declare function simulateSilverSpend(spendPlanArtifact: SilverSpendPlanArtifactLike, simulatedState: SilverSimulationState, options?: SilverSimulationOptions): SilverSpendSimulationResult;
|
|
533
|
+
declare function calculateSilverArgsHash(args: readonly SilverArgArtifactLike[]): string;
|
|
534
|
+
declare function outpointKey(outpoint: SilverSyntheticOutpoint): string;
|
|
535
|
+
declare function parsePushOnlyScript(signatureScriptHex: string): string[];
|
|
536
|
+
|
|
537
|
+
export { ApproxGhostdagEngine, type BlockHash, type BlueWorkType, type CandidateColor, type CompactGhostdagData, DEFAULT_K, type DagMetrics, GENESIS_HASH, type GhostdagData, GhostdagStore, type MassBreakdown, type MassComparison, type MassSnapshot, type MassSnapshotStore, SILVER_SIMULATOR_CREATED_AT, SILVER_SIMULATOR_FEE_SOMPI, SILVER_SIMULATOR_VERSION, type ScenarioConfig, type ScenarioResult, type SilverArgArtifactLike, type SilverDeployPlanArtifactLike, type SilverDeploySimulationReceipt, type SilverDeploySimulationResult, type SilverExpectedOutput, type SilverSimulatedUtxo, SilverSimulationError, type SilverSimulationErrorCode, type SilverSimulationOptions, type SilverSimulationState, type SilverSimulationStatus, type SilverSpendPlanArtifactLike, type SilverSpendSimulationReceipt, type SilverSpendSimulationResult, type SimBlock, type SimBlockHeader, type SimulationResult, type SortableBlock, type TxLifecyclePhase, TxSimulator, type TxTraceEvent, blockBlueScore, blockBlueWork, blockHash, blockParents, calculateSilverArgsHash, compactFromFull, compareMassProfiles, compareSortableBlocks, computeDagMetrics, createSilverSimulationState, createTraceId, findSelectedParent, formatMassComparison, formatMassProfile, formatScenarioReport, genesisGhostdagData, headerWork, isDagAncestorOf, loadMassSnapshot, orderedMergesetWithoutSelectedParent, outpointKey, parsePushOnlyScript, pastSet, profileAndCompare, profileMass, runAllScenarios, runDiamondDag, runForkResolution, runLinearChain, runWideDag, saveMassSnapshot, simulateSilverDeploy, simulateSilverSpend, sortBlocks, unorderedMergesetWithoutSelectedParent };
|
package/dist/index.js
CHANGED
|
@@ -624,6 +624,7 @@ function formatScenarioReport(results) {
|
|
|
624
624
|
|
|
625
625
|
// src/mass-profile.ts
|
|
626
626
|
import { estimateTransactionMass, estimateFeeFromMass } from "@hardkas/tx-builder";
|
|
627
|
+
import { formatSompiToKas } from "@hardkas/core";
|
|
627
628
|
function formatBigInt(n) {
|
|
628
629
|
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
629
630
|
}
|
|
@@ -678,7 +679,7 @@ function compareMassProfiles(current, previous) {
|
|
|
678
679
|
};
|
|
679
680
|
}
|
|
680
681
|
function formatMassProfile(breakdown) {
|
|
681
|
-
const kasAmount =
|
|
682
|
+
const kasAmount = formatSompiToKas(breakdown.estimatedFeeSompi);
|
|
682
683
|
return [
|
|
683
684
|
"\u2550\u2550\u2550 Mass Profile \u2550\u2550\u2550",
|
|
684
685
|
` Inputs : ${breakdown.inputCount} inputs \u2192 ${formatBigInt(breakdown.inputMass)} mass`,
|
|
@@ -686,7 +687,7 @@ function formatMassProfile(breakdown) {
|
|
|
686
687
|
` Payload : ${breakdown.payloadBytes} bytes \u2192 ${formatBigInt(breakdown.payloadMass)} mass`,
|
|
687
688
|
` Total mass : ${formatBigInt(breakdown.totalMass)}`,
|
|
688
689
|
` Fee rate : ${formatBigInt(breakdown.feeRate)} sompi/mass`,
|
|
689
|
-
` Est. fee : ${formatBigInt(breakdown.estimatedFeeSompi)} sompi (${kasAmount
|
|
690
|
+
` Est. fee : ${formatBigInt(breakdown.estimatedFeeSompi)} sompi (${kasAmount} KAS)`
|
|
690
691
|
].join("\n");
|
|
691
692
|
}
|
|
692
693
|
function formatMassComparison(comparison) {
|
|
@@ -757,20 +758,445 @@ function reviveSnapshot(snapshot) {
|
|
|
757
758
|
};
|
|
758
759
|
return snapshot;
|
|
759
760
|
}
|
|
761
|
+
|
|
762
|
+
// src/silver-simulator.ts
|
|
763
|
+
import { createHash as createHash2 } from "crypto";
|
|
764
|
+
import { createKaspaP2shBlake2bLock } from "@hardkas/core";
|
|
765
|
+
var SILVER_SIMULATOR_FEE_SOMPI = 2000n;
|
|
766
|
+
var SILVER_SIMULATOR_CREATED_AT = "1970-01-01T00:00:00.000Z";
|
|
767
|
+
var SILVER_SIMULATOR_VERSION = "1.0.0-alpha";
|
|
768
|
+
var CURRENT_HASH_VERSION = 4;
|
|
769
|
+
var SilverSimulationError = class extends Error {
|
|
770
|
+
code;
|
|
771
|
+
constructor(code, message) {
|
|
772
|
+
super(message);
|
|
773
|
+
this.name = "SilverSimulationError";
|
|
774
|
+
this.code = code;
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
function createSilverSimulationState() {
|
|
778
|
+
return {
|
|
779
|
+
schema: "hardkas.silver.simulationState.v1",
|
|
780
|
+
version: SILVER_SIMULATOR_VERSION,
|
|
781
|
+
networkId: "simnet",
|
|
782
|
+
mode: "simulated",
|
|
783
|
+
deployReceipts: {},
|
|
784
|
+
utxos: {},
|
|
785
|
+
spentOutpoints: []
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
function simulateSilverDeploy(deployPlanArtifact, options = {}) {
|
|
789
|
+
if (deployPlanArtifact.schema !== "hardkas.silver.deployPlan") {
|
|
790
|
+
throw new SilverSimulationError(
|
|
791
|
+
"SILVERSCRIPT_SCHEMA_INVALID",
|
|
792
|
+
"Expected hardkas.silver.deployPlan."
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
assertSimnet(deployPlanArtifact.networkId);
|
|
796
|
+
assertHex(deployPlanArtifact.redeemScriptHex, "redeemScriptHex");
|
|
797
|
+
const amountSompi = parseSompi(deployPlanArtifact.amountSompi);
|
|
798
|
+
if (amountSompi <= SILVER_SIMULATOR_FEE_SOMPI) {
|
|
799
|
+
throw new SilverSimulationError(
|
|
800
|
+
"SILVERSCRIPT_AMOUNT_TOO_SMALL",
|
|
801
|
+
"Deploy amount must be greater than the simulator fee."
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
const lock = createKaspaP2shBlake2bLock(deployPlanArtifact.redeemScriptHex);
|
|
805
|
+
if (lock.redeemScriptHash !== deployPlanArtifact.redeemScriptHash) {
|
|
806
|
+
throw new SilverSimulationError(
|
|
807
|
+
"SILVERSCRIPT_REDEEM_HASH_MISMATCH",
|
|
808
|
+
"redeemScriptHash must equal blake2b32(raw redeem script bytes)."
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
if (lock.lockingScriptHex !== deployPlanArtifact.lockingScriptHex) {
|
|
812
|
+
throw new SilverSimulationError(
|
|
813
|
+
"SILVERSCRIPT_LOCKING_SCRIPT_MISMATCH",
|
|
814
|
+
"lockingScriptHex must equal aa20 + redeemScriptHash + 87."
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
if (deployPlanArtifact.scriptPublicKeyVersion !== 0) {
|
|
818
|
+
throw new SilverSimulationError(
|
|
819
|
+
"SILVERSCRIPT_LOCKING_SCRIPT_MISMATCH",
|
|
820
|
+
"scriptPublicKeyVersion must be 0 for this local simulator."
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
const deployPlanHash = artifactHash(deployPlanArtifact);
|
|
824
|
+
const simulatedDeployTxId = deterministicHex({
|
|
825
|
+
kind: "hardkas.silver.simulatedDeployTx",
|
|
826
|
+
deployPlanHash,
|
|
827
|
+
redeemScriptHash: deployPlanArtifact.redeemScriptHash,
|
|
828
|
+
amountSompi: deployPlanArtifact.amountSompi
|
|
829
|
+
});
|
|
830
|
+
const syntheticOutpoint = {
|
|
831
|
+
transactionId: simulatedDeployTxId,
|
|
832
|
+
index: 0
|
|
833
|
+
};
|
|
834
|
+
const draft = {
|
|
835
|
+
schema: "hardkas.silver.deploySimulation",
|
|
836
|
+
hardkasVersion: options.hardkasVersion ?? deployPlanArtifact.hardkasVersion ?? "0.9.1-alpha",
|
|
837
|
+
version: SILVER_SIMULATOR_VERSION,
|
|
838
|
+
hashVersion: CURRENT_HASH_VERSION,
|
|
839
|
+
networkId: "simnet",
|
|
840
|
+
mode: "simulated",
|
|
841
|
+
createdAt: options.createdAt ?? SILVER_SIMULATOR_CREATED_AT,
|
|
842
|
+
deployPlanHash,
|
|
843
|
+
compileArtifactHash: deployPlanArtifact.compileArtifactHash,
|
|
844
|
+
compiledScriptHash: deployPlanArtifact.compiledScriptHash,
|
|
845
|
+
redeemScriptHex: deployPlanArtifact.redeemScriptHex,
|
|
846
|
+
redeemScriptHash: deployPlanArtifact.redeemScriptHash,
|
|
847
|
+
lockingScriptHex: deployPlanArtifact.lockingScriptHex,
|
|
848
|
+
scriptPublicKeyVersion: 0,
|
|
849
|
+
simulatedDeployTxId,
|
|
850
|
+
syntheticOutpoint,
|
|
851
|
+
amountSompi: amountSompi.toString(),
|
|
852
|
+
feeSompi: SILVER_SIMULATOR_FEE_SOMPI.toString(),
|
|
853
|
+
status: "SIMULATED_ACCEPTED",
|
|
854
|
+
lineage: {
|
|
855
|
+
artifactId: `deploy-sim-${deployPlanHash.slice(0, 16)}`,
|
|
856
|
+
lineageId: `silver-lineage-${deployPlanHash.slice(0, 16)}`,
|
|
857
|
+
parentArtifactId: deployPlanHash,
|
|
858
|
+
rootArtifactId: deployPlanHash,
|
|
859
|
+
sequence: 1
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
const receipt = finalizeArtifact(draft, "silverdeploysim");
|
|
863
|
+
const state = createSilverSimulationState();
|
|
864
|
+
state.deployReceipts[receipt.contentHash] = receipt;
|
|
865
|
+
state.utxos[outpointKey(syntheticOutpoint)] = {
|
|
866
|
+
outpoint: syntheticOutpoint,
|
|
867
|
+
deploySimulationHash: receipt.contentHash,
|
|
868
|
+
deployPlanHash,
|
|
869
|
+
redeemScriptHex: deployPlanArtifact.redeemScriptHex,
|
|
870
|
+
redeemScriptHash: deployPlanArtifact.redeemScriptHash,
|
|
871
|
+
lockingScriptHex: deployPlanArtifact.lockingScriptHex,
|
|
872
|
+
amountSompi: amountSompi.toString(),
|
|
873
|
+
networkId: "simnet",
|
|
874
|
+
spent: false
|
|
875
|
+
};
|
|
876
|
+
return {
|
|
877
|
+
receipt,
|
|
878
|
+
state: normalizeState(state)
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
function simulateSilverSpend(spendPlanArtifact, simulatedState, options = {}) {
|
|
882
|
+
if (spendPlanArtifact.schema !== "hardkas.silver.spendPlan") {
|
|
883
|
+
throw new SilverSimulationError(
|
|
884
|
+
"SILVERSCRIPT_SCHEMA_INVALID",
|
|
885
|
+
"Expected hardkas.silver.spendPlan."
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
assertSimnet(spendPlanArtifact.networkId);
|
|
889
|
+
if (simulatedState.networkId !== "simnet") {
|
|
890
|
+
throw new SilverSimulationError(
|
|
891
|
+
"SILVERSCRIPT_NETWORK_UNSUPPORTED",
|
|
892
|
+
"Silver/Toccata simulation state must be simnet."
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
if (!Array.isArray(spendPlanArtifact.expectedOutputs) || spendPlanArtifact.expectedOutputs.length === 0) {
|
|
896
|
+
throw new SilverSimulationError(
|
|
897
|
+
"SILVERSCRIPT_EXPECTED_OUTPUTS_REQUIRED",
|
|
898
|
+
"Spend simulation requires explicit expectedOutputs."
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
const key = outpointKey(spendPlanArtifact.contractUtxoRef);
|
|
902
|
+
const utxo = simulatedState.utxos[key];
|
|
903
|
+
const deployReceipt = simulatedState.deployReceipts[spendPlanArtifact.deployArtifactHash] ?? Object.values(simulatedState.deployReceipts).find((receipt2) => {
|
|
904
|
+
return outpointKey(receipt2.syntheticOutpoint) === key;
|
|
905
|
+
});
|
|
906
|
+
if (!utxo || !deployReceipt) {
|
|
907
|
+
throw new SilverSimulationError(
|
|
908
|
+
"SILVERSCRIPT_DEPLOY_RECEIPT_NOT_FOUND",
|
|
909
|
+
`No deploy simulation receipt exists for ${key}.`
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
if (deployReceipt.networkId !== spendPlanArtifact.networkId || utxo.networkId !== spendPlanArtifact.networkId) {
|
|
913
|
+
throw new SilverSimulationError(
|
|
914
|
+
"SILVERSCRIPT_NETWORK_UNSUPPORTED",
|
|
915
|
+
"Spend plan network must match the deployed synthetic UTXO network."
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
if (utxo.spent || simulatedState.spentOutpoints.includes(key)) {
|
|
919
|
+
throw new SilverSimulationError(
|
|
920
|
+
"SILVERSCRIPT_UTXO_ALREADY_SPENT",
|
|
921
|
+
`Synthetic UTXO ${key} has already been spent.`
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
const pushes = parsePushOnlyScript(spendPlanArtifact.signatureScriptHex);
|
|
925
|
+
const redeemScriptHex = pushes.at(-1);
|
|
926
|
+
if (!redeemScriptHex) {
|
|
927
|
+
throw new SilverSimulationError(
|
|
928
|
+
"SILVERSCRIPT_SIGNATURE_SCRIPT_NOT_PUSH_ONLY",
|
|
929
|
+
"signatureScriptHex must end with a pushed redeem script."
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
if (redeemScriptHex !== utxo.redeemScriptHex.toLowerCase()) {
|
|
933
|
+
throw new SilverSimulationError(
|
|
934
|
+
"SILVERSCRIPT_SIGNATURE_SCRIPT_MISMATCH",
|
|
935
|
+
"Redeem script must be the last push and match the deployed script."
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
const lock = createKaspaP2shBlake2bLock(redeemScriptHex);
|
|
939
|
+
if (lock.redeemScriptHash !== spendPlanArtifact.redeemScriptHash) {
|
|
940
|
+
throw new SilverSimulationError(
|
|
941
|
+
"SILVERSCRIPT_REDEEM_HASH_MISMATCH",
|
|
942
|
+
"Spend redeemScriptHash does not match pushed redeem script."
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
if (lock.lockingScriptHex !== spendPlanArtifact.lockingScriptHex) {
|
|
946
|
+
throw new SilverSimulationError(
|
|
947
|
+
"SILVERSCRIPT_LOCKING_SCRIPT_MISMATCH",
|
|
948
|
+
"Spend lockingScriptHex does not match pushed redeem script."
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
if (calculateSilverArgsHash(spendPlanArtifact.args) !== spendPlanArtifact.argsHash) {
|
|
952
|
+
throw new SilverSimulationError(
|
|
953
|
+
"SILVERSCRIPT_ARGS_HASH_MISMATCH",
|
|
954
|
+
"Spend argsHash does not match args."
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
const argValues = spendPlanArtifact.args.map((arg) => {
|
|
958
|
+
if (arg.type !== "hex" || !isHex(arg.value)) {
|
|
959
|
+
throw new SilverSimulationError(
|
|
960
|
+
"SILVERSCRIPT_INVALID_HEX",
|
|
961
|
+
"Spend args must be hex push values."
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
return arg.value.toLowerCase();
|
|
965
|
+
});
|
|
966
|
+
const pushedArgs = pushes.slice(0, -1);
|
|
967
|
+
if (JSON.stringify(pushedArgs) !== JSON.stringify(argValues)) {
|
|
968
|
+
throw new SilverSimulationError(
|
|
969
|
+
"SILVERSCRIPT_SIGNATURE_SCRIPT_MISMATCH",
|
|
970
|
+
"signatureScriptHex args do not match spend plan args."
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
const inputAmount = BigInt(utxo.amountSompi);
|
|
974
|
+
const outputTotal = spendPlanArtifact.expectedOutputs.reduce((sum, output) => {
|
|
975
|
+
return sum + parseSompi(output.amountSompi);
|
|
976
|
+
}, 0n);
|
|
977
|
+
if (outputTotal <= 0n || outputTotal + SILVER_SIMULATOR_FEE_SOMPI > inputAmount) {
|
|
978
|
+
throw new SilverSimulationError(
|
|
979
|
+
"SILVERSCRIPT_AMOUNT_TOO_SMALL",
|
|
980
|
+
"Synthetic UTXO amount must cover expectedOutputs plus simulator fee."
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
const spendPlanHash = artifactHash(spendPlanArtifact);
|
|
984
|
+
const simulatedSpendTxId = deterministicHex({
|
|
985
|
+
kind: "hardkas.silver.simulatedSpendTx",
|
|
986
|
+
spendPlanHash,
|
|
987
|
+
deploySimulationHash: deployReceipt.contentHash,
|
|
988
|
+
spentOutpoint: key,
|
|
989
|
+
expectedOutputs: spendPlanArtifact.expectedOutputs
|
|
990
|
+
});
|
|
991
|
+
const draft = {
|
|
992
|
+
schema: "hardkas.silver.spendSimulation",
|
|
993
|
+
hardkasVersion: options.hardkasVersion ?? spendPlanArtifact.hardkasVersion ?? "0.9.1-alpha",
|
|
994
|
+
version: SILVER_SIMULATOR_VERSION,
|
|
995
|
+
hashVersion: CURRENT_HASH_VERSION,
|
|
996
|
+
networkId: "simnet",
|
|
997
|
+
mode: "simulated",
|
|
998
|
+
createdAt: options.createdAt ?? SILVER_SIMULATOR_CREATED_AT,
|
|
999
|
+
deploySimulationHash: deployReceipt.contentHash,
|
|
1000
|
+
spendPlanHash,
|
|
1001
|
+
redeemScriptHash: spendPlanArtifact.redeemScriptHash,
|
|
1002
|
+
lockingScriptHex: spendPlanArtifact.lockingScriptHex,
|
|
1003
|
+
signatureScriptHex: spendPlanArtifact.signatureScriptHex,
|
|
1004
|
+
simulatedSpendTxId,
|
|
1005
|
+
spentOutpoint: spendPlanArtifact.contractUtxoRef,
|
|
1006
|
+
expectedOutputs: spendPlanArtifact.expectedOutputs,
|
|
1007
|
+
feeSompi: SILVER_SIMULATOR_FEE_SOMPI.toString(),
|
|
1008
|
+
status: "SIMULATED_ACCEPTED",
|
|
1009
|
+
lineage: {
|
|
1010
|
+
artifactId: `spend-sim-${spendPlanHash.slice(0, 16)}`,
|
|
1011
|
+
lineageId: deployReceipt.lineage.lineageId,
|
|
1012
|
+
parentArtifactId: spendPlanHash,
|
|
1013
|
+
rootArtifactId: deployReceipt.lineage.rootArtifactId,
|
|
1014
|
+
sequence: 2
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
const receipt = finalizeArtifact(draft, "silverspendsim");
|
|
1018
|
+
const nextState = cloneState(simulatedState);
|
|
1019
|
+
nextState.utxos[key] = {
|
|
1020
|
+
...utxo,
|
|
1021
|
+
spent: true,
|
|
1022
|
+
spentByTxId: simulatedSpendTxId
|
|
1023
|
+
};
|
|
1024
|
+
nextState.spentOutpoints = Array.from(
|
|
1025
|
+
/* @__PURE__ */ new Set([...nextState.spentOutpoints, key])
|
|
1026
|
+
).sort();
|
|
1027
|
+
return {
|
|
1028
|
+
receipt,
|
|
1029
|
+
state: normalizeState(nextState)
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
function calculateSilverArgsHash(args) {
|
|
1033
|
+
return createHash2("sha256").update(JSON.stringify(args)).digest("hex");
|
|
1034
|
+
}
|
|
1035
|
+
function outpointKey(outpoint) {
|
|
1036
|
+
return `${outpoint.transactionId}:${outpoint.index}`;
|
|
1037
|
+
}
|
|
1038
|
+
function parsePushOnlyScript(signatureScriptHex) {
|
|
1039
|
+
assertHex(signatureScriptHex, "signatureScriptHex");
|
|
1040
|
+
const pushes = [];
|
|
1041
|
+
let offset = 0;
|
|
1042
|
+
while (offset < signatureScriptHex.length) {
|
|
1043
|
+
const opcode = readByte(signatureScriptHex, offset);
|
|
1044
|
+
offset += 2;
|
|
1045
|
+
let byteCount;
|
|
1046
|
+
if (opcode === 0) {
|
|
1047
|
+
byteCount = 0;
|
|
1048
|
+
} else if (opcode >= 1 && opcode <= 75) {
|
|
1049
|
+
byteCount = opcode;
|
|
1050
|
+
} else if (opcode === 76) {
|
|
1051
|
+
byteCount = readByte(signatureScriptHex, offset);
|
|
1052
|
+
offset += 2;
|
|
1053
|
+
} else if (opcode === 77) {
|
|
1054
|
+
byteCount = readLittleEndian(signatureScriptHex, offset, 2);
|
|
1055
|
+
offset += 4;
|
|
1056
|
+
} else if (opcode === 78) {
|
|
1057
|
+
byteCount = readLittleEndian(signatureScriptHex, offset, 4);
|
|
1058
|
+
offset += 8;
|
|
1059
|
+
} else {
|
|
1060
|
+
throw new SilverSimulationError(
|
|
1061
|
+
"SILVERSCRIPT_SIGNATURE_SCRIPT_NOT_PUSH_ONLY",
|
|
1062
|
+
`Non-push opcode 0x${opcode.toString(16)} encountered.`
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
const hexCount = byteCount * 2;
|
|
1066
|
+
const end = offset + hexCount;
|
|
1067
|
+
if (end > signatureScriptHex.length) {
|
|
1068
|
+
throw new SilverSimulationError(
|
|
1069
|
+
"SILVERSCRIPT_SIGNATURE_SCRIPT_NOT_PUSH_ONLY",
|
|
1070
|
+
"Truncated pushdata in signatureScriptHex."
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
pushes.push(signatureScriptHex.slice(offset, end).toLowerCase());
|
|
1074
|
+
offset = end;
|
|
1075
|
+
}
|
|
1076
|
+
return pushes;
|
|
1077
|
+
}
|
|
1078
|
+
function assertSimnet(networkId) {
|
|
1079
|
+
if (networkId !== "simnet") {
|
|
1080
|
+
throw new SilverSimulationError(
|
|
1081
|
+
"SILVERSCRIPT_NETWORK_UNSUPPORTED",
|
|
1082
|
+
"Silver/Toccata simulator only supports local simnet."
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
function assertHex(value, field) {
|
|
1087
|
+
if (!isHex(value)) {
|
|
1088
|
+
throw new SilverSimulationError(
|
|
1089
|
+
"SILVERSCRIPT_INVALID_HEX",
|
|
1090
|
+
`${field} must be even-length hex.`
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
function isHex(value) {
|
|
1095
|
+
return typeof value === "string" && value.length % 2 === 0 && /^[0-9a-fA-F]*$/.test(value);
|
|
1096
|
+
}
|
|
1097
|
+
function calculateContentHash(value, _version = CURRENT_HASH_VERSION) {
|
|
1098
|
+
return createHash2("sha256").update(canonicalStringify(value)).digest("hex");
|
|
1099
|
+
}
|
|
1100
|
+
function canonicalStringify(value) {
|
|
1101
|
+
if (typeof value === "bigint") {
|
|
1102
|
+
return JSON.stringify(`n:${value.toString()}`);
|
|
1103
|
+
}
|
|
1104
|
+
if (value === null || typeof value !== "object") {
|
|
1105
|
+
return JSON.stringify(value);
|
|
1106
|
+
}
|
|
1107
|
+
if (Array.isArray(value)) {
|
|
1108
|
+
return `[${value.map((item) => canonicalStringify(item)).join(",")}]`;
|
|
1109
|
+
}
|
|
1110
|
+
const exclusions = /* @__PURE__ */ new Set([
|
|
1111
|
+
"contentHash",
|
|
1112
|
+
"artifactId",
|
|
1113
|
+
"hashVersion",
|
|
1114
|
+
"createdAt",
|
|
1115
|
+
"hardkasVersion",
|
|
1116
|
+
"status"
|
|
1117
|
+
]);
|
|
1118
|
+
const record = value;
|
|
1119
|
+
return `{${Object.keys(record).filter((key) => !exclusions.has(key) && record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalStringify(record[key])}`).join(",")}}`;
|
|
1120
|
+
}
|
|
1121
|
+
function parseSompi(value) {
|
|
1122
|
+
if (!/^[0-9]+$/.test(value)) {
|
|
1123
|
+
throw new SilverSimulationError(
|
|
1124
|
+
"SILVERSCRIPT_AMOUNT_TOO_SMALL",
|
|
1125
|
+
"Sompi values must be unsigned integer strings."
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
return BigInt(value);
|
|
1129
|
+
}
|
|
1130
|
+
function artifactHash(value) {
|
|
1131
|
+
return value.contentHash ?? calculateContentHash(value, CURRENT_HASH_VERSION);
|
|
1132
|
+
}
|
|
1133
|
+
function deterministicHex(value) {
|
|
1134
|
+
return calculateContentHash(value, CURRENT_HASH_VERSION);
|
|
1135
|
+
}
|
|
1136
|
+
function finalizeArtifact(artifact, prefix) {
|
|
1137
|
+
const contentHash = calculateContentHash(artifact, CURRENT_HASH_VERSION);
|
|
1138
|
+
return {
|
|
1139
|
+
...artifact,
|
|
1140
|
+
contentHash,
|
|
1141
|
+
artifactId: `${prefix}-${contentHash.slice(0, 16)}`
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
function cloneState(state) {
|
|
1145
|
+
return JSON.parse(JSON.stringify(state));
|
|
1146
|
+
}
|
|
1147
|
+
function normalizeState(state) {
|
|
1148
|
+
return {
|
|
1149
|
+
...state,
|
|
1150
|
+
deployReceipts: Object.fromEntries(
|
|
1151
|
+
Object.entries(state.deployReceipts).sort(([a], [b]) => a.localeCompare(b))
|
|
1152
|
+
),
|
|
1153
|
+
utxos: Object.fromEntries(
|
|
1154
|
+
Object.entries(state.utxos).sort(([a], [b]) => a.localeCompare(b))
|
|
1155
|
+
),
|
|
1156
|
+
spentOutpoints: [...state.spentOutpoints].sort()
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
function readByte(hex, offset) {
|
|
1160
|
+
const raw = hex.slice(offset, offset + 2);
|
|
1161
|
+
if (raw.length !== 2) {
|
|
1162
|
+
throw new SilverSimulationError(
|
|
1163
|
+
"SILVERSCRIPT_SIGNATURE_SCRIPT_NOT_PUSH_ONLY",
|
|
1164
|
+
"Truncated opcode in signatureScriptHex."
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
return parseInt(raw, 16);
|
|
1168
|
+
}
|
|
1169
|
+
function readLittleEndian(hex, offset, byteCount) {
|
|
1170
|
+
const raw = hex.slice(offset, offset + byteCount * 2);
|
|
1171
|
+
if (raw.length !== byteCount * 2) {
|
|
1172
|
+
throw new SilverSimulationError(
|
|
1173
|
+
"SILVERSCRIPT_SIGNATURE_SCRIPT_NOT_PUSH_ONLY",
|
|
1174
|
+
"Truncated pushdata length in signatureScriptHex."
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
const bytes = raw.match(/../g) ?? [];
|
|
1178
|
+
return parseInt(bytes.reverse().join(""), 16);
|
|
1179
|
+
}
|
|
760
1180
|
export {
|
|
761
1181
|
ApproxGhostdagEngine,
|
|
762
1182
|
DEFAULT_K,
|
|
763
1183
|
GENESIS_HASH,
|
|
764
1184
|
GhostdagStore,
|
|
1185
|
+
SILVER_SIMULATOR_CREATED_AT,
|
|
1186
|
+
SILVER_SIMULATOR_FEE_SOMPI,
|
|
1187
|
+
SILVER_SIMULATOR_VERSION,
|
|
1188
|
+
SilverSimulationError,
|
|
765
1189
|
TxSimulator,
|
|
766
1190
|
blockBlueScore,
|
|
767
1191
|
blockBlueWork,
|
|
768
1192
|
blockHash,
|
|
769
1193
|
blockParents,
|
|
1194
|
+
calculateSilverArgsHash,
|
|
770
1195
|
compactFromFull,
|
|
771
1196
|
compareMassProfiles,
|
|
772
1197
|
compareSortableBlocks,
|
|
773
1198
|
computeDagMetrics,
|
|
1199
|
+
createSilverSimulationState,
|
|
774
1200
|
createTraceId,
|
|
775
1201
|
findSelectedParent,
|
|
776
1202
|
formatMassComparison,
|
|
@@ -781,6 +1207,8 @@ export {
|
|
|
781
1207
|
isDagAncestorOf,
|
|
782
1208
|
loadMassSnapshot,
|
|
783
1209
|
orderedMergesetWithoutSelectedParent,
|
|
1210
|
+
outpointKey,
|
|
1211
|
+
parsePushOnlyScript,
|
|
784
1212
|
pastSet,
|
|
785
1213
|
profileAndCompare,
|
|
786
1214
|
profileMass,
|
|
@@ -790,6 +1218,8 @@ export {
|
|
|
790
1218
|
runLinearChain,
|
|
791
1219
|
runWideDag,
|
|
792
1220
|
saveMassSnapshot,
|
|
1221
|
+
simulateSilverDeploy,
|
|
1222
|
+
simulateSilverSpend,
|
|
793
1223
|
sortBlocks,
|
|
794
1224
|
unorderedMergesetWithoutSelectedParent
|
|
795
1225
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hardkas/simulator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1-alpha",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@hardkas/core": "0.
|
|
16
|
-
"@hardkas/tx-builder": "0.
|
|
15
|
+
"@hardkas/core": "0.9.1-alpha",
|
|
16
|
+
"@hardkas/tx-builder": "0.9.1-alpha"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"tsup": "^8.3.5",
|