@hardkas/simulator 0.8.20-alpha → 0.9.0-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 +427 -0
- 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
|
@@ -757,20 +757,443 @@ function reviveSnapshot(snapshot) {
|
|
|
757
757
|
};
|
|
758
758
|
return snapshot;
|
|
759
759
|
}
|
|
760
|
+
|
|
761
|
+
// src/silver-simulator.ts
|
|
762
|
+
import { createHash as createHash2 } from "crypto";
|
|
763
|
+
import { createKaspaP2shBlake2bLock } from "@hardkas/core";
|
|
764
|
+
var SILVER_SIMULATOR_FEE_SOMPI = 2000n;
|
|
765
|
+
var SILVER_SIMULATOR_CREATED_AT = "1970-01-01T00:00:00.000Z";
|
|
766
|
+
var SILVER_SIMULATOR_VERSION = "1.0.0-alpha";
|
|
767
|
+
var CURRENT_HASH_VERSION = 4;
|
|
768
|
+
var SilverSimulationError = class extends Error {
|
|
769
|
+
code;
|
|
770
|
+
constructor(code, message) {
|
|
771
|
+
super(message);
|
|
772
|
+
this.name = "SilverSimulationError";
|
|
773
|
+
this.code = code;
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
function createSilverSimulationState() {
|
|
777
|
+
return {
|
|
778
|
+
schema: "hardkas.silver.simulationState.v1",
|
|
779
|
+
version: SILVER_SIMULATOR_VERSION,
|
|
780
|
+
networkId: "simnet",
|
|
781
|
+
mode: "simulated",
|
|
782
|
+
deployReceipts: {},
|
|
783
|
+
utxos: {},
|
|
784
|
+
spentOutpoints: []
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function simulateSilverDeploy(deployPlanArtifact, options = {}) {
|
|
788
|
+
if (deployPlanArtifact.schema !== "hardkas.silver.deployPlan") {
|
|
789
|
+
throw new SilverSimulationError(
|
|
790
|
+
"SILVERSCRIPT_SCHEMA_INVALID",
|
|
791
|
+
"Expected hardkas.silver.deployPlan."
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
assertSimnet(deployPlanArtifact.networkId);
|
|
795
|
+
assertHex(deployPlanArtifact.redeemScriptHex, "redeemScriptHex");
|
|
796
|
+
const amountSompi = parseSompi(deployPlanArtifact.amountSompi);
|
|
797
|
+
if (amountSompi <= SILVER_SIMULATOR_FEE_SOMPI) {
|
|
798
|
+
throw new SilverSimulationError(
|
|
799
|
+
"SILVERSCRIPT_AMOUNT_TOO_SMALL",
|
|
800
|
+
"Deploy amount must be greater than the simulator fee."
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
const lock = createKaspaP2shBlake2bLock(deployPlanArtifact.redeemScriptHex);
|
|
804
|
+
if (lock.redeemScriptHash !== deployPlanArtifact.redeemScriptHash) {
|
|
805
|
+
throw new SilverSimulationError(
|
|
806
|
+
"SILVERSCRIPT_REDEEM_HASH_MISMATCH",
|
|
807
|
+
"redeemScriptHash must equal blake2b32(raw redeem script bytes)."
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
if (lock.lockingScriptHex !== deployPlanArtifact.lockingScriptHex) {
|
|
811
|
+
throw new SilverSimulationError(
|
|
812
|
+
"SILVERSCRIPT_LOCKING_SCRIPT_MISMATCH",
|
|
813
|
+
"lockingScriptHex must equal aa20 + redeemScriptHash + 87."
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
if (deployPlanArtifact.scriptPublicKeyVersion !== 0) {
|
|
817
|
+
throw new SilverSimulationError(
|
|
818
|
+
"SILVERSCRIPT_LOCKING_SCRIPT_MISMATCH",
|
|
819
|
+
"scriptPublicKeyVersion must be 0 for this local simulator."
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
const deployPlanHash = artifactHash(deployPlanArtifact);
|
|
823
|
+
const simulatedDeployTxId = deterministicHex({
|
|
824
|
+
kind: "hardkas.silver.simulatedDeployTx",
|
|
825
|
+
deployPlanHash,
|
|
826
|
+
redeemScriptHash: deployPlanArtifact.redeemScriptHash,
|
|
827
|
+
amountSompi: deployPlanArtifact.amountSompi
|
|
828
|
+
});
|
|
829
|
+
const syntheticOutpoint = {
|
|
830
|
+
transactionId: simulatedDeployTxId,
|
|
831
|
+
index: 0
|
|
832
|
+
};
|
|
833
|
+
const draft = {
|
|
834
|
+
schema: "hardkas.silver.deploySimulation",
|
|
835
|
+
hardkasVersion: options.hardkasVersion ?? deployPlanArtifact.hardkasVersion ?? "0.9.0-alpha",
|
|
836
|
+
version: SILVER_SIMULATOR_VERSION,
|
|
837
|
+
hashVersion: CURRENT_HASH_VERSION,
|
|
838
|
+
networkId: "simnet",
|
|
839
|
+
mode: "simulated",
|
|
840
|
+
createdAt: options.createdAt ?? SILVER_SIMULATOR_CREATED_AT,
|
|
841
|
+
deployPlanHash,
|
|
842
|
+
compileArtifactHash: deployPlanArtifact.compileArtifactHash,
|
|
843
|
+
compiledScriptHash: deployPlanArtifact.compiledScriptHash,
|
|
844
|
+
redeemScriptHex: deployPlanArtifact.redeemScriptHex,
|
|
845
|
+
redeemScriptHash: deployPlanArtifact.redeemScriptHash,
|
|
846
|
+
lockingScriptHex: deployPlanArtifact.lockingScriptHex,
|
|
847
|
+
scriptPublicKeyVersion: 0,
|
|
848
|
+
simulatedDeployTxId,
|
|
849
|
+
syntheticOutpoint,
|
|
850
|
+
amountSompi: amountSompi.toString(),
|
|
851
|
+
feeSompi: SILVER_SIMULATOR_FEE_SOMPI.toString(),
|
|
852
|
+
status: "SIMULATED_ACCEPTED",
|
|
853
|
+
lineage: {
|
|
854
|
+
artifactId: `deploy-sim-${deployPlanHash.slice(0, 16)}`,
|
|
855
|
+
lineageId: `silver-lineage-${deployPlanHash.slice(0, 16)}`,
|
|
856
|
+
parentArtifactId: deployPlanHash,
|
|
857
|
+
rootArtifactId: deployPlanHash,
|
|
858
|
+
sequence: 1
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
const receipt = finalizeArtifact(draft, "silverdeploysim");
|
|
862
|
+
const state = createSilverSimulationState();
|
|
863
|
+
state.deployReceipts[receipt.contentHash] = receipt;
|
|
864
|
+
state.utxos[outpointKey(syntheticOutpoint)] = {
|
|
865
|
+
outpoint: syntheticOutpoint,
|
|
866
|
+
deploySimulationHash: receipt.contentHash,
|
|
867
|
+
deployPlanHash,
|
|
868
|
+
redeemScriptHex: deployPlanArtifact.redeemScriptHex,
|
|
869
|
+
redeemScriptHash: deployPlanArtifact.redeemScriptHash,
|
|
870
|
+
lockingScriptHex: deployPlanArtifact.lockingScriptHex,
|
|
871
|
+
amountSompi: amountSompi.toString(),
|
|
872
|
+
networkId: "simnet",
|
|
873
|
+
spent: false
|
|
874
|
+
};
|
|
875
|
+
return {
|
|
876
|
+
receipt,
|
|
877
|
+
state: normalizeState(state)
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
function simulateSilverSpend(spendPlanArtifact, simulatedState, options = {}) {
|
|
881
|
+
if (spendPlanArtifact.schema !== "hardkas.silver.spendPlan") {
|
|
882
|
+
throw new SilverSimulationError(
|
|
883
|
+
"SILVERSCRIPT_SCHEMA_INVALID",
|
|
884
|
+
"Expected hardkas.silver.spendPlan."
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
assertSimnet(spendPlanArtifact.networkId);
|
|
888
|
+
if (simulatedState.networkId !== "simnet") {
|
|
889
|
+
throw new SilverSimulationError(
|
|
890
|
+
"SILVERSCRIPT_NETWORK_UNSUPPORTED",
|
|
891
|
+
"Silver/Toccata simulation state must be simnet."
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
if (!Array.isArray(spendPlanArtifact.expectedOutputs) || spendPlanArtifact.expectedOutputs.length === 0) {
|
|
895
|
+
throw new SilverSimulationError(
|
|
896
|
+
"SILVERSCRIPT_EXPECTED_OUTPUTS_REQUIRED",
|
|
897
|
+
"Spend simulation requires explicit expectedOutputs."
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
const key = outpointKey(spendPlanArtifact.contractUtxoRef);
|
|
901
|
+
const utxo = simulatedState.utxos[key];
|
|
902
|
+
const deployReceipt = simulatedState.deployReceipts[spendPlanArtifact.deployArtifactHash] ?? Object.values(simulatedState.deployReceipts).find((receipt2) => {
|
|
903
|
+
return outpointKey(receipt2.syntheticOutpoint) === key;
|
|
904
|
+
});
|
|
905
|
+
if (!utxo || !deployReceipt) {
|
|
906
|
+
throw new SilverSimulationError(
|
|
907
|
+
"SILVERSCRIPT_DEPLOY_RECEIPT_NOT_FOUND",
|
|
908
|
+
`No deploy simulation receipt exists for ${key}.`
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
if (deployReceipt.networkId !== spendPlanArtifact.networkId || utxo.networkId !== spendPlanArtifact.networkId) {
|
|
912
|
+
throw new SilverSimulationError(
|
|
913
|
+
"SILVERSCRIPT_NETWORK_UNSUPPORTED",
|
|
914
|
+
"Spend plan network must match the deployed synthetic UTXO network."
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
if (utxo.spent || simulatedState.spentOutpoints.includes(key)) {
|
|
918
|
+
throw new SilverSimulationError(
|
|
919
|
+
"SILVERSCRIPT_UTXO_ALREADY_SPENT",
|
|
920
|
+
`Synthetic UTXO ${key} has already been spent.`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
const pushes = parsePushOnlyScript(spendPlanArtifact.signatureScriptHex);
|
|
924
|
+
const redeemScriptHex = pushes.at(-1);
|
|
925
|
+
if (!redeemScriptHex) {
|
|
926
|
+
throw new SilverSimulationError(
|
|
927
|
+
"SILVERSCRIPT_SIGNATURE_SCRIPT_NOT_PUSH_ONLY",
|
|
928
|
+
"signatureScriptHex must end with a pushed redeem script."
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
if (redeemScriptHex !== utxo.redeemScriptHex.toLowerCase()) {
|
|
932
|
+
throw new SilverSimulationError(
|
|
933
|
+
"SILVERSCRIPT_SIGNATURE_SCRIPT_MISMATCH",
|
|
934
|
+
"Redeem script must be the last push and match the deployed script."
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
const lock = createKaspaP2shBlake2bLock(redeemScriptHex);
|
|
938
|
+
if (lock.redeemScriptHash !== spendPlanArtifact.redeemScriptHash) {
|
|
939
|
+
throw new SilverSimulationError(
|
|
940
|
+
"SILVERSCRIPT_REDEEM_HASH_MISMATCH",
|
|
941
|
+
"Spend redeemScriptHash does not match pushed redeem script."
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
if (lock.lockingScriptHex !== spendPlanArtifact.lockingScriptHex) {
|
|
945
|
+
throw new SilverSimulationError(
|
|
946
|
+
"SILVERSCRIPT_LOCKING_SCRIPT_MISMATCH",
|
|
947
|
+
"Spend lockingScriptHex does not match pushed redeem script."
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
if (calculateSilverArgsHash(spendPlanArtifact.args) !== spendPlanArtifact.argsHash) {
|
|
951
|
+
throw new SilverSimulationError(
|
|
952
|
+
"SILVERSCRIPT_ARGS_HASH_MISMATCH",
|
|
953
|
+
"Spend argsHash does not match args."
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
const argValues = spendPlanArtifact.args.map((arg) => {
|
|
957
|
+
if (arg.type !== "hex" || !isHex(arg.value)) {
|
|
958
|
+
throw new SilverSimulationError(
|
|
959
|
+
"SILVERSCRIPT_INVALID_HEX",
|
|
960
|
+
"Spend args must be hex push values."
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
return arg.value.toLowerCase();
|
|
964
|
+
});
|
|
965
|
+
const pushedArgs = pushes.slice(0, -1);
|
|
966
|
+
if (JSON.stringify(pushedArgs) !== JSON.stringify(argValues)) {
|
|
967
|
+
throw new SilverSimulationError(
|
|
968
|
+
"SILVERSCRIPT_SIGNATURE_SCRIPT_MISMATCH",
|
|
969
|
+
"signatureScriptHex args do not match spend plan args."
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
const inputAmount = BigInt(utxo.amountSompi);
|
|
973
|
+
const outputTotal = spendPlanArtifact.expectedOutputs.reduce((sum, output) => {
|
|
974
|
+
return sum + parseSompi(output.amountSompi);
|
|
975
|
+
}, 0n);
|
|
976
|
+
if (outputTotal <= 0n || outputTotal + SILVER_SIMULATOR_FEE_SOMPI > inputAmount) {
|
|
977
|
+
throw new SilverSimulationError(
|
|
978
|
+
"SILVERSCRIPT_AMOUNT_TOO_SMALL",
|
|
979
|
+
"Synthetic UTXO amount must cover expectedOutputs plus simulator fee."
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
const spendPlanHash = artifactHash(spendPlanArtifact);
|
|
983
|
+
const simulatedSpendTxId = deterministicHex({
|
|
984
|
+
kind: "hardkas.silver.simulatedSpendTx",
|
|
985
|
+
spendPlanHash,
|
|
986
|
+
deploySimulationHash: deployReceipt.contentHash,
|
|
987
|
+
spentOutpoint: key,
|
|
988
|
+
expectedOutputs: spendPlanArtifact.expectedOutputs
|
|
989
|
+
});
|
|
990
|
+
const draft = {
|
|
991
|
+
schema: "hardkas.silver.spendSimulation",
|
|
992
|
+
hardkasVersion: options.hardkasVersion ?? spendPlanArtifact.hardkasVersion ?? "0.9.0-alpha",
|
|
993
|
+
version: SILVER_SIMULATOR_VERSION,
|
|
994
|
+
hashVersion: CURRENT_HASH_VERSION,
|
|
995
|
+
networkId: "simnet",
|
|
996
|
+
mode: "simulated",
|
|
997
|
+
createdAt: options.createdAt ?? SILVER_SIMULATOR_CREATED_AT,
|
|
998
|
+
deploySimulationHash: deployReceipt.contentHash,
|
|
999
|
+
spendPlanHash,
|
|
1000
|
+
redeemScriptHash: spendPlanArtifact.redeemScriptHash,
|
|
1001
|
+
lockingScriptHex: spendPlanArtifact.lockingScriptHex,
|
|
1002
|
+
signatureScriptHex: spendPlanArtifact.signatureScriptHex,
|
|
1003
|
+
simulatedSpendTxId,
|
|
1004
|
+
spentOutpoint: spendPlanArtifact.contractUtxoRef,
|
|
1005
|
+
expectedOutputs: spendPlanArtifact.expectedOutputs,
|
|
1006
|
+
feeSompi: SILVER_SIMULATOR_FEE_SOMPI.toString(),
|
|
1007
|
+
status: "SIMULATED_ACCEPTED",
|
|
1008
|
+
lineage: {
|
|
1009
|
+
artifactId: `spend-sim-${spendPlanHash.slice(0, 16)}`,
|
|
1010
|
+
lineageId: deployReceipt.lineage.lineageId,
|
|
1011
|
+
parentArtifactId: spendPlanHash,
|
|
1012
|
+
rootArtifactId: deployReceipt.lineage.rootArtifactId,
|
|
1013
|
+
sequence: 2
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
const receipt = finalizeArtifact(draft, "silverspendsim");
|
|
1017
|
+
const nextState = cloneState(simulatedState);
|
|
1018
|
+
nextState.utxos[key] = {
|
|
1019
|
+
...utxo,
|
|
1020
|
+
spent: true,
|
|
1021
|
+
spentByTxId: simulatedSpendTxId
|
|
1022
|
+
};
|
|
1023
|
+
nextState.spentOutpoints = Array.from(/* @__PURE__ */ new Set([...nextState.spentOutpoints, key])).sort();
|
|
1024
|
+
return {
|
|
1025
|
+
receipt,
|
|
1026
|
+
state: normalizeState(nextState)
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
function calculateSilverArgsHash(args) {
|
|
1030
|
+
return createHash2("sha256").update(JSON.stringify(args)).digest("hex");
|
|
1031
|
+
}
|
|
1032
|
+
function outpointKey(outpoint) {
|
|
1033
|
+
return `${outpoint.transactionId}:${outpoint.index}`;
|
|
1034
|
+
}
|
|
1035
|
+
function parsePushOnlyScript(signatureScriptHex) {
|
|
1036
|
+
assertHex(signatureScriptHex, "signatureScriptHex");
|
|
1037
|
+
const pushes = [];
|
|
1038
|
+
let offset = 0;
|
|
1039
|
+
while (offset < signatureScriptHex.length) {
|
|
1040
|
+
const opcode = readByte(signatureScriptHex, offset);
|
|
1041
|
+
offset += 2;
|
|
1042
|
+
let byteCount;
|
|
1043
|
+
if (opcode === 0) {
|
|
1044
|
+
byteCount = 0;
|
|
1045
|
+
} else if (opcode >= 1 && opcode <= 75) {
|
|
1046
|
+
byteCount = opcode;
|
|
1047
|
+
} else if (opcode === 76) {
|
|
1048
|
+
byteCount = readByte(signatureScriptHex, offset);
|
|
1049
|
+
offset += 2;
|
|
1050
|
+
} else if (opcode === 77) {
|
|
1051
|
+
byteCount = readLittleEndian(signatureScriptHex, offset, 2);
|
|
1052
|
+
offset += 4;
|
|
1053
|
+
} else if (opcode === 78) {
|
|
1054
|
+
byteCount = readLittleEndian(signatureScriptHex, offset, 4);
|
|
1055
|
+
offset += 8;
|
|
1056
|
+
} else {
|
|
1057
|
+
throw new SilverSimulationError(
|
|
1058
|
+
"SILVERSCRIPT_SIGNATURE_SCRIPT_NOT_PUSH_ONLY",
|
|
1059
|
+
`Non-push opcode 0x${opcode.toString(16)} encountered.`
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
const hexCount = byteCount * 2;
|
|
1063
|
+
const end = offset + hexCount;
|
|
1064
|
+
if (end > signatureScriptHex.length) {
|
|
1065
|
+
throw new SilverSimulationError(
|
|
1066
|
+
"SILVERSCRIPT_SIGNATURE_SCRIPT_NOT_PUSH_ONLY",
|
|
1067
|
+
"Truncated pushdata in signatureScriptHex."
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
pushes.push(signatureScriptHex.slice(offset, end).toLowerCase());
|
|
1071
|
+
offset = end;
|
|
1072
|
+
}
|
|
1073
|
+
return pushes;
|
|
1074
|
+
}
|
|
1075
|
+
function assertSimnet(networkId) {
|
|
1076
|
+
if (networkId !== "simnet") {
|
|
1077
|
+
throw new SilverSimulationError(
|
|
1078
|
+
"SILVERSCRIPT_NETWORK_UNSUPPORTED",
|
|
1079
|
+
"Silver/Toccata simulator only supports local simnet."
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
function assertHex(value, field) {
|
|
1084
|
+
if (!isHex(value)) {
|
|
1085
|
+
throw new SilverSimulationError(
|
|
1086
|
+
"SILVERSCRIPT_INVALID_HEX",
|
|
1087
|
+
`${field} must be even-length hex.`
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
function isHex(value) {
|
|
1092
|
+
return typeof value === "string" && value.length % 2 === 0 && /^[0-9a-fA-F]*$/.test(value);
|
|
1093
|
+
}
|
|
1094
|
+
function calculateContentHash(value, _version = CURRENT_HASH_VERSION) {
|
|
1095
|
+
return createHash2("sha256").update(canonicalStringify(value)).digest("hex");
|
|
1096
|
+
}
|
|
1097
|
+
function canonicalStringify(value) {
|
|
1098
|
+
if (typeof value === "bigint") {
|
|
1099
|
+
return JSON.stringify(`n:${value.toString()}`);
|
|
1100
|
+
}
|
|
1101
|
+
if (value === null || typeof value !== "object") {
|
|
1102
|
+
return JSON.stringify(value);
|
|
1103
|
+
}
|
|
1104
|
+
if (Array.isArray(value)) {
|
|
1105
|
+
return `[${value.map((item) => canonicalStringify(item)).join(",")}]`;
|
|
1106
|
+
}
|
|
1107
|
+
const exclusions = /* @__PURE__ */ new Set([
|
|
1108
|
+
"contentHash",
|
|
1109
|
+
"artifactId",
|
|
1110
|
+
"hashVersion",
|
|
1111
|
+
"createdAt",
|
|
1112
|
+
"hardkasVersion",
|
|
1113
|
+
"status"
|
|
1114
|
+
]);
|
|
1115
|
+
const record = value;
|
|
1116
|
+
return `{${Object.keys(record).filter((key) => !exclusions.has(key) && record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalStringify(record[key])}`).join(",")}}`;
|
|
1117
|
+
}
|
|
1118
|
+
function parseSompi(value) {
|
|
1119
|
+
if (!/^[0-9]+$/.test(value)) {
|
|
1120
|
+
throw new SilverSimulationError(
|
|
1121
|
+
"SILVERSCRIPT_AMOUNT_TOO_SMALL",
|
|
1122
|
+
"Sompi values must be unsigned integer strings."
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
return BigInt(value);
|
|
1126
|
+
}
|
|
1127
|
+
function artifactHash(value) {
|
|
1128
|
+
return value.contentHash ?? calculateContentHash(value, CURRENT_HASH_VERSION);
|
|
1129
|
+
}
|
|
1130
|
+
function deterministicHex(value) {
|
|
1131
|
+
return calculateContentHash(value, CURRENT_HASH_VERSION);
|
|
1132
|
+
}
|
|
1133
|
+
function finalizeArtifact(artifact, prefix) {
|
|
1134
|
+
const contentHash = calculateContentHash(artifact, CURRENT_HASH_VERSION);
|
|
1135
|
+
return {
|
|
1136
|
+
...artifact,
|
|
1137
|
+
contentHash,
|
|
1138
|
+
artifactId: `${prefix}-${contentHash.slice(0, 16)}`
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
function cloneState(state) {
|
|
1142
|
+
return JSON.parse(JSON.stringify(state));
|
|
1143
|
+
}
|
|
1144
|
+
function normalizeState(state) {
|
|
1145
|
+
return {
|
|
1146
|
+
...state,
|
|
1147
|
+
deployReceipts: Object.fromEntries(
|
|
1148
|
+
Object.entries(state.deployReceipts).sort(([a], [b]) => a.localeCompare(b))
|
|
1149
|
+
),
|
|
1150
|
+
utxos: Object.fromEntries(
|
|
1151
|
+
Object.entries(state.utxos).sort(([a], [b]) => a.localeCompare(b))
|
|
1152
|
+
),
|
|
1153
|
+
spentOutpoints: [...state.spentOutpoints].sort()
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
function readByte(hex, offset) {
|
|
1157
|
+
const raw = hex.slice(offset, offset + 2);
|
|
1158
|
+
if (raw.length !== 2) {
|
|
1159
|
+
throw new SilverSimulationError(
|
|
1160
|
+
"SILVERSCRIPT_SIGNATURE_SCRIPT_NOT_PUSH_ONLY",
|
|
1161
|
+
"Truncated opcode in signatureScriptHex."
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
return parseInt(raw, 16);
|
|
1165
|
+
}
|
|
1166
|
+
function readLittleEndian(hex, offset, byteCount) {
|
|
1167
|
+
const raw = hex.slice(offset, offset + byteCount * 2);
|
|
1168
|
+
if (raw.length !== byteCount * 2) {
|
|
1169
|
+
throw new SilverSimulationError(
|
|
1170
|
+
"SILVERSCRIPT_SIGNATURE_SCRIPT_NOT_PUSH_ONLY",
|
|
1171
|
+
"Truncated pushdata length in signatureScriptHex."
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
const bytes = raw.match(/../g) ?? [];
|
|
1175
|
+
return parseInt(bytes.reverse().join(""), 16);
|
|
1176
|
+
}
|
|
760
1177
|
export {
|
|
761
1178
|
ApproxGhostdagEngine,
|
|
762
1179
|
DEFAULT_K,
|
|
763
1180
|
GENESIS_HASH,
|
|
764
1181
|
GhostdagStore,
|
|
1182
|
+
SILVER_SIMULATOR_CREATED_AT,
|
|
1183
|
+
SILVER_SIMULATOR_FEE_SOMPI,
|
|
1184
|
+
SILVER_SIMULATOR_VERSION,
|
|
1185
|
+
SilverSimulationError,
|
|
765
1186
|
TxSimulator,
|
|
766
1187
|
blockBlueScore,
|
|
767
1188
|
blockBlueWork,
|
|
768
1189
|
blockHash,
|
|
769
1190
|
blockParents,
|
|
1191
|
+
calculateSilverArgsHash,
|
|
770
1192
|
compactFromFull,
|
|
771
1193
|
compareMassProfiles,
|
|
772
1194
|
compareSortableBlocks,
|
|
773
1195
|
computeDagMetrics,
|
|
1196
|
+
createSilverSimulationState,
|
|
774
1197
|
createTraceId,
|
|
775
1198
|
findSelectedParent,
|
|
776
1199
|
formatMassComparison,
|
|
@@ -781,6 +1204,8 @@ export {
|
|
|
781
1204
|
isDagAncestorOf,
|
|
782
1205
|
loadMassSnapshot,
|
|
783
1206
|
orderedMergesetWithoutSelectedParent,
|
|
1207
|
+
outpointKey,
|
|
1208
|
+
parsePushOnlyScript,
|
|
784
1209
|
pastSet,
|
|
785
1210
|
profileAndCompare,
|
|
786
1211
|
profileMass,
|
|
@@ -790,6 +1215,8 @@ export {
|
|
|
790
1215
|
runLinearChain,
|
|
791
1216
|
runWideDag,
|
|
792
1217
|
saveMassSnapshot,
|
|
1218
|
+
simulateSilverDeploy,
|
|
1219
|
+
simulateSilverSpend,
|
|
793
1220
|
sortBlocks,
|
|
794
1221
|
unorderedMergesetWithoutSelectedParent
|
|
795
1222
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hardkas/simulator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0-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.0-alpha",
|
|
16
|
+
"@hardkas/tx-builder": "0.9.0-alpha"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"tsup": "^8.3.5",
|