@offckb/cli 0.4.9-canary-807f2ef.0 → 0.4.9
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 +42 -11
- package/build/index.js +1234 -455
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -353,6 +353,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
353
353
|
});
|
|
354
354
|
};
|
|
355
355
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
356
|
+
exports.runCli = runCli;
|
|
356
357
|
const commander_1 = __nccwpck_require__(53497);
|
|
357
358
|
const node_1 = __nccwpck_require__(998);
|
|
358
359
|
const accounts_1 = __nccwpck_require__(87398);
|
|
@@ -367,6 +368,7 @@ const create_1 = __nccwpck_require__(51534);
|
|
|
367
368
|
const config_1 = __nccwpck_require__(93004);
|
|
368
369
|
const devnet_config_1 = __nccwpck_require__(10015);
|
|
369
370
|
const devnet_fork_1 = __nccwpck_require__(36709);
|
|
371
|
+
const devnet_info_1 = __nccwpck_require__(60905);
|
|
370
372
|
const debug_1 = __nccwpck_require__(39591);
|
|
371
373
|
const system_scripts_1 = __nccwpck_require__(86198);
|
|
372
374
|
const transfer_all_1 = __nccwpck_require__(92039);
|
|
@@ -374,16 +376,26 @@ const gen_1 = __nccwpck_require__(750);
|
|
|
374
376
|
const ckb_debugger_1 = __nccwpck_require__(14815);
|
|
375
377
|
const logger_1 = __nccwpck_require__(65370);
|
|
376
378
|
const status_1 = __nccwpck_require__(71420);
|
|
377
|
-
const validator_1 = __nccwpck_require__(39534);
|
|
378
379
|
const version = (__nccwpck_require__(8330)/* .version */ .rE);
|
|
379
380
|
const description = (__nccwpck_require__(8330)/* .description */ .h_);
|
|
380
381
|
// fix windows terminal encoding of simplified chinese text
|
|
381
382
|
(0, encoding_1.setUTF8EncodingForWindows)();
|
|
382
383
|
const program = new commander_1.Command();
|
|
383
384
|
program.name('offckb').description(description).version(version).enablePositionalOptions();
|
|
385
|
+
let activeCommand = 'offckb';
|
|
386
|
+
function commandPath(command) {
|
|
387
|
+
const names = [];
|
|
388
|
+
let current = command;
|
|
389
|
+
while (current === null || current === void 0 ? void 0 : current.parent) {
|
|
390
|
+
names.unshift(current.name());
|
|
391
|
+
current = current.parent;
|
|
392
|
+
}
|
|
393
|
+
return names.join('.') || 'offckb';
|
|
394
|
+
}
|
|
384
395
|
program.option('--json', 'Output logs in JSON format for agent/programmatic consumption');
|
|
385
|
-
program.hook('preAction', (
|
|
386
|
-
|
|
396
|
+
program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
397
|
+
activeCommand = commandPath(actionCommand);
|
|
398
|
+
const opts = actionCommand.optsWithGlobals();
|
|
387
399
|
if (opts.json) {
|
|
388
400
|
logger_1.logger.setJsonMode(true);
|
|
389
401
|
}
|
|
@@ -420,7 +432,8 @@ program
|
|
|
420
432
|
.option('--target <target>', 'Specify the script binaries file/folder path to deploy', './')
|
|
421
433
|
.option('-o, --output <output>', 'Specify the output folder path for the deployment record files', './deployment')
|
|
422
434
|
.option('-t, --type-id', 'Specify if use upgradable type id to deploy the script')
|
|
423
|
-
.option('--privkey <privkey>', 'Specify the private key to deploy scripts')
|
|
435
|
+
.option('--privkey <privkey>', 'Specify the private key to deploy scripts (visible in shell history)')
|
|
436
|
+
.option('--privkey-file <path>', 'Read the private key from a local file')
|
|
424
437
|
.option('-y, --yes', 'Skip confirmation prompt and deploy immediately')
|
|
425
438
|
.action((options) => (0, deploy_1.deploy)(options));
|
|
426
439
|
program
|
|
@@ -433,8 +446,7 @@ program
|
|
|
433
446
|
.action((option) => __awaiter(void 0, void 0, void 0, function* () {
|
|
434
447
|
// For debugging, tx-hash is required
|
|
435
448
|
if (!option.txHash) {
|
|
436
|
-
|
|
437
|
-
process.exit(1);
|
|
449
|
+
throw new Error('--tx-hash is required for debugging operations');
|
|
438
450
|
}
|
|
439
451
|
const txHash = option.txHash;
|
|
440
452
|
if (option.singleScript) {
|
|
@@ -464,34 +476,43 @@ program
|
|
|
464
476
|
.description('Clean the devnet data, need to stop running the chain first')
|
|
465
477
|
.option('-d, --data', 'Only remove chain data, keep devnet config files')
|
|
466
478
|
.action((options) => (0, clean_1.clean)(options));
|
|
467
|
-
program
|
|
479
|
+
program
|
|
480
|
+
.command('accounts')
|
|
481
|
+
.description('Print account list info')
|
|
482
|
+
.option('--show-private-keys', 'Include built-in dev private keys (hidden by default)')
|
|
483
|
+
.action((options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
484
|
+
yield (0, accounts_1.accounts)(options);
|
|
485
|
+
}));
|
|
468
486
|
program
|
|
469
487
|
.command('deposit [toAddress] [amountInCKB]')
|
|
470
488
|
.description('Deposit CKB tokens to address, only devnet and testnet')
|
|
471
489
|
.option('--network <network>', 'Specify the network to deposit to', 'devnet')
|
|
472
490
|
.option('-r, --proxy-rpc', 'Use Proxy RPC to connect to blockchain')
|
|
473
491
|
.action((toAddress, amountInCKB, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
474
|
-
|
|
492
|
+
yield (0, deposit_1.deposit)(toAddress, amountInCKB, options);
|
|
475
493
|
}));
|
|
476
494
|
program
|
|
477
495
|
.command('transfer [toAddress] [amount]')
|
|
478
496
|
.description('Transfer CKB or UDT tokens to address, only devnet and testnet')
|
|
479
497
|
.option('--network <network>', 'Specify the network to transfer to', 'devnet')
|
|
480
|
-
.option('--privkey <privkey>', 'Specify the private key to transfer')
|
|
481
|
-
.
|
|
498
|
+
.option('--privkey <privkey>', 'Specify the private key to transfer (visible in shell history)')
|
|
499
|
+
.option('--privkey-file <path>', 'Read the private key from a local file')
|
|
500
|
+
.addOption(new commander_1.Option('--udt-kind <kind>', 'Specify the UDT kind').choices(['sudt', 'xudt']))
|
|
482
501
|
.option('--udt-type-args <typeArgs>', 'Specify the UDT type script args to transfer UDT')
|
|
502
|
+
.option('--allow-mainnet-replay-risk', 'Allow a non-built-in key on a Mainnet fork (copied inputs remain blocked)')
|
|
483
503
|
.option('-r, --proxy-rpc', 'Use Proxy RPC to connect to blockchain')
|
|
484
504
|
.action((toAddress, amount, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
485
|
-
|
|
505
|
+
yield (0, transfer_1.transfer)(toAddress, amount, options);
|
|
486
506
|
}));
|
|
487
507
|
program
|
|
488
508
|
.command('transfer-all [toAddress]')
|
|
489
509
|
.description('Transfer All CKB tokens to address, only devnet and testnet')
|
|
490
510
|
.option('--network <network>', 'Specify the network to transfer to', 'devnet')
|
|
491
|
-
.option('--privkey <privkey>', 'Specify the private key
|
|
511
|
+
.option('--privkey <privkey>', 'Specify the private key (visible in shell history)')
|
|
512
|
+
.option('--privkey-file <path>', 'Read the private key from a local file')
|
|
492
513
|
.option('-r, --proxy-rpc', 'Use Proxy RPC to connect to blockchain')
|
|
493
514
|
.action((toAddress, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
494
|
-
|
|
515
|
+
yield (0, transfer_all_1.transferAll)(toAddress, options);
|
|
495
516
|
}));
|
|
496
517
|
program
|
|
497
518
|
.command('balance [toAddress]')
|
|
@@ -501,7 +522,7 @@ program
|
|
|
501
522
|
.option('--udt-type-args <typeArgs>', 'Filter by UDT type script args')
|
|
502
523
|
.option('--no-udt', 'Skip UDT balance scan')
|
|
503
524
|
.action((toAddress, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
504
|
-
|
|
525
|
+
yield (0, balance_1.balanceOf)(toAddress, options);
|
|
505
526
|
}));
|
|
506
527
|
const udtCommand = program.command('udt').description('UDT token commands');
|
|
507
528
|
udtCommand
|
|
@@ -511,9 +532,10 @@ udtCommand
|
|
|
511
532
|
.addOption(new commander_1.Option('--udt-kind <kind>', 'Specify the UDT kind').choices(['sudt', 'xudt']).default('sudt'))
|
|
512
533
|
.option('--type-args <typeArgs>', 'Specify the UDT type script args (xudt only; defaults to signer lock hash)')
|
|
513
534
|
.option('--to <toAddress>', 'Specify the receiver address (defaults to signer)')
|
|
514
|
-
.option('--privkey <privkey>', 'Specify the private key to issue UDT')
|
|
535
|
+
.option('--privkey <privkey>', 'Specify the private key to issue UDT (visible in shell history)')
|
|
536
|
+
.option('--privkey-file <path>', 'Read the private key from a local file')
|
|
515
537
|
.action((amount, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
516
|
-
|
|
538
|
+
yield (0, udt_1.udtIssue)(amount, options);
|
|
517
539
|
}));
|
|
518
540
|
udtCommand
|
|
519
541
|
.command('destroy <amount>')
|
|
@@ -521,9 +543,10 @@ udtCommand
|
|
|
521
543
|
.option('--network <network>', 'Specify the network', 'devnet')
|
|
522
544
|
.addOption(new commander_1.Option('--udt-kind <kind>', 'Specify the UDT kind').choices(['sudt', 'xudt']).default('sudt'))
|
|
523
545
|
.requiredOption('--type-args <typeArgs>', 'Specify the UDT type script args')
|
|
524
|
-
.option('--privkey <privkey>', 'Specify the private key to destroy UDT')
|
|
546
|
+
.option('--privkey <privkey>', 'Specify the private key to destroy UDT (visible in shell history)')
|
|
547
|
+
.option('--privkey-file <path>', 'Read the private key from a local file')
|
|
525
548
|
.action((amount, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
526
|
-
|
|
549
|
+
yield (0, udt_1.udtDestroy)(amount, options);
|
|
527
550
|
}));
|
|
528
551
|
program
|
|
529
552
|
.command('debugger')
|
|
@@ -537,10 +560,11 @@ program
|
|
|
537
560
|
program
|
|
538
561
|
.command('status')
|
|
539
562
|
.description('Show ckb-tui status interface')
|
|
540
|
-
.
|
|
563
|
+
.addOption(new commander_1.Option('--network <network>', 'Specify the network whose node status to monitor')
|
|
564
|
+
.choices(['devnet', 'testnet', 'mainnet'])
|
|
565
|
+
.default('devnet'))
|
|
541
566
|
.action((option) => __awaiter(void 0, void 0, void 0, function* () {
|
|
542
|
-
(0,
|
|
543
|
-
return yield (0, status_1.status)({ network: option.network });
|
|
567
|
+
yield (0, status_1.status)({ network: option.network });
|
|
544
568
|
}));
|
|
545
569
|
program
|
|
546
570
|
.command('config <action> [item] [value]')
|
|
@@ -552,18 +576,77 @@ devnetCommand
|
|
|
552
576
|
.description('Open a full-screen editor to tweak devnet config files')
|
|
553
577
|
.option('-s, --set <key=value>', 'Set a devnet config field non-interactively (repeatable), e.g. --set ckb.logger.filter=info', (value, previous = []) => [...previous, value], [])
|
|
554
578
|
.action(devnet_config_1.devnetConfig);
|
|
579
|
+
devnetCommand
|
|
580
|
+
.command('info')
|
|
581
|
+
.description('Show fork metadata and node/indexer readiness')
|
|
582
|
+
.action(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
583
|
+
yield (0, devnet_info_1.devnetInfo)();
|
|
584
|
+
}));
|
|
555
585
|
devnetCommand
|
|
556
586
|
.command('fork')
|
|
557
587
|
.description('Fork an existing mainnet/testnet chain data directory into the local devnet')
|
|
558
|
-
.
|
|
588
|
+
.option('--from <dir>', 'Path to the source CKB node directory used with `ckb -C`')
|
|
559
589
|
.option('--source <source>', 'Source chain: mainnet or testnet (auto-detected from the source ckb.toml when omitted)')
|
|
560
590
|
.option('--spec-file <path>', 'Use a local chain spec file instead of downloading it')
|
|
561
591
|
.option('--force', 'Replace the existing devnet (or a previous fork)')
|
|
592
|
+
.option('--migrate', 'Migrate only the copied database when the selected CKB version requires it')
|
|
593
|
+
.option('--dry-run', 'Run source/spec/database preflight without replacing the current devnet')
|
|
562
594
|
.action(devnet_fork_1.devnetFork);
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
if (!
|
|
566
|
-
|
|
595
|
+
function normalizeGlobalJsonFlag(argv) {
|
|
596
|
+
const jsonRequested = argv.slice(2).includes('--json');
|
|
597
|
+
if (!jsonRequested)
|
|
598
|
+
return argv;
|
|
599
|
+
return [argv[0], argv[1], '--json', ...argv.slice(2).filter((arg) => arg !== '--json')];
|
|
600
|
+
}
|
|
601
|
+
function installBrokenPipeHandlers() {
|
|
602
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
603
|
+
stream.on('error', (error) => {
|
|
604
|
+
if (error.code === 'EPIPE') {
|
|
605
|
+
process.exit(0);
|
|
606
|
+
}
|
|
607
|
+
throw error;
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function configureCommanderErrors(command) {
|
|
612
|
+
command.exitOverride();
|
|
613
|
+
command.configureOutput({
|
|
614
|
+
writeErr: (text) => {
|
|
615
|
+
if (!logger_1.logger.isJsonMode())
|
|
616
|
+
process.stderr.write(text);
|
|
617
|
+
},
|
|
618
|
+
});
|
|
619
|
+
command.commands.forEach(configureCommanderErrors);
|
|
620
|
+
}
|
|
621
|
+
function runCli() {
|
|
622
|
+
return __awaiter(this, arguments, void 0, function* (argv = process.argv) {
|
|
623
|
+
installBrokenPipeHandlers();
|
|
624
|
+
const normalizedArgv = normalizeGlobalJsonFlag(argv);
|
|
625
|
+
if (normalizedArgv.includes('--json'))
|
|
626
|
+
logger_1.logger.setJsonMode(true);
|
|
627
|
+
if (!normalizedArgv.slice(2).length) {
|
|
628
|
+
program.outputHelp();
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
configureCommanderErrors(program);
|
|
632
|
+
try {
|
|
633
|
+
yield program.parseAsync(normalizedArgv);
|
|
634
|
+
if (logger_1.logger.isJsonMode() && !logger_1.logger.hasResult() && (process.exitCode == null || process.exitCode === 0)) {
|
|
635
|
+
logger_1.logger.result({ command: activeCommand, completed: true });
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
if (error instanceof commander_1.CommanderError && error.exitCode === 0)
|
|
640
|
+
return;
|
|
641
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
642
|
+
const code = error instanceof commander_1.CommanderError ? error.code : 'COMMAND_FAILED';
|
|
643
|
+
logger_1.logger.failure(code, message);
|
|
644
|
+
process.exitCode = error instanceof commander_1.CommanderError ? error.exitCode : 1;
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
if (require.main === require.cache[eval('__filename')]) {
|
|
649
|
+
void runCli();
|
|
567
650
|
}
|
|
568
651
|
|
|
569
652
|
|
|
@@ -574,40 +657,90 @@ if (!process.argv.slice(2).length) {
|
|
|
574
657
|
|
|
575
658
|
"use strict";
|
|
576
659
|
|
|
660
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
661
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
662
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
663
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
664
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
665
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
666
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
667
|
+
});
|
|
668
|
+
};
|
|
577
669
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
578
670
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
579
671
|
};
|
|
580
672
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
581
673
|
exports.accounts = accounts;
|
|
582
674
|
const account_json_1 = __importDefault(__nccwpck_require__(13849));
|
|
675
|
+
const core_1 = __nccwpck_require__(39842);
|
|
676
|
+
const setting_1 = __nccwpck_require__(25546);
|
|
677
|
+
const fork_1 = __nccwpck_require__(77912);
|
|
678
|
+
const readiness_1 = __nccwpck_require__(19326);
|
|
679
|
+
const base_1 = __nccwpck_require__(69951);
|
|
583
680
|
const logger_1 = __nccwpck_require__(65370);
|
|
584
681
|
function accounts() {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
`privkey: ${account.privkey}`,
|
|
600
|
-
`pubkey: ${account.pubkey}`,
|
|
601
|
-
`lock_arg: ${account.lockScript.args}`,
|
|
602
|
-
'lockScript:',
|
|
603
|
-
` codeHash: ${account.lockScript.codeHash}`,
|
|
604
|
-
` hashType: ${account.lockScript.hashType}`,
|
|
605
|
-
` args: ${account.lockScript.args}`,
|
|
682
|
+
return __awaiter(this, arguments, void 0, function* (options = {}) {
|
|
683
|
+
const settings = (0, setting_1.readSettings)();
|
|
684
|
+
const fork = (0, fork_1.readForkState)(settings.devnet.configPath);
|
|
685
|
+
const isMainnetFork = (fork === null || fork === void 0 ? void 0 : fork.source) === 'mainnet';
|
|
686
|
+
const client = isMainnetFork
|
|
687
|
+
? new core_1.ccc.ClientPublicMainnet({ url: settings.devnet.rpcUrl, fallbacks: [] })
|
|
688
|
+
: new core_1.ccc.ClientPublicTestnet({ url: settings.devnet.rpcUrl, fallbacks: [] });
|
|
689
|
+
const context = fork ? `DEVNET (fork of ${fork.source.toUpperCase()})` : 'DEVNET';
|
|
690
|
+
const readiness = fork ? yield (0, readiness_1.warnIfForkIndexerIsBehind)(base_1.Network.devnet) : undefined;
|
|
691
|
+
const canReadSpendableBalance = (readiness === null || readiness === void 0 ? void 0 : readiness.ready) === true && readiness.indexerTip != null && readiness.indexerLag === BigInt(0);
|
|
692
|
+
logger_1.logger.warn([
|
|
693
|
+
'#### All Accounts are for test and develop only ####'.toUpperCase(),
|
|
694
|
+
"#### DON'T use these accounts on Mainnet ####".toUpperCase(),
|
|
695
|
+
'#### Otherwise You will lose your money ####'.toUpperCase(),
|
|
606
696
|
'',
|
|
607
|
-
];
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
697
|
+
]);
|
|
698
|
+
if (fork) {
|
|
699
|
+
logger_1.logger.info([
|
|
700
|
+
`Print account list for ${context}. Addresses use the source-chain prefix.`,
|
|
701
|
+
'Forked devnets do not include the standard offckb genesis allocation; funds come from fork-mined cellbase cells.',
|
|
702
|
+
'Run `offckb devnet info` before trusting balances while the indexer catches up.',
|
|
703
|
+
'',
|
|
704
|
+
]);
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
logger_1.logger.info([
|
|
708
|
+
'Print account list, each account is funded with 42_000_000_00000000 capacity in the devnet genesis block.',
|
|
709
|
+
'',
|
|
710
|
+
]);
|
|
711
|
+
}
|
|
712
|
+
const resolvedAccounts = yield Promise.all(account_json_1.default.map((account, index) => __awaiter(this, void 0, void 0, function* () {
|
|
713
|
+
const script = core_1.ccc.Script.from(account.lockScript);
|
|
714
|
+
const address = core_1.ccc.Address.fromScript(script, client).toString();
|
|
715
|
+
const spendableCkb = canReadSpendableBalance
|
|
716
|
+
? core_1.ccc.fixedPointToString(yield client.getBalanceSingle(script))
|
|
717
|
+
: undefined;
|
|
718
|
+
return Object.assign(Object.assign(Object.assign({ index,
|
|
719
|
+
address }, (spendableCkb == null ? {} : { spendableCkb })), (options.showPrivateKeys ? { privkey: account.privkey } : {})), { pubkey: account.pubkey, lockArg: account.lockScript.args, lockScript: account.lockScript });
|
|
720
|
+
})));
|
|
721
|
+
const accountDetails = resolvedAccounts.map((account) => {
|
|
722
|
+
return [
|
|
723
|
+
`- "#": ${account.index}`,
|
|
724
|
+
`address: ${account.address}`,
|
|
725
|
+
...('spendableCkb' in account ? [`spendable_ckb: ${account.spendableCkb}`] : []),
|
|
726
|
+
...(options.showPrivateKeys ? [`privkey: ${account.privkey}`] : []),
|
|
727
|
+
`pubkey: ${account.pubkey}`,
|
|
728
|
+
`lock_arg: ${account.lockArg}`,
|
|
729
|
+
'lockScript:',
|
|
730
|
+
` codeHash: ${account.lockScript.codeHash}`,
|
|
731
|
+
` hashType: ${account.lockScript.hashType}`,
|
|
732
|
+
` args: ${account.lockScript.args}`,
|
|
733
|
+
'',
|
|
734
|
+
];
|
|
735
|
+
});
|
|
736
|
+
accountDetails.forEach((details, _index) => {
|
|
737
|
+
logger_1.logger.info(details);
|
|
738
|
+
});
|
|
739
|
+
if (!options.showPrivateKeys) {
|
|
740
|
+
logger_1.logger.info('Private keys are hidden by default. Use --show-private-keys only in a trusted local terminal.');
|
|
741
|
+
}
|
|
742
|
+
logger_1.logger.result({ command: 'accounts', context, forked: Boolean(fork), accounts: resolvedAccounts });
|
|
743
|
+
return resolvedAccounts;
|
|
611
744
|
});
|
|
612
745
|
}
|
|
613
746
|
|
|
@@ -634,10 +767,12 @@ const ckb_1 = __nccwpck_require__(83748);
|
|
|
634
767
|
const validator_1 = __nccwpck_require__(39534);
|
|
635
768
|
const base_1 = __nccwpck_require__(69951);
|
|
636
769
|
const logger_1 = __nccwpck_require__(65370);
|
|
770
|
+
const readiness_1 = __nccwpck_require__(19326);
|
|
637
771
|
function balanceOf(address_1) {
|
|
638
772
|
return __awaiter(this, arguments, void 0, function* (address, opt = { network: base_1.Network.devnet }) {
|
|
639
773
|
const network = opt.network;
|
|
640
774
|
(0, validator_1.validateNetworkOpt)(network);
|
|
775
|
+
yield (0, readiness_1.warnIfForkIndexerIsBehind)(network);
|
|
641
776
|
const ckb = new ckb_1.CKB({ network });
|
|
642
777
|
const [balanceInCKB, udtBalances] = yield Promise.all([
|
|
643
778
|
ckb.balance(address),
|
|
@@ -651,7 +786,9 @@ function balanceOf(address_1) {
|
|
|
651
786
|
logger_1.logger.info(` ${udt.kind} (args=${udt.args}): ${udt.balance}`);
|
|
652
787
|
}
|
|
653
788
|
}
|
|
654
|
-
|
|
789
|
+
const result = { command: 'balance', network, address, ckb: balanceInCKB, udt: filtered };
|
|
790
|
+
logger_1.logger.result(result);
|
|
791
|
+
return result;
|
|
655
792
|
});
|
|
656
793
|
}
|
|
657
794
|
function filterUdtBalances(balances, opt) {
|
|
@@ -695,8 +832,7 @@ function clean(options) {
|
|
|
695
832
|
logger_1.logger.info(`Chain data cleaned. Devnet config files preserved.`);
|
|
696
833
|
}
|
|
697
834
|
catch (error) {
|
|
698
|
-
|
|
699
|
-
logger_1.logger.error(error.message);
|
|
835
|
+
throw new Error(`Failed to clean chain data. Did you stop the chain first? ${error.message}`);
|
|
700
836
|
}
|
|
701
837
|
}
|
|
702
838
|
else {
|
|
@@ -712,8 +848,7 @@ function clean(options) {
|
|
|
712
848
|
logger_1.logger.info(`Chain data cleaned.`);
|
|
713
849
|
}
|
|
714
850
|
catch (error) {
|
|
715
|
-
|
|
716
|
-
logger_1.logger.error(error.message);
|
|
851
|
+
throw new Error(`Failed to clean devnet data. Did you stop the chain first? ${error.message}`);
|
|
717
852
|
}
|
|
718
853
|
}
|
|
719
854
|
else {
|
|
@@ -770,8 +905,7 @@ function Config(action, item, value) {
|
|
|
770
905
|
const settings = (0, setting_1.readSettings)();
|
|
771
906
|
const proxy = settings.proxy;
|
|
772
907
|
if (proxy == null) {
|
|
773
|
-
logger_1.logger.info(`No Proxy.`);
|
|
774
|
-
process.exit(0);
|
|
908
|
+
return logger_1.logger.info(`No Proxy.`);
|
|
775
909
|
}
|
|
776
910
|
return logger_1.logger.info(`${request_1.Request.proxyConfigToUrl(proxy)}`);
|
|
777
911
|
}
|
|
@@ -796,7 +930,7 @@ function Config(action, item, value) {
|
|
|
796
930
|
return (0, setting_1.writeSettings)(settings);
|
|
797
931
|
}
|
|
798
932
|
catch (error) {
|
|
799
|
-
|
|
933
|
+
throw new Error(`invalid proxyURL: ${error.message}`);
|
|
800
934
|
}
|
|
801
935
|
}
|
|
802
936
|
case ConfigItem.ckbVersion: {
|
|
@@ -808,11 +942,11 @@ function Config(action, item, value) {
|
|
|
808
942
|
return (0, setting_1.writeSettings)(settings);
|
|
809
943
|
}
|
|
810
944
|
else {
|
|
811
|
-
|
|
945
|
+
throw new Error(`invalid version value, ${value}. Check available versions on https://github.com/nervosnetwork/ckb/tags`);
|
|
812
946
|
}
|
|
813
947
|
}
|
|
814
948
|
catch (error) {
|
|
815
|
-
|
|
949
|
+
throw new Error(`invalid version value: ${error.message}`);
|
|
816
950
|
}
|
|
817
951
|
}
|
|
818
952
|
default:
|
|
@@ -932,8 +1066,7 @@ function createScriptProject(name_1) {
|
|
|
932
1066
|
const fullProjectPath = path.resolve(projectPath);
|
|
933
1067
|
// Check if directory already exists
|
|
934
1068
|
if (fs.existsSync(fullProjectPath)) {
|
|
935
|
-
|
|
936
|
-
process.exit(1);
|
|
1069
|
+
throw new Error(`Directory '${projectPath}' already exists!`);
|
|
937
1070
|
}
|
|
938
1071
|
logger_1.logger.info([
|
|
939
1072
|
'📝 Project details:',
|
|
@@ -954,8 +1087,7 @@ function createScriptProject(name_1) {
|
|
|
954
1087
|
// Get template directory (adjust path based on whether we're running from source or built)
|
|
955
1088
|
const templateDir = possiblePaths.find((p) => fs.existsSync(p)) || possiblePaths[0];
|
|
956
1089
|
if (!fs.existsSync(templateDir)) {
|
|
957
|
-
|
|
958
|
-
process.exit(1);
|
|
1090
|
+
throw new Error(`Template directory not found: ${templateDir}`);
|
|
959
1091
|
}
|
|
960
1092
|
// Initialize template processor
|
|
961
1093
|
const processor = new processor_1.TemplateProcessor(templateDir);
|
|
@@ -1031,8 +1163,7 @@ function createScriptProject(name_1) {
|
|
|
1031
1163
|
}
|
|
1032
1164
|
}
|
|
1033
1165
|
catch (error) {
|
|
1034
|
-
|
|
1035
|
-
process.exit(1);
|
|
1166
|
+
throw new Error(`Failed to create project: ${error.message}`);
|
|
1036
1167
|
}
|
|
1037
1168
|
});
|
|
1038
1169
|
}
|
|
@@ -1257,8 +1388,7 @@ function buildContract(jsFile, outputFile, jsVmPath) {
|
|
|
1257
1388
|
logger_1.logger.success(`✅ Contract built successfully: ${outputFile}`);
|
|
1258
1389
|
}
|
|
1259
1390
|
catch (error) {
|
|
1260
|
-
|
|
1261
|
-
process.exit(1);
|
|
1391
|
+
throw new Error(`Build failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1262
1392
|
}
|
|
1263
1393
|
});
|
|
1264
1394
|
}
|
|
@@ -1294,14 +1424,19 @@ const deploy_1 = __nccwpck_require__(45615);
|
|
|
1294
1424
|
const ckb_1 = __nccwpck_require__(83748);
|
|
1295
1425
|
const prompts_1 = __nccwpck_require__(18167);
|
|
1296
1426
|
const logger_1 = __nccwpck_require__(65370);
|
|
1427
|
+
const private_key_1 = __nccwpck_require__(26241);
|
|
1428
|
+
const readiness_1 = __nccwpck_require__(19326);
|
|
1429
|
+
const fork_safety_1 = __nccwpck_require__(47625);
|
|
1297
1430
|
function deploy() {
|
|
1298
1431
|
return __awaiter(this, arguments, void 0, function* (opt = { network: base_1.Network.devnet, typeId: false, target: './', output: './deployment' }) {
|
|
1299
1432
|
var _a;
|
|
1300
1433
|
const network = opt.network;
|
|
1301
1434
|
(0, validator_1.validateNetworkOpt)(network);
|
|
1302
|
-
const ckb = new ckb_1.CKB({ network });
|
|
1303
1435
|
// we use deployerAccount to deploy contract by default
|
|
1304
|
-
const privateKey = opt
|
|
1436
|
+
const privateKey = (0, private_key_1.resolvePrivateKey)(opt, account_1.deployerAccount.privkey);
|
|
1437
|
+
(0, fork_safety_1.warnIfMainnetForkSigning)(network, privateKey);
|
|
1438
|
+
yield (0, readiness_1.warnIfForkIndexerIsBehind)(network);
|
|
1439
|
+
const ckb = new ckb_1.CKB({ network });
|
|
1305
1440
|
const enableTypeId = (_a = opt.typeId) !== null && _a !== void 0 ? _a : false;
|
|
1306
1441
|
const targetFolder = opt.target;
|
|
1307
1442
|
const output = opt.output;
|
|
@@ -1325,7 +1460,7 @@ function deploy() {
|
|
|
1325
1460
|
'',
|
|
1326
1461
|
` 📁 Deployment artifacts will be saved to: ${outputFolder}`,
|
|
1327
1462
|
` 🌐 Network: ${network}`,
|
|
1328
|
-
` 🔑 Using ${opt.privkey ? 'custom' : 'default'} private key`,
|
|
1463
|
+
` 🔑 Using ${opt.privkey || opt.privkeyFile || process.env.OFFCKB_PRIVATE_KEY ? 'custom' : 'default'} private key`,
|
|
1329
1464
|
` 🔄 Type ID: ${enableTypeId ? 'enabled (upgradable)' : 'disabled (immutable)'}`,
|
|
1330
1465
|
]);
|
|
1331
1466
|
// Skip confirmation if -y flag is provided
|
|
@@ -1371,22 +1506,40 @@ const link_1 = __nccwpck_require__(27814);
|
|
|
1371
1506
|
const validator_1 = __nccwpck_require__(39534);
|
|
1372
1507
|
const request_1 = __nccwpck_require__(65897);
|
|
1373
1508
|
const logger_1 = __nccwpck_require__(65370);
|
|
1509
|
+
const readiness_1 = __nccwpck_require__(19326);
|
|
1510
|
+
const fork_safety_1 = __nccwpck_require__(47625);
|
|
1511
|
+
const TESTNET_FAUCET_CLAIM_AMOUNT = '10000';
|
|
1374
1512
|
function deposit(toAddress_1, amountInCKB_1) {
|
|
1375
1513
|
return __awaiter(this, arguments, void 0, function* (toAddress, amountInCKB, opt = { network: base_1.Network.devnet }) {
|
|
1376
1514
|
const network = opt.network;
|
|
1377
1515
|
(0, validator_1.validateNetworkOpt)(network);
|
|
1378
1516
|
const ckb = new ckb_1.CKB({ network });
|
|
1379
1517
|
if (network === 'testnet') {
|
|
1380
|
-
|
|
1518
|
+
const txHash = yield depositFromTestnetFaucet(toAddress, ckb);
|
|
1519
|
+
logger_1.logger.result({
|
|
1520
|
+
command: 'deposit',
|
|
1521
|
+
network,
|
|
1522
|
+
source: 'fixed-testnet-faucet-claim',
|
|
1523
|
+
requestedAmount: amountInCKB,
|
|
1524
|
+
faucetClaimAmount: TESTNET_FAUCET_CLAIM_AMOUNT,
|
|
1525
|
+
toAddress,
|
|
1526
|
+
txHash,
|
|
1527
|
+
});
|
|
1528
|
+
return txHash;
|
|
1381
1529
|
}
|
|
1382
1530
|
// deposit from devnet miner
|
|
1383
1531
|
const privateKey = account_1.ckbDevnetMinerAccount.privkey;
|
|
1532
|
+
const rejectInputsAtOrBeforeBlock = (0, fork_safety_1.validateMainnetForkSigning)(network, privateKey);
|
|
1533
|
+
yield (0, readiness_1.warnIfForkIndexerIsBehind)(network);
|
|
1384
1534
|
const txHash = yield ckb.transfer({
|
|
1385
1535
|
toAddress,
|
|
1386
1536
|
privateKey,
|
|
1387
1537
|
amountInCKB,
|
|
1538
|
+
rejectInputsAtOrBeforeBlock,
|
|
1388
1539
|
});
|
|
1389
1540
|
logger_1.logger.info('tx hash: ', txHash);
|
|
1541
|
+
logger_1.logger.result({ command: 'deposit', network, amount: amountInCKB, toAddress, txHash });
|
|
1542
|
+
return txHash;
|
|
1390
1543
|
});
|
|
1391
1544
|
}
|
|
1392
1545
|
function depositFromTestnetFaucet(ckbAddress, ckb) {
|
|
@@ -1412,6 +1565,7 @@ function depositFromTestnetFaucet(ckbAddress, ckb) {
|
|
|
1412
1565
|
}
|
|
1413
1566
|
const txHash = yield ckb.transferAll({ privateKey: randomAccountPrivateKey, toAddress: ckbAddress });
|
|
1414
1567
|
logger_1.logger.info(`Done, check ${(0, link_1.buildTestnetTxLink)(txHash)} for details.`);
|
|
1568
|
+
return txHash;
|
|
1415
1569
|
});
|
|
1416
1570
|
}
|
|
1417
1571
|
function sendClaimRequest(toAddress) {
|
|
@@ -1427,7 +1581,7 @@ function sendClaimRequest(toAddress) {
|
|
|
1427
1581
|
const body = JSON.stringify({
|
|
1428
1582
|
claim_event: {
|
|
1429
1583
|
address_hash: toAddress,
|
|
1430
|
-
amount:
|
|
1584
|
+
amount: TESTNET_FAUCET_CLAIM_AMOUNT, // unit: CKB
|
|
1431
1585
|
},
|
|
1432
1586
|
});
|
|
1433
1587
|
const config = {
|
|
@@ -1509,12 +1663,8 @@ function devnetConfig() {
|
|
|
1509
1663
|
return;
|
|
1510
1664
|
}
|
|
1511
1665
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
logger_1.logger.info(' offckb devnet config --set ckb.logger.filter=info');
|
|
1515
|
-
logger_1.logger.info(' offckb devnet config --set miner.client.poll_interval=1500');
|
|
1516
|
-
process.exitCode = 1;
|
|
1517
|
-
return;
|
|
1666
|
+
throw new Error('Interactive devnet config editor requires a TTY terminal. Use non-interactive mode, e.g. ' +
|
|
1667
|
+
'`offckb devnet config --set ckb.logger.filter=info`.');
|
|
1518
1668
|
}
|
|
1519
1669
|
const isSaved = yield (0, devnet_config_tui_1.runDevnetConfigTui)(editor, configPath);
|
|
1520
1670
|
if (isSaved) {
|
|
@@ -1525,12 +1675,11 @@ function devnetConfig() {
|
|
|
1525
1675
|
logger_1.logger.info('No changes saved.');
|
|
1526
1676
|
}
|
|
1527
1677
|
catch (error) {
|
|
1528
|
-
|
|
1529
|
-
logger_1.logger.error(message);
|
|
1678
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
1530
1679
|
if (error instanceof config_editor_1.InitializationError) {
|
|
1531
|
-
|
|
1680
|
+
message += ' Tip: run `offckb node` once to initialize devnet config files first.';
|
|
1532
1681
|
}
|
|
1533
|
-
|
|
1682
|
+
throw new Error(message);
|
|
1534
1683
|
}
|
|
1535
1684
|
});
|
|
1536
1685
|
}
|
|
@@ -1555,20 +1704,92 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
1555
1704
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1556
1705
|
exports.devnetFork = devnetFork;
|
|
1557
1706
|
const fork_1 = __nccwpck_require__(77912);
|
|
1558
|
-
const logger_1 = __nccwpck_require__(65370);
|
|
1559
1707
|
function devnetFork(options) {
|
|
1560
1708
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1561
1709
|
if (options.source && options.source !== 'mainnet' && options.source !== 'testnet') {
|
|
1562
|
-
|
|
1563
|
-
process.exit(1);
|
|
1564
|
-
}
|
|
1565
|
-
try {
|
|
1566
|
-
yield (0, fork_1.forkDevnet)(options);
|
|
1567
|
-
}
|
|
1568
|
-
catch (error) {
|
|
1569
|
-
logger_1.logger.error(error.message);
|
|
1570
|
-
process.exit(1);
|
|
1710
|
+
throw new Error(`Invalid --source value: ${options.source}. Expected mainnet or testnet.`);
|
|
1571
1711
|
}
|
|
1712
|
+
yield (0, fork_1.forkDevnet)(options);
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
|
|
1717
|
+
/***/ }),
|
|
1718
|
+
|
|
1719
|
+
/***/ 60905:
|
|
1720
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
1721
|
+
|
|
1722
|
+
"use strict";
|
|
1723
|
+
|
|
1724
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
1725
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
1726
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
1727
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
1728
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
1729
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
1730
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
1731
|
+
});
|
|
1732
|
+
};
|
|
1733
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1734
|
+
exports.devnetInfo = devnetInfo;
|
|
1735
|
+
const setting_1 = __nccwpck_require__(25546);
|
|
1736
|
+
const fork_1 = __nccwpck_require__(77912);
|
|
1737
|
+
const readiness_1 = __nccwpck_require__(19326);
|
|
1738
|
+
const logger_1 = __nccwpck_require__(65370);
|
|
1739
|
+
function devnetInfo() {
|
|
1740
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1741
|
+
var _a, _b, _c;
|
|
1742
|
+
const settings = (0, setting_1.readSettings)();
|
|
1743
|
+
const fork = (0, fork_1.readForkState)(settings.devnet.configPath);
|
|
1744
|
+
const readiness = yield (0, readiness_1.checkNodeReadiness)(settings.devnet.rpcUrl);
|
|
1745
|
+
const indexerReady = readiness.indexerTip != null && readiness.indexerLag === BigInt(0);
|
|
1746
|
+
const networkIsolated = fork && readiness.peers != null ? readiness.peers === 0 : undefined;
|
|
1747
|
+
const result = {
|
|
1748
|
+
command: 'devnet.info',
|
|
1749
|
+
kind: fork ? `fork-of-${fork.source}` : 'pure-devnet',
|
|
1750
|
+
configPath: settings.devnet.configPath,
|
|
1751
|
+
rpcUrl: settings.devnet.rpcUrl,
|
|
1752
|
+
proxyUrl: `http://127.0.0.1:${settings.devnet.rpcProxyPort}`,
|
|
1753
|
+
ready: readiness.ready,
|
|
1754
|
+
nodeTip: (_a = readiness.nodeTip) === null || _a === void 0 ? void 0 : _a.toString(),
|
|
1755
|
+
indexerTip: (_b = readiness.indexerTip) === null || _b === void 0 ? void 0 : _b.toString(),
|
|
1756
|
+
indexerLag: (_c = readiness.indexerLag) === null || _c === void 0 ? void 0 : _c.toString(),
|
|
1757
|
+
indexerReady,
|
|
1758
|
+
peers: readiness.peers,
|
|
1759
|
+
networkIsolated,
|
|
1760
|
+
error: readiness.error,
|
|
1761
|
+
fork,
|
|
1762
|
+
};
|
|
1763
|
+
logger_1.logger.info(`Devnet: ${result.kind}`);
|
|
1764
|
+
logger_1.logger.info(`RPC: ${result.rpcUrl}`);
|
|
1765
|
+
logger_1.logger.info(`Proxy RPC: ${result.proxyUrl}`);
|
|
1766
|
+
logger_1.logger.info(`Node ready: ${result.ready ? 'yes' : 'no'}`);
|
|
1767
|
+
if (readiness.nodeTip != null)
|
|
1768
|
+
logger_1.logger.info(`Node tip: ${readiness.nodeTip}`);
|
|
1769
|
+
if (readiness.indexerTip != null)
|
|
1770
|
+
logger_1.logger.info(`Indexer tip: ${readiness.indexerTip}`);
|
|
1771
|
+
if (readiness.indexerLag != null) {
|
|
1772
|
+
const message = `Indexer lag: ${readiness.indexerLag}`;
|
|
1773
|
+
if (readiness.indexerLag > BigInt(0))
|
|
1774
|
+
logger_1.logger.warn(`${message}; indexed queries may be stale.`);
|
|
1775
|
+
else
|
|
1776
|
+
logger_1.logger.info(message);
|
|
1777
|
+
}
|
|
1778
|
+
logger_1.logger.info(`Indexer ready: ${indexerReady ? 'yes' : 'no'}`);
|
|
1779
|
+
if (readiness.peers != null)
|
|
1780
|
+
logger_1.logger.info(`Peers: ${readiness.peers}`);
|
|
1781
|
+
if (fork)
|
|
1782
|
+
logger_1.logger.info(`Public network isolated: ${networkIsolated == null ? 'unknown' : networkIsolated ? 'yes' : 'NO'}`);
|
|
1783
|
+
if (fork && readiness.peers != null && readiness.peers > 0) {
|
|
1784
|
+
logger_1.logger.warn('A forked devnet has connected peers. Stop it and inspect ckb.toml before signing or mining.');
|
|
1785
|
+
}
|
|
1786
|
+
if ((fork === null || fork === void 0 ? void 0 : fork.source) === 'mainnet') {
|
|
1787
|
+
logger_1.logger.warn('MAINNET FORK REPLAY RISK: only sign with built-in dev keys and fork-mined cells.');
|
|
1788
|
+
}
|
|
1789
|
+
if (readiness.error)
|
|
1790
|
+
logger_1.logger.warn(readiness.error);
|
|
1791
|
+
logger_1.logger.result(result);
|
|
1792
|
+
return result;
|
|
1572
1793
|
});
|
|
1573
1794
|
}
|
|
1574
1795
|
|
|
@@ -1634,16 +1855,23 @@ const path = __importStar(__nccwpck_require__(16928));
|
|
|
1634
1855
|
const init_chain_1 = __nccwpck_require__(1360);
|
|
1635
1856
|
const install_1 = __nccwpck_require__(65611);
|
|
1636
1857
|
const setting_1 = __nccwpck_require__(25546);
|
|
1637
|
-
const encoding_1 = __nccwpck_require__(6851);
|
|
1638
1858
|
const rpc_proxy_1 = __nccwpck_require__(46589);
|
|
1639
1859
|
const fork_1 = __nccwpck_require__(77912);
|
|
1640
1860
|
const json_rpc_1 = __nccwpck_require__(51726);
|
|
1641
1861
|
const base_1 = __nccwpck_require__(69951);
|
|
1642
1862
|
const logger_1 = __nccwpck_require__(65370);
|
|
1863
|
+
const readiness_1 = __nccwpck_require__(19326);
|
|
1643
1864
|
const DAEMON_LOG_DIR = 'logs';
|
|
1644
1865
|
const DAEMON_LOG_FILE = 'daemon.log';
|
|
1645
1866
|
const DAEMON_PID_FILE = 'daemon.pid';
|
|
1646
1867
|
const DAEMON_CHILD_ENV = 'OFFCKB_DAEMON_CHILD';
|
|
1868
|
+
const NODE_READY_TIMEOUT_MS = 90000;
|
|
1869
|
+
const FORK_NODE_READY_TIMEOUT_MS = 10 * 60000;
|
|
1870
|
+
function cleanChildOutput(data) {
|
|
1871
|
+
// CKB colors its output even when it is redirected. Strip ANSI control
|
|
1872
|
+
// sequences so JSON logs stay machine-readable.
|
|
1873
|
+
return String(data).replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, '');
|
|
1874
|
+
}
|
|
1647
1875
|
function startNode({ version, network = base_1.Network.devnet, binaryPath, daemon }) {
|
|
1648
1876
|
if (binaryPath && network !== base_1.Network.devnet) {
|
|
1649
1877
|
logger_1.logger.warn('Custom binaryPath is only supported for devnet. The provided binaryPath will be ignored.');
|
|
@@ -1664,7 +1892,7 @@ function startNode({ version, network = base_1.Network.devnet, binaryPath, daemo
|
|
|
1664
1892
|
}
|
|
1665
1893
|
function nodeDevnet(_a) {
|
|
1666
1894
|
return __awaiter(this, arguments, void 0, function* ({ version, binaryPath, daemon }) {
|
|
1667
|
-
var _b, _c;
|
|
1895
|
+
var _b, _c, _d, _e, _f;
|
|
1668
1896
|
if (daemon) {
|
|
1669
1897
|
return startDaemon();
|
|
1670
1898
|
}
|
|
@@ -1672,15 +1900,15 @@ function nodeDevnet(_a) {
|
|
|
1672
1900
|
const ckbVersion = version || settings.bins.defaultCKBVersion;
|
|
1673
1901
|
let ckbBinPath = '';
|
|
1674
1902
|
if (binaryPath) {
|
|
1675
|
-
ckbBinPath =
|
|
1903
|
+
ckbBinPath = binaryPath;
|
|
1676
1904
|
logger_1.logger.info(`Using custom CKB binary path: ${ckbBinPath}`);
|
|
1677
1905
|
}
|
|
1678
1906
|
else {
|
|
1679
1907
|
yield (0, install_1.installCKBBinary)(ckbVersion);
|
|
1680
|
-
ckbBinPath = (0,
|
|
1908
|
+
ckbBinPath = (0, setting_1.getCKBBinaryPath)(ckbVersion);
|
|
1681
1909
|
}
|
|
1682
1910
|
yield (0, init_chain_1.initChainIfNeeded)();
|
|
1683
|
-
const devnetConfigPath =
|
|
1911
|
+
const devnetConfigPath = settings.devnet.configPath;
|
|
1684
1912
|
// A forked devnet must boot once with --skip-spec-check --overwrite-spec so
|
|
1685
1913
|
// the imported (and patched) spec replaces the source chain's stored spec.
|
|
1686
1914
|
const forkState = (0, fork_1.readForkState)(settings.devnet.configPath);
|
|
@@ -1688,50 +1916,98 @@ function nodeDevnet(_a) {
|
|
|
1688
1916
|
if (forkState === null || forkState === void 0 ? void 0 : forkState.firstRunPending) {
|
|
1689
1917
|
logger_1.logger.info(`Forked devnet (${forkState.source}) detected, first run uses --skip-spec-check --overwrite-spec.`);
|
|
1690
1918
|
}
|
|
1691
|
-
const ckbCmd = `${ckbBinPath} run -C ${devnetConfigPath}${firstRunFlags}`;
|
|
1692
|
-
const minerCmd = `${ckbBinPath} miner -C ${devnetConfigPath}`;
|
|
1693
1919
|
logger_1.logger.info(`Launching CKB devnet Node...`);
|
|
1920
|
+
const runArgs = ['run', '-C', devnetConfigPath];
|
|
1921
|
+
if (firstRunFlags)
|
|
1922
|
+
runArgs.push('--skip-spec-check', '--overwrite-spec');
|
|
1923
|
+
const ckbProcess = (0, child_process_1.spawn)(ckbBinPath, runArgs, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1924
|
+
(_b = ckbProcess.stdout) === null || _b === void 0 ? void 0 : _b.on('data', (data) => logger_1.logger.info(['CKB:', cleanChildOutput(data)]));
|
|
1925
|
+
(_c = ckbProcess.stderr) === null || _c === void 0 ? void 0 : _c.on('data', (data) => logger_1.logger.error(['CKB error:', cleanChildOutput(data)]));
|
|
1926
|
+
let ckbExited = false;
|
|
1927
|
+
ckbProcess.once('exit', () => {
|
|
1928
|
+
ckbExited = true;
|
|
1929
|
+
});
|
|
1930
|
+
ckbProcess.once('error', () => {
|
|
1931
|
+
ckbExited = true;
|
|
1932
|
+
});
|
|
1933
|
+
const timeoutMs = forkState ? FORK_NODE_READY_TIMEOUT_MS : NODE_READY_TIMEOUT_MS;
|
|
1934
|
+
const readiness = yield (0, readiness_1.waitForNodeReady)(settings.devnet.rpcUrl, timeoutMs, () => !ckbExited);
|
|
1935
|
+
if (!readiness.ready) {
|
|
1936
|
+
if (!ckbExited)
|
|
1937
|
+
ckbProcess.kill('SIGTERM');
|
|
1938
|
+
throw new Error(`CKB devnet failed to become ready: ${(_d = readiness.error) !== null && _d !== void 0 ? _d : 'CKB process exited'}`);
|
|
1939
|
+
}
|
|
1940
|
+
if (ckbExited) {
|
|
1941
|
+
throw new Error('CKB devnet exited immediately after its readiness check.');
|
|
1942
|
+
}
|
|
1943
|
+
if (forkState === null || forkState === void 0 ? void 0 : forkState.firstRunPending) {
|
|
1944
|
+
yield clearForkFirstRunWhenNodeUp(ckbProcess, settings.devnet.rpcUrl, settings.devnet.configPath, forkState.genesisHash);
|
|
1945
|
+
}
|
|
1946
|
+
let minerProcess;
|
|
1694
1947
|
try {
|
|
1695
|
-
|
|
1696
|
-
const ckbProcess = (0, child_process_1.exec)(ckbCmd);
|
|
1697
|
-
// Log first command's output
|
|
1698
|
-
(_b = ckbProcess.stdout) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
|
|
1699
|
-
logger_1.logger.info(['CKB:', data.toString()]);
|
|
1700
|
-
});
|
|
1701
|
-
(_c = ckbProcess.stderr) === null || _c === void 0 ? void 0 : _c.on('data', (data) => {
|
|
1702
|
-
logger_1.logger.error(['CKB error:', data.toString()]);
|
|
1703
|
-
});
|
|
1704
|
-
if (forkState === null || forkState === void 0 ? void 0 : forkState.firstRunPending) {
|
|
1705
|
-
// Only clear the flag once the spawned node is actually up and reports
|
|
1706
|
-
// the fork's genesis; if startup fails, the next run retries the flags.
|
|
1707
|
-
void clearForkFirstRunWhenNodeUp(ckbProcess, settings.devnet.rpcUrl, settings.devnet.configPath, forkState.genesisHash);
|
|
1708
|
-
}
|
|
1709
|
-
// Start the second command after 3 seconds
|
|
1710
|
-
setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
|
1711
|
-
var _a, _b;
|
|
1712
|
-
try {
|
|
1713
|
-
// Run second command
|
|
1714
|
-
const minerProcess = (0, child_process_1.exec)(minerCmd);
|
|
1715
|
-
(_a = minerProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
|
|
1716
|
-
logger_1.logger.info(['CKB-Miner:', data.toString()]);
|
|
1717
|
-
});
|
|
1718
|
-
(_b = minerProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
|
|
1719
|
-
logger_1.logger.error(['CKB-Miner error:', data.toString()]);
|
|
1720
|
-
});
|
|
1721
|
-
// by default we start the proxy server
|
|
1722
|
-
const ckbRpc = settings.devnet.rpcUrl;
|
|
1723
|
-
const port = settings.devnet.rpcProxyPort;
|
|
1724
|
-
const proxy = (0, rpc_proxy_1.createRPCProxy)(base_1.Network.devnet, ckbRpc, port);
|
|
1725
|
-
proxy.start();
|
|
1726
|
-
}
|
|
1727
|
-
catch (error) {
|
|
1728
|
-
logger_1.logger.error('Error running CKB-Miner:', error);
|
|
1729
|
-
}
|
|
1730
|
-
}), 3000);
|
|
1948
|
+
minerProcess = (0, child_process_1.spawn)(ckbBinPath, ['miner', '-C', devnetConfigPath], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1731
1949
|
}
|
|
1732
1950
|
catch (error) {
|
|
1733
|
-
|
|
1951
|
+
ckbProcess.kill('SIGTERM');
|
|
1952
|
+
throw new Error(`CKB miner failed to start: ${error.message}`);
|
|
1734
1953
|
}
|
|
1954
|
+
(_e = minerProcess.stdout) === null || _e === void 0 ? void 0 : _e.on('data', (data) => logger_1.logger.info(['CKB-Miner:', cleanChildOutput(data)]));
|
|
1955
|
+
(_f = minerProcess.stderr) === null || _f === void 0 ? void 0 : _f.on('data', (data) => logger_1.logger.error(['CKB-Miner error:', cleanChildOutput(data)]));
|
|
1956
|
+
try {
|
|
1957
|
+
yield waitForChildSpawn(minerProcess, 'CKB miner');
|
|
1958
|
+
}
|
|
1959
|
+
catch (error) {
|
|
1960
|
+
ckbProcess.kill('SIGTERM');
|
|
1961
|
+
throw error;
|
|
1962
|
+
}
|
|
1963
|
+
if (ckbExited) {
|
|
1964
|
+
if (!minerProcess.killed)
|
|
1965
|
+
minerProcess.kill('SIGTERM');
|
|
1966
|
+
throw new Error('CKB devnet exited while the miner was starting.');
|
|
1967
|
+
}
|
|
1968
|
+
const proxy = (0, rpc_proxy_1.createRPCProxy)(base_1.Network.devnet, settings.devnet.rpcUrl, settings.devnet.rpcProxyPort);
|
|
1969
|
+
proxy.start();
|
|
1970
|
+
logger_1.logger.success(`CKB devnet is ready at ${settings.devnet.rpcUrl}.`);
|
|
1971
|
+
logger_1.logger.result({
|
|
1972
|
+
command: 'node',
|
|
1973
|
+
network: base_1.Network.devnet,
|
|
1974
|
+
daemon: false,
|
|
1975
|
+
rpcUrl: settings.devnet.rpcUrl,
|
|
1976
|
+
proxyUrl: `http://127.0.0.1:${settings.devnet.rpcProxyPort}`,
|
|
1977
|
+
});
|
|
1978
|
+
// Treat CKB, miner and proxy as one service. A dead CKB must not leave a
|
|
1979
|
+
// healthy-looking proxy and a miner that retries forever.
|
|
1980
|
+
let serviceStopping = false;
|
|
1981
|
+
const stopService = (component, code, signal) => {
|
|
1982
|
+
if (serviceStopping)
|
|
1983
|
+
return;
|
|
1984
|
+
serviceStopping = true;
|
|
1985
|
+
if (component !== 'CKB node' && !ckbProcess.killed)
|
|
1986
|
+
ckbProcess.kill('SIGTERM');
|
|
1987
|
+
if (component !== 'CKB miner' && !minerProcess.killed)
|
|
1988
|
+
minerProcess.kill('SIGTERM');
|
|
1989
|
+
proxy.stop();
|
|
1990
|
+
if (process.env[DAEMON_CHILD_ENV] === '1')
|
|
1991
|
+
cleanupPidFile(resolveDaemonPaths().pidFile);
|
|
1992
|
+
logger_1.logger.error(`${component} exited unexpectedly (code=${code !== null && code !== void 0 ? code : 'null'}, signal=${signal !== null && signal !== void 0 ? signal : 'none'}).`);
|
|
1993
|
+
process.exitCode = typeof code === 'number' && code > 0 ? code : 1;
|
|
1994
|
+
};
|
|
1995
|
+
ckbProcess.once('exit', (code, signal) => stopService('CKB node', code, signal));
|
|
1996
|
+
minerProcess.once('exit', (code, signal) => stopService('CKB miner', code, signal));
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
function waitForChildSpawn(child, label) {
|
|
2000
|
+
return new Promise((resolve, reject) => {
|
|
2001
|
+
const onSpawn = () => {
|
|
2002
|
+
child.removeListener('error', onError);
|
|
2003
|
+
resolve();
|
|
2004
|
+
};
|
|
2005
|
+
const onError = (error) => {
|
|
2006
|
+
child.removeListener('spawn', onSpawn);
|
|
2007
|
+
reject(new Error(`${label} failed to start: ${error.message}`));
|
|
2008
|
+
};
|
|
2009
|
+
child.once('spawn', onSpawn);
|
|
2010
|
+
child.once('error', onError);
|
|
1735
2011
|
});
|
|
1736
2012
|
}
|
|
1737
2013
|
function resolveDaemonPaths() {
|
|
@@ -1765,7 +2041,10 @@ function clearForkFirstRunWhenNodeUp(ckbProcess, rpcUrl, configPath, expectedGen
|
|
|
1765
2041
|
'leaving the first-run flags in place.');
|
|
1766
2042
|
return;
|
|
1767
2043
|
}
|
|
1768
|
-
|
|
2044
|
+
// The miner has not started yet, so this tip is the exact boundary
|
|
2045
|
+
// between copied public-chain state and cells mined on the local fork.
|
|
2046
|
+
const forkBlockNumber = BigInt(String(yield (0, json_rpc_1.callJsonRpc)(rpcUrl, 'get_tip_block_number', [], 5000))).toString();
|
|
2047
|
+
(0, fork_1.markForkFirstRunComplete)(configPath, forkBlockNumber);
|
|
1769
2048
|
logger_1.logger.success('Forked devnet is up; first-run spec flags cleared.');
|
|
1770
2049
|
return;
|
|
1771
2050
|
}
|
|
@@ -1807,6 +2086,7 @@ function readPidFile(pidFile) {
|
|
|
1807
2086
|
pid,
|
|
1808
2087
|
scriptPath: parsed.scriptPath,
|
|
1809
2088
|
startedAt: (_b = parsed.startedAt) !== null && _b !== void 0 ? _b : new Date(0).toISOString(),
|
|
2089
|
+
status: parsed.status,
|
|
1810
2090
|
};
|
|
1811
2091
|
}
|
|
1812
2092
|
}
|
|
@@ -1820,6 +2100,39 @@ function readPidFile(pidFile) {
|
|
|
1820
2100
|
function writePidFile(pidFile, metadata) {
|
|
1821
2101
|
fs.writeFileSync(pidFile, JSON.stringify(metadata, null, 2));
|
|
1822
2102
|
}
|
|
2103
|
+
function reservePidFile(pidFile, scriptPath) {
|
|
2104
|
+
let fd;
|
|
2105
|
+
try {
|
|
2106
|
+
fd = fs.openSync(pidFile, 'wx');
|
|
2107
|
+
}
|
|
2108
|
+
catch (error) {
|
|
2109
|
+
const err = error;
|
|
2110
|
+
if (err.code === 'EEXIST') {
|
|
2111
|
+
throw new Error('A CKB devnet daemon startup is already in progress. Try again after it completes.');
|
|
2112
|
+
}
|
|
2113
|
+
throw new Error(`Failed to reserve daemon PID file ${pidFile}: ${err.message}`);
|
|
2114
|
+
}
|
|
2115
|
+
let writeError;
|
|
2116
|
+
try {
|
|
2117
|
+
const reservation = {
|
|
2118
|
+
pid: process.pid,
|
|
2119
|
+
scriptPath,
|
|
2120
|
+
startedAt: new Date().toISOString(),
|
|
2121
|
+
status: 'starting',
|
|
2122
|
+
};
|
|
2123
|
+
fs.writeFileSync(fd, JSON.stringify(reservation, null, 2));
|
|
2124
|
+
}
|
|
2125
|
+
catch (error) {
|
|
2126
|
+
writeError = error;
|
|
2127
|
+
}
|
|
2128
|
+
finally {
|
|
2129
|
+
fs.closeSync(fd);
|
|
2130
|
+
}
|
|
2131
|
+
if (writeError) {
|
|
2132
|
+
cleanupPidFile(pidFile);
|
|
2133
|
+
throw new Error(`Failed to initialize daemon PID reservation ${pidFile}: ${writeError.message}`);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
1823
2136
|
function resolveCliEntry() {
|
|
1824
2137
|
var _a;
|
|
1825
2138
|
// In priority order. process.argv[1] is the most reliable for a Node CLI.
|
|
@@ -1841,12 +2154,19 @@ function resolveCliEntry() {
|
|
|
1841
2154
|
return null;
|
|
1842
2155
|
}
|
|
1843
2156
|
function isProcessAlive(pid) {
|
|
2157
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
2158
|
+
return false;
|
|
1844
2159
|
try {
|
|
1845
2160
|
process.kill(pid, 0);
|
|
1846
2161
|
return true;
|
|
1847
2162
|
}
|
|
1848
|
-
catch (
|
|
1849
|
-
|
|
2163
|
+
catch (error) {
|
|
2164
|
+
const err = error;
|
|
2165
|
+
if (err.code === 'ESRCH')
|
|
2166
|
+
return false;
|
|
2167
|
+
if (err.code === 'EPERM')
|
|
2168
|
+
throw new Error(`Permission denied when checking daemon process ${pid}.`);
|
|
2169
|
+
throw error;
|
|
1850
2170
|
}
|
|
1851
2171
|
}
|
|
1852
2172
|
function cleanupPidFile(pidFile) {
|
|
@@ -1859,10 +2179,16 @@ function cleanupPidFile(pidFile) {
|
|
|
1859
2179
|
}
|
|
1860
2180
|
function waitForProcessExit(pid, timeoutMs) {
|
|
1861
2181
|
const start = Date.now();
|
|
1862
|
-
return new Promise((resolve) => {
|
|
2182
|
+
return new Promise((resolve, reject) => {
|
|
1863
2183
|
const check = () => {
|
|
1864
|
-
|
|
1865
|
-
|
|
2184
|
+
try {
|
|
2185
|
+
if (!isProcessAlive(pid)) {
|
|
2186
|
+
resolve(true);
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
catch (error) {
|
|
2191
|
+
reject(error);
|
|
1866
2192
|
return;
|
|
1867
2193
|
}
|
|
1868
2194
|
if (Date.now() - start >= timeoutMs) {
|
|
@@ -1942,73 +2268,159 @@ function terminateProcess(pid, signal) {
|
|
|
1942
2268
|
}
|
|
1943
2269
|
});
|
|
1944
2270
|
}
|
|
2271
|
+
function failDaemonStartup(error, pid, pidFile) {
|
|
2272
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2273
|
+
let exited = false;
|
|
2274
|
+
try {
|
|
2275
|
+
exited = !isProcessAlive(pid);
|
|
2276
|
+
if (!exited) {
|
|
2277
|
+
yield terminateProcess(pid, 'SIGTERM');
|
|
2278
|
+
exited = yield waitForProcessExit(pid, 5000);
|
|
2279
|
+
if (!exited) {
|
|
2280
|
+
yield terminateProcess(pid, 'SIGKILL');
|
|
2281
|
+
exited = yield waitForProcessExit(pid, 5000);
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
catch (_a) {
|
|
2286
|
+
// The child may have exited while cleanup signals were sent. If liveness
|
|
2287
|
+
// cannot be checked, preserve the PID file for a later explicit stop.
|
|
2288
|
+
try {
|
|
2289
|
+
exited = !isProcessAlive(pid);
|
|
2290
|
+
}
|
|
2291
|
+
catch (_b) {
|
|
2292
|
+
exited = false;
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
if (exited) {
|
|
2296
|
+
cleanupPidFile(pidFile);
|
|
2297
|
+
}
|
|
2298
|
+
else {
|
|
2299
|
+
error.message += ` Process ${pid} is still running; PID file was preserved.`;
|
|
2300
|
+
}
|
|
2301
|
+
throw error;
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
1945
2304
|
function startDaemon() {
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2305
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2306
|
+
var _a;
|
|
2307
|
+
const { logDir, logFile, pidFile } = resolveDaemonPaths();
|
|
2308
|
+
try {
|
|
2309
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
2310
|
+
}
|
|
2311
|
+
catch (error) {
|
|
2312
|
+
throw new Error(`Failed to prepare daemon log directory at ${logDir}: ${error.message}`);
|
|
2313
|
+
}
|
|
2314
|
+
const settings = (0, setting_1.readSettings)();
|
|
2315
|
+
const activeNode = yield (0, readiness_1.checkNodeReadiness)(settings.devnet.rpcUrl, 1000);
|
|
2316
|
+
if (activeNode.ready) {
|
|
2317
|
+
throw new Error(`A CKB node is already answering at ${settings.devnet.rpcUrl}. Stop it before starting daemon mode.`);
|
|
2318
|
+
}
|
|
2319
|
+
// Prevent duplicate daemon starts. If a daemon is already running, refuse
|
|
2320
|
+
// to overwrite its PID file.
|
|
2321
|
+
const existing = readPidFile(pidFile);
|
|
2322
|
+
if (existing) {
|
|
2323
|
+
if (isProcessAlive(existing.pid)) {
|
|
2324
|
+
const identityOk = yield verifyDaemonIdentity(existing.pid, existing);
|
|
2325
|
+
if (identityOk) {
|
|
2326
|
+
if (existing.status === 'starting') {
|
|
2327
|
+
throw new Error(`Another CKB devnet daemon startup is already in progress (PID ${existing.pid}).`);
|
|
2328
|
+
}
|
|
2329
|
+
throw new Error(`A CKB devnet daemon is already running (PID ${existing.pid}). Stop it first with: offckb node stop`);
|
|
2330
|
+
}
|
|
2331
|
+
logger_1.logger.warn(`PID ${existing.pid} from ${pidFile} belongs to another process; removing stale daemon metadata without signaling it.`);
|
|
2332
|
+
}
|
|
2333
|
+
// Stale PID file from a crashed daemon; clean it up before atomically
|
|
2334
|
+
// reserving the same control file for this startup attempt.
|
|
2335
|
+
cleanupPidFile(pidFile);
|
|
2336
|
+
}
|
|
2337
|
+
const scriptPath = resolveCliEntry();
|
|
2338
|
+
if (!scriptPath) {
|
|
2339
|
+
throw new Error('Unable to determine the CLI entry point for daemon mode. Set OFFCKB_CLI_PATH to the offckb script.');
|
|
2340
|
+
}
|
|
2341
|
+
reservePidFile(pidFile, scriptPath);
|
|
2342
|
+
let out;
|
|
2343
|
+
let err;
|
|
2344
|
+
try {
|
|
2345
|
+
out = fs.openSync(logFile, 'a');
|
|
2346
|
+
err = fs.openSync(logFile, 'a');
|
|
2347
|
+
}
|
|
2348
|
+
catch (error) {
|
|
2349
|
+
closeFileDescriptors(out, err);
|
|
2350
|
+
cleanupPidFile(pidFile);
|
|
2351
|
+
throw new Error(`Failed to prepare daemon log directory or log file at ${logFile}: ${error.message}`);
|
|
2352
|
+
}
|
|
2353
|
+
const childArgs = process.argv.slice(2).filter((arg) => arg !== '--daemon');
|
|
2354
|
+
const childEnv = Object.assign(Object.assign({}, process.env), { [DAEMON_CHILD_ENV]: '1' });
|
|
2355
|
+
let child;
|
|
2356
|
+
try {
|
|
2357
|
+
child = (0, child_process_1.spawn)(process.execPath, [scriptPath, ...childArgs], {
|
|
2358
|
+
detached: true,
|
|
2359
|
+
stdio: ['ignore', out, err],
|
|
2360
|
+
env: childEnv,
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
catch (error) {
|
|
2364
|
+
closeFileDescriptors(out, err);
|
|
2365
|
+
cleanupPidFile(pidFile);
|
|
2366
|
+
throw new Error(`Failed to spawn daemon process: ${error.message}`);
|
|
2367
|
+
}
|
|
2368
|
+
if (!child.pid) {
|
|
2369
|
+
closeFileDescriptors(out, err);
|
|
2370
|
+
cleanupPidFile(pidFile);
|
|
2371
|
+
throw new Error('Failed to spawn daemon process: no PID returned.');
|
|
2372
|
+
}
|
|
2373
|
+
child.unref();
|
|
2374
|
+
child.on('error', (error) => {
|
|
2375
|
+
logger_1.logger.error('Daemon child process failed to start:', error);
|
|
2376
|
+
cleanupPidFile(pidFile);
|
|
1983
2377
|
});
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2378
|
+
const metadata = {
|
|
2379
|
+
pid: child.pid,
|
|
2380
|
+
scriptPath,
|
|
2381
|
+
startedAt: new Date().toISOString(),
|
|
2382
|
+
status: 'starting',
|
|
2383
|
+
};
|
|
2384
|
+
try {
|
|
2385
|
+
writePidFile(pidFile, metadata);
|
|
2386
|
+
}
|
|
2387
|
+
catch (error) {
|
|
2388
|
+
closeFileDescriptors(out, err);
|
|
2389
|
+
return failDaemonStartup(error, child.pid, pidFile);
|
|
2390
|
+
}
|
|
2391
|
+
// File descriptors are now owned by the spawned child; close our copies.
|
|
1992
2392
|
closeFileDescriptors(out, err);
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
2393
|
+
const proxyUrl = `http://127.0.0.1:${settings.devnet.rpcProxyPort}`;
|
|
2394
|
+
try {
|
|
2395
|
+
const forkState = (0, fork_1.readForkState)(settings.devnet.configPath);
|
|
2396
|
+
const timeoutMs = forkState ? FORK_NODE_READY_TIMEOUT_MS : NODE_READY_TIMEOUT_MS;
|
|
2397
|
+
// The proxy only starts after the child has a healthy CKB RPC and has
|
|
2398
|
+
// successfully spawned the miner, so this is the daemon's service-level
|
|
2399
|
+
// readiness check rather than a port/process check.
|
|
2400
|
+
const readiness = yield (0, readiness_1.waitForNodeReady)(proxyUrl, timeoutMs, () => isProcessAlive(child.pid));
|
|
2401
|
+
if (!readiness.ready) {
|
|
2402
|
+
throw new Error(`CKB devnet daemon failed to become ready. See ${logFile}. ${(_a = readiness.error) !== null && _a !== void 0 ? _a : 'Daemon process exited.'}`);
|
|
2403
|
+
}
|
|
2404
|
+
writePidFile(pidFile, Object.assign(Object.assign({}, metadata), { status: 'running' }));
|
|
2405
|
+
}
|
|
2406
|
+
catch (error) {
|
|
2407
|
+
return failDaemonStartup(error, child.pid, pidFile);
|
|
2408
|
+
}
|
|
2409
|
+
logger_1.logger.success(`CKB devnet daemon started with PID ${child.pid} and passed its RPC/proxy health check.`);
|
|
2410
|
+
logger_1.logger.info(`Logs: ${logFile}`);
|
|
2411
|
+
logger_1.logger.info(`PID file: ${pidFile}`);
|
|
2412
|
+
logger_1.logger.info('Stop the daemon with: offckb node stop');
|
|
2413
|
+
logger_1.logger.result({
|
|
2414
|
+
command: 'node',
|
|
2415
|
+
network: base_1.Network.devnet,
|
|
2416
|
+
daemon: true,
|
|
2417
|
+
pid: child.pid,
|
|
2418
|
+
rpcUrl: settings.devnet.rpcUrl,
|
|
2419
|
+
proxyUrl,
|
|
2420
|
+
logFile,
|
|
2421
|
+
pidFile,
|
|
2422
|
+
});
|
|
1999
2423
|
});
|
|
2000
|
-
const metadata = {
|
|
2001
|
-
pid: child.pid,
|
|
2002
|
-
scriptPath,
|
|
2003
|
-
startedAt: new Date().toISOString(),
|
|
2004
|
-
};
|
|
2005
|
-
writePidFile(pidFile, metadata);
|
|
2006
|
-
// File descriptors are now owned by the spawned child; close our copies.
|
|
2007
|
-
closeFileDescriptors(out, err);
|
|
2008
|
-
logger_1.logger.success(`CKB devnet daemon started with PID ${child.pid}.`);
|
|
2009
|
-
logger_1.logger.info(`Logs: ${logFile}`);
|
|
2010
|
-
logger_1.logger.info(`PID file: ${pidFile}`);
|
|
2011
|
-
logger_1.logger.info('Stop the daemon with: offckb node stop');
|
|
2012
2424
|
}
|
|
2013
2425
|
function closeFileDescriptors(...fds) {
|
|
2014
2426
|
for (const fd of fds) {
|
|
@@ -2028,24 +2440,28 @@ function stopNode() {
|
|
|
2028
2440
|
const metadata = readPidFile(pidFile);
|
|
2029
2441
|
if (!metadata) {
|
|
2030
2442
|
logger_1.logger.warn(`No daemon PID file found at ${pidFile}. Is the devnet daemon running?`);
|
|
2443
|
+
logger_1.logger.result({ command: 'node.stop', stopped: false, reason: 'not-running' });
|
|
2031
2444
|
return;
|
|
2032
2445
|
}
|
|
2033
2446
|
const pid = metadata.pid;
|
|
2034
2447
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
2035
|
-
logger_1.logger.error(`Invalid PID in ${pidFile}: ${pid}`);
|
|
2036
2448
|
cleanupPidFile(pidFile);
|
|
2037
|
-
|
|
2449
|
+
throw new Error(`Invalid PID in ${pidFile}: ${pid}`);
|
|
2038
2450
|
}
|
|
2039
|
-
|
|
2451
|
+
const processAlive = isProcessAlive(pid);
|
|
2452
|
+
if (!processAlive) {
|
|
2040
2453
|
logger_1.logger.warn(`Daemon process ${pid} is not running.`);
|
|
2041
2454
|
cleanupPidFile(pidFile);
|
|
2455
|
+
logger_1.logger.result({ command: 'node.stop', stopped: false, reason: 'stale-pid', pid });
|
|
2042
2456
|
return;
|
|
2043
2457
|
}
|
|
2458
|
+
if (metadata.status === 'starting') {
|
|
2459
|
+
throw new Error(`CKB devnet daemon startup is still in progress (PID ${pid}). Try stopping it again shortly.`);
|
|
2460
|
+
}
|
|
2044
2461
|
const identityOk = yield verifyDaemonIdentity(pid, metadata);
|
|
2045
2462
|
if (!identityOk) {
|
|
2046
|
-
|
|
2463
|
+
throw new Error(`Process ${pid} does not appear to be the offckb daemon. Refusing to send signals to avoid killing an unrelated process. ` +
|
|
2047
2464
|
`If you are sure this is the daemon, stop it manually and remove ${pidFile}.`);
|
|
2048
|
-
return;
|
|
2049
2465
|
}
|
|
2050
2466
|
logger_1.logger.info(`Stopping CKB devnet daemon (PID ${pid})...`);
|
|
2051
2467
|
try {
|
|
@@ -2056,17 +2472,13 @@ function stopNode() {
|
|
|
2056
2472
|
if (err.code === 'ESRCH') {
|
|
2057
2473
|
logger_1.logger.warn(`Daemon process ${pid} is not running.`);
|
|
2058
2474
|
cleanupPidFile(pidFile);
|
|
2475
|
+
logger_1.logger.result({ command: 'node.stop', stopped: false, reason: 'already-exited', pid });
|
|
2059
2476
|
return;
|
|
2060
2477
|
}
|
|
2061
2478
|
if (err.code === 'EPERM') {
|
|
2062
|
-
|
|
2479
|
+
throw new Error(`Permission denied when sending SIGTERM to daemon process ${pid}.`);
|
|
2063
2480
|
}
|
|
2064
|
-
|
|
2065
|
-
logger_1.logger.error(`Failed to send SIGTERM to daemon process ${pid}:`, error);
|
|
2066
|
-
}
|
|
2067
|
-
// Still try to clean up the PID file so the user can recover.
|
|
2068
|
-
cleanupPidFile(pidFile);
|
|
2069
|
-
return;
|
|
2481
|
+
throw new Error(`Failed to send SIGTERM to daemon process ${pid}: ${err.message}`);
|
|
2070
2482
|
}
|
|
2071
2483
|
const exited = yield waitForProcessExit(pid, 5000);
|
|
2072
2484
|
if (!exited) {
|
|
@@ -2075,11 +2487,12 @@ function stopNode() {
|
|
|
2075
2487
|
yield terminateProcess(pid, 'SIGKILL');
|
|
2076
2488
|
}
|
|
2077
2489
|
catch (error) {
|
|
2078
|
-
|
|
2490
|
+
throw new Error(`Failed to send SIGKILL to daemon process ${pid}: ${error.message}`);
|
|
2079
2491
|
}
|
|
2080
2492
|
}
|
|
2081
2493
|
cleanupPidFile(pidFile);
|
|
2082
2494
|
logger_1.logger.success('CKB devnet daemon stopped.');
|
|
2495
|
+
logger_1.logger.result({ command: 'node.stop', stopped: true, pid });
|
|
2083
2496
|
});
|
|
2084
2497
|
}
|
|
2085
2498
|
function nodeTestnet() {
|
|
@@ -2113,39 +2526,6 @@ function nodeMainnet() {
|
|
|
2113
2526
|
|
|
2114
2527
|
"use strict";
|
|
2115
2528
|
|
|
2116
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
2117
|
-
if (k2 === undefined) k2 = k;
|
|
2118
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
2119
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
2120
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
2121
|
-
}
|
|
2122
|
-
Object.defineProperty(o, k2, desc);
|
|
2123
|
-
}) : (function(o, m, k, k2) {
|
|
2124
|
-
if (k2 === undefined) k2 = k;
|
|
2125
|
-
o[k2] = m[k];
|
|
2126
|
-
}));
|
|
2127
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
2128
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
2129
|
-
}) : function(o, v) {
|
|
2130
|
-
o["default"] = v;
|
|
2131
|
-
});
|
|
2132
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
2133
|
-
var ownKeys = function(o) {
|
|
2134
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
2135
|
-
var ar = [];
|
|
2136
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
2137
|
-
return ar;
|
|
2138
|
-
};
|
|
2139
|
-
return ownKeys(o);
|
|
2140
|
-
};
|
|
2141
|
-
return function (mod) {
|
|
2142
|
-
if (mod && mod.__esModule) return mod;
|
|
2143
|
-
var result = {};
|
|
2144
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
2145
|
-
__setModuleDefault(result, mod);
|
|
2146
|
-
return result;
|
|
2147
|
-
};
|
|
2148
|
-
})();
|
|
2149
2529
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2150
2530
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
2151
2531
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -2160,8 +2540,7 @@ exports.status = status;
|
|
|
2160
2540
|
const setting_1 = __nccwpck_require__(25546);
|
|
2161
2541
|
const ckb_tui_1 = __nccwpck_require__(7020);
|
|
2162
2542
|
const base_1 = __nccwpck_require__(69951);
|
|
2163
|
-
const
|
|
2164
|
-
const net = __importStar(__nccwpck_require__(69278));
|
|
2543
|
+
const readiness_1 = __nccwpck_require__(19326);
|
|
2165
2544
|
const NETWORK_SETTINGS_KEY = {
|
|
2166
2545
|
[base_1.Network.devnet]: 'devnet',
|
|
2167
2546
|
[base_1.Network.testnet]: 'testnet',
|
|
@@ -2169,65 +2548,27 @@ const NETWORK_SETTINGS_KEY = {
|
|
|
2169
2548
|
};
|
|
2170
2549
|
function status(_a) {
|
|
2171
2550
|
return __awaiter(this, arguments, void 0, function* ({ network }) {
|
|
2172
|
-
var _b;
|
|
2551
|
+
var _b, _c;
|
|
2173
2552
|
// ckb-tui is an interactive terminal UI. Running it without a TTY
|
|
2174
2553
|
// (pipe, redirect, CI) would hang or produce garbage output.
|
|
2175
2554
|
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
2176
|
-
|
|
2177
|
-
'It cannot be used in pipes, redirects, or non-interactive environments like CI.');
|
|
2178
|
-
process.exit(1);
|
|
2555
|
+
throw new Error('The status command requires an interactive terminal (TTY). It cannot be used in pipes, redirects, or CI.');
|
|
2179
2556
|
}
|
|
2180
2557
|
const settings = (0, setting_1.readSettings)();
|
|
2181
2558
|
const networkKey = NETWORK_SETTINGS_KEY[network];
|
|
2182
2559
|
const port = settings[networkKey].rpcProxyPort;
|
|
2183
2560
|
const url = `http://127.0.0.1:${port}`;
|
|
2184
|
-
const
|
|
2185
|
-
if (!
|
|
2186
|
-
|
|
2187
|
-
return;
|
|
2561
|
+
const readiness = yield (0, readiness_1.checkNodeReadiness)(url);
|
|
2562
|
+
if (!readiness.ready) {
|
|
2563
|
+
throw new Error(`RPC proxy ${url} is not connected to a healthy ${network} node: ${(_b = readiness.error) !== null && _b !== void 0 ? _b : 'health check failed'}`);
|
|
2188
2564
|
}
|
|
2189
2565
|
const result = ckb_tui_1.CKBTui.run(['-r', url]);
|
|
2190
2566
|
// Propagate ckb-tui exit code so scripts can detect TUI failure
|
|
2191
2567
|
if (result.status !== 0) {
|
|
2192
|
-
|
|
2568
|
+
throw new Error(`ckb-tui exited with code ${(_c = result.status) !== null && _c !== void 0 ? _c : 'unknown'}`);
|
|
2193
2569
|
}
|
|
2194
2570
|
});
|
|
2195
2571
|
}
|
|
2196
|
-
function isRPCPortListening(port) {
|
|
2197
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2198
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
2199
|
-
return false;
|
|
2200
|
-
}
|
|
2201
|
-
const client = new net.Socket();
|
|
2202
|
-
return new Promise((resolve) => {
|
|
2203
|
-
let settled = false;
|
|
2204
|
-
const TIMEOUT_MS = 2000;
|
|
2205
|
-
const timeout = setTimeout(() => {
|
|
2206
|
-
if (!settled) {
|
|
2207
|
-
settled = true;
|
|
2208
|
-
client.destroy();
|
|
2209
|
-
resolve(false);
|
|
2210
|
-
}
|
|
2211
|
-
}, TIMEOUT_MS);
|
|
2212
|
-
client.once('error', () => {
|
|
2213
|
-
if (!settled) {
|
|
2214
|
-
settled = true;
|
|
2215
|
-
clearTimeout(timeout);
|
|
2216
|
-
resolve(false);
|
|
2217
|
-
}
|
|
2218
|
-
});
|
|
2219
|
-
client.once('connect', () => {
|
|
2220
|
-
if (!settled) {
|
|
2221
|
-
settled = true;
|
|
2222
|
-
clearTimeout(timeout);
|
|
2223
|
-
client.end();
|
|
2224
|
-
resolve(true);
|
|
2225
|
-
}
|
|
2226
|
-
});
|
|
2227
|
-
client.connect(port, '127.0.0.1');
|
|
2228
|
-
});
|
|
2229
|
-
});
|
|
2230
|
-
}
|
|
2231
2572
|
|
|
2232
2573
|
|
|
2233
2574
|
/***/ }),
|
|
@@ -2460,14 +2801,16 @@ const base_1 = __nccwpck_require__(69951);
|
|
|
2460
2801
|
const link_1 = __nccwpck_require__(27814);
|
|
2461
2802
|
const validator_1 = __nccwpck_require__(39534);
|
|
2462
2803
|
const logger_1 = __nccwpck_require__(65370);
|
|
2804
|
+
const private_key_1 = __nccwpck_require__(26241);
|
|
2805
|
+
const readiness_1 = __nccwpck_require__(19326);
|
|
2806
|
+
const fork_safety_1 = __nccwpck_require__(47625);
|
|
2463
2807
|
function transferAll(toAddress_1) {
|
|
2464
2808
|
return __awaiter(this, arguments, void 0, function* (toAddress, opt = { network: base_1.Network.devnet }) {
|
|
2465
2809
|
const network = opt.network;
|
|
2466
2810
|
(0, validator_1.validateNetworkOpt)(network);
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
const privateKey = opt.privkey;
|
|
2811
|
+
const privateKey = (0, private_key_1.resolvePrivateKey)(opt);
|
|
2812
|
+
(0, fork_safety_1.warnIfMainnetForkSigning)(network, privateKey);
|
|
2813
|
+
yield (0, readiness_1.warnIfForkIndexerIsBehind)(network);
|
|
2471
2814
|
const ckb = new ckb_1.CKB({ network });
|
|
2472
2815
|
const txHash = yield ckb.transferAll({
|
|
2473
2816
|
toAddress,
|
|
@@ -2475,9 +2818,12 @@ function transferAll(toAddress_1) {
|
|
|
2475
2818
|
});
|
|
2476
2819
|
if (network === 'testnet') {
|
|
2477
2820
|
logger_1.logger.info(`Successfully transfer, check ${(0, link_1.buildTestnetTxLink)(txHash)} for details.`);
|
|
2478
|
-
|
|
2821
|
+
logger_1.logger.result({ command: 'transfer-all', network, toAddress, txHash });
|
|
2822
|
+
return txHash;
|
|
2479
2823
|
}
|
|
2480
2824
|
logger_1.logger.info('Successfully transfer, txHash:', txHash);
|
|
2825
|
+
logger_1.logger.result({ command: 'transfer-all', network, toAddress, txHash });
|
|
2826
|
+
return txHash;
|
|
2481
2827
|
});
|
|
2482
2828
|
}
|
|
2483
2829
|
|
|
@@ -2504,37 +2850,61 @@ const ckb_1 = __nccwpck_require__(83748);
|
|
|
2504
2850
|
const base_1 = __nccwpck_require__(69951);
|
|
2505
2851
|
const link_1 = __nccwpck_require__(27814);
|
|
2506
2852
|
const validator_1 = __nccwpck_require__(39534);
|
|
2853
|
+
const private_key_1 = __nccwpck_require__(26241);
|
|
2854
|
+
const logger_1 = __nccwpck_require__(65370);
|
|
2855
|
+
const readiness_1 = __nccwpck_require__(19326);
|
|
2856
|
+
const fork_safety_1 = __nccwpck_require__(47625);
|
|
2507
2857
|
function transfer(toAddress_1, amount_1) {
|
|
2508
2858
|
return __awaiter(this, arguments, void 0, function* (toAddress, amount, opt = { network: base_1.Network.devnet }) {
|
|
2509
2859
|
var _a;
|
|
2510
2860
|
const network = opt.network;
|
|
2511
2861
|
(0, validator_1.validateNetworkOpt)(network);
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2862
|
+
let udtKind;
|
|
2863
|
+
let udtTypeArgs;
|
|
2864
|
+
if (opt.udtKind != null || opt.udtTypeArgs != null) {
|
|
2865
|
+
if (!opt.udtTypeArgs) {
|
|
2866
|
+
throw new Error('UDT type args are required for a UDT transfer');
|
|
2867
|
+
}
|
|
2868
|
+
(0, validator_1.validateUdtAmount)(amount);
|
|
2869
|
+
udtKind = (_a = opt.udtKind) !== null && _a !== void 0 ? _a : 'sudt';
|
|
2870
|
+
(0, validator_1.validateUdtKind)(udtKind);
|
|
2871
|
+
udtTypeArgs = (0, validator_1.validateUdtTypeArgs)(udtKind, opt.udtTypeArgs);
|
|
2872
|
+
}
|
|
2873
|
+
const privateKey = (0, private_key_1.resolvePrivateKey)(opt);
|
|
2874
|
+
const rejectInputsAtOrBeforeBlock = (0, fork_safety_1.validateMainnetForkSigning)(network, privateKey, opt.allowMainnetReplayRisk);
|
|
2875
|
+
yield (0, readiness_1.warnIfForkIndexerIsBehind)(network);
|
|
2516
2876
|
const ckb = new ckb_1.CKB({ network });
|
|
2517
|
-
if (
|
|
2518
|
-
const
|
|
2519
|
-
(0, validator_1.validateUdtKind)(kind);
|
|
2520
|
-
const udtTypeArgs = (0, validator_1.validateUdtTypeArgs)(kind, opt.udtTypeArgs);
|
|
2521
|
-
const udtType = yield ckb.buildUdtTypeScript(kind, udtTypeArgs);
|
|
2877
|
+
if (udtKind && udtTypeArgs) {
|
|
2878
|
+
const udtType = yield ckb.buildUdtTypeScript(udtKind, udtTypeArgs);
|
|
2522
2879
|
const txHash = yield ckb.udtTransfer({
|
|
2523
2880
|
toAddress,
|
|
2524
2881
|
amount,
|
|
2525
2882
|
privateKey,
|
|
2526
2883
|
udtType,
|
|
2527
|
-
kind,
|
|
2884
|
+
kind: udtKind,
|
|
2885
|
+
rejectInputsAtOrBeforeBlock,
|
|
2528
2886
|
});
|
|
2529
2887
|
(0, link_1.logTxSuccess)(network, txHash, 'transfer UDT');
|
|
2530
|
-
|
|
2888
|
+
logger_1.logger.result({
|
|
2889
|
+
command: 'udt.transfer',
|
|
2890
|
+
network,
|
|
2891
|
+
kind: udtKind,
|
|
2892
|
+
amount,
|
|
2893
|
+
typeArgs: udtTypeArgs,
|
|
2894
|
+
toAddress,
|
|
2895
|
+
txHash,
|
|
2896
|
+
});
|
|
2897
|
+
return txHash;
|
|
2531
2898
|
}
|
|
2532
2899
|
const txHash = yield ckb.transfer({
|
|
2533
2900
|
toAddress,
|
|
2534
2901
|
amountInCKB: amount,
|
|
2535
2902
|
privateKey,
|
|
2903
|
+
rejectInputsAtOrBeforeBlock,
|
|
2536
2904
|
});
|
|
2537
2905
|
(0, link_1.logTxSuccess)(network, txHash, 'transfer');
|
|
2906
|
+
logger_1.logger.result({ command: 'transfer', network, amount, toAddress, txHash });
|
|
2907
|
+
return txHash;
|
|
2538
2908
|
});
|
|
2539
2909
|
}
|
|
2540
2910
|
|
|
@@ -2562,41 +2932,65 @@ const ckb_1 = __nccwpck_require__(83748);
|
|
|
2562
2932
|
const base_1 = __nccwpck_require__(69951);
|
|
2563
2933
|
const link_1 = __nccwpck_require__(27814);
|
|
2564
2934
|
const validator_1 = __nccwpck_require__(39534);
|
|
2935
|
+
const private_key_1 = __nccwpck_require__(26241);
|
|
2936
|
+
const logger_1 = __nccwpck_require__(65370);
|
|
2937
|
+
const readiness_1 = __nccwpck_require__(19326);
|
|
2938
|
+
const fork_safety_1 = __nccwpck_require__(47625);
|
|
2565
2939
|
function udtIssue(amount_1) {
|
|
2566
|
-
return __awaiter(this, arguments, void 0, function* (amount, opt = { network: base_1.Network.devnet, udtKind: 'sudt'
|
|
2940
|
+
return __awaiter(this, arguments, void 0, function* (amount, opt = { network: base_1.Network.devnet, udtKind: 'sudt' }) {
|
|
2567
2941
|
const network = opt.network;
|
|
2568
2942
|
(0, validator_1.validateNetworkOpt)(network);
|
|
2569
2943
|
(0, validator_1.validateUdtKind)(opt.udtKind);
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2944
|
+
(0, validator_1.validateUdtAmount)(amount);
|
|
2945
|
+
const typeArgs = opt.typeArgs ? (0, validator_1.validateUdtTypeArgs)(opt.udtKind, opt.typeArgs) : undefined;
|
|
2946
|
+
const privateKey = (0, private_key_1.resolvePrivateKey)(opt);
|
|
2947
|
+
(0, fork_safety_1.warnIfMainnetForkSigning)(network, privateKey);
|
|
2948
|
+
yield (0, readiness_1.warnIfForkIndexerIsBehind)(network);
|
|
2573
2949
|
const ckb = new ckb_1.CKB({ network });
|
|
2574
|
-
const
|
|
2575
|
-
privateKey
|
|
2950
|
+
const result = yield ckb.udtIssue({
|
|
2951
|
+
privateKey,
|
|
2576
2952
|
kind: opt.udtKind,
|
|
2577
2953
|
amount,
|
|
2578
|
-
typeArgs
|
|
2954
|
+
typeArgs,
|
|
2579
2955
|
toAddress: opt.to,
|
|
2580
2956
|
});
|
|
2581
|
-
(0, link_1.logTxSuccess)(network, txHash, 'issued UDT');
|
|
2957
|
+
(0, link_1.logTxSuccess)(network, result.txHash, 'issued UDT');
|
|
2958
|
+
logger_1.logger.info(`UDT kind: ${opt.udtKind}`);
|
|
2959
|
+
logger_1.logger.info(`UDT type args: ${result.typeArgs}`);
|
|
2960
|
+
logger_1.logger.info(`Receiver: ${result.receiver}`);
|
|
2961
|
+
logger_1.logger.info(`Next: offckb balance ${result.receiver} --udt-kind ${opt.udtKind} --udt-type-args ${result.typeArgs}`);
|
|
2962
|
+
logger_1.logger.result({
|
|
2963
|
+
command: 'udt.issue',
|
|
2964
|
+
network,
|
|
2965
|
+
kind: opt.udtKind,
|
|
2966
|
+
amount,
|
|
2967
|
+
receiver: result.receiver,
|
|
2968
|
+
typeArgs: result.typeArgs,
|
|
2969
|
+
txHash: result.txHash,
|
|
2970
|
+
});
|
|
2971
|
+
return result;
|
|
2582
2972
|
});
|
|
2583
2973
|
}
|
|
2584
2974
|
function udtDestroy(amount_1) {
|
|
2585
|
-
return __awaiter(this, arguments, void 0, function* (amount, opt = { network: base_1.Network.devnet, udtKind: 'sudt', typeArgs: ''
|
|
2975
|
+
return __awaiter(this, arguments, void 0, function* (amount, opt = { network: base_1.Network.devnet, udtKind: 'sudt', typeArgs: '' }) {
|
|
2586
2976
|
const network = opt.network;
|
|
2587
2977
|
(0, validator_1.validateNetworkOpt)(network);
|
|
2588
2978
|
(0, validator_1.validateUdtKind)(opt.udtKind);
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2979
|
+
(0, validator_1.validateUdtAmount)(amount);
|
|
2980
|
+
const typeArgs = (0, validator_1.validateUdtTypeArgs)(opt.udtKind, opt.typeArgs);
|
|
2981
|
+
const privateKey = (0, private_key_1.resolvePrivateKey)(opt);
|
|
2982
|
+
(0, fork_safety_1.warnIfMainnetForkSigning)(network, privateKey);
|
|
2983
|
+
yield (0, readiness_1.warnIfForkIndexerIsBehind)(network);
|
|
2592
2984
|
const ckb = new ckb_1.CKB({ network });
|
|
2593
2985
|
const txHash = yield ckb.udtDestroy({
|
|
2594
|
-
privateKey
|
|
2986
|
+
privateKey,
|
|
2595
2987
|
kind: opt.udtKind,
|
|
2596
2988
|
amount,
|
|
2597
|
-
typeArgs
|
|
2989
|
+
typeArgs,
|
|
2598
2990
|
});
|
|
2599
2991
|
(0, link_1.logTxSuccess)(network, txHash, 'destroyed UDT');
|
|
2992
|
+
logger_1.logger.result({ command: 'udt.destroy', network, kind: opt.udtKind, amount, typeArgs, txHash });
|
|
2993
|
+
return txHash;
|
|
2600
2994
|
});
|
|
2601
2995
|
}
|
|
2602
2996
|
|
|
@@ -3598,9 +3992,13 @@ exports.parseGenesisHashFromInitOutput = parseGenesisHashFromInitOutput;
|
|
|
3598
3992
|
exports.parseGenesisHashFromListHashes = parseGenesisHashFromListHashes;
|
|
3599
3993
|
exports.expectedGenesisHash = expectedGenesisHash;
|
|
3600
3994
|
exports.patchDevSpecForFork = patchDevSpecForFork;
|
|
3995
|
+
exports.copySourceData = copySourceData;
|
|
3996
|
+
exports.isolateForkCkbConfig = isolateForkCkbConfig;
|
|
3997
|
+
exports.migrationNeededFromExitCode = migrationNeededFromExitCode;
|
|
3601
3998
|
exports.forkDevnet = forkDevnet;
|
|
3602
3999
|
const child_process_1 = __nccwpck_require__(35317);
|
|
3603
4000
|
const fs_1 = __importDefault(__nccwpck_require__(79896));
|
|
4001
|
+
const os_1 = __importDefault(__nccwpck_require__(70857));
|
|
3604
4002
|
const path_1 = __importDefault(__nccwpck_require__(16928));
|
|
3605
4003
|
const toml_1 = __importDefault(__nccwpck_require__(2811));
|
|
3606
4004
|
const setting_1 = __nccwpck_require__(25546);
|
|
@@ -3644,11 +4042,11 @@ function writeForkState(configPath, state) {
|
|
|
3644
4042
|
fs_1.default.writeFileSync(tempPath, JSON.stringify(state, null, 2));
|
|
3645
4043
|
fs_1.default.renameSync(tempPath, statePath);
|
|
3646
4044
|
}
|
|
3647
|
-
function markForkFirstRunComplete(configPath) {
|
|
4045
|
+
function markForkFirstRunComplete(configPath, forkBlockNumber) {
|
|
3648
4046
|
const state = readForkState(configPath);
|
|
3649
4047
|
if (!state || !state.firstRunPending)
|
|
3650
4048
|
return;
|
|
3651
|
-
writeForkState(configPath, Object.assign(Object.assign({}, state), { firstRunPending: false }));
|
|
4049
|
+
writeForkState(configPath, Object.assign(Object.assign(Object.assign({}, state), { firstRunPending: false }), (forkBlockNumber == null ? {} : { forkBlockNumber })));
|
|
3652
4050
|
}
|
|
3653
4051
|
function detectSourceFromCkbToml(ckbTomlContent) {
|
|
3654
4052
|
let parsed;
|
|
@@ -3823,7 +4221,28 @@ function copySourceData(sourceDir, configPath) {
|
|
|
3823
4221
|
fs_1.default.mkdirSync(configPath, { recursive: true });
|
|
3824
4222
|
// Full copy on purpose: never hardlink — RocksDB appends to WAL/MANIFEST in
|
|
3825
4223
|
// place, and linked files would corrupt the source chain.
|
|
3826
|
-
|
|
4224
|
+
const excludedTopLevelEntries = new Set(['network', 'logs', 'tmp']);
|
|
4225
|
+
fs_1.default.mkdirSync(targetData, { recursive: true });
|
|
4226
|
+
// Enumerate top-level entries instead of relying on fs.cp's filter paths,
|
|
4227
|
+
// which may use Windows extended-length prefixes and bypass relative-path
|
|
4228
|
+
// comparisons.
|
|
4229
|
+
for (const entry of fs_1.default.readdirSync(sourceData)) {
|
|
4230
|
+
if (excludedTopLevelEntries.has(entry))
|
|
4231
|
+
continue;
|
|
4232
|
+
fs_1.default.cpSync(path_1.default.join(sourceData, entry), path_1.default.join(targetData, entry), { recursive: true });
|
|
4233
|
+
}
|
|
4234
|
+
logger_1.logger.info('Excluded source network peers and transient logs/tmp data from the fork.');
|
|
4235
|
+
}
|
|
4236
|
+
function isolateForkCkbConfig(config) {
|
|
4237
|
+
var _a, _b;
|
|
4238
|
+
const network = Object.assign({}, ((_a = config.network) !== null && _a !== void 0 ? _a : {}));
|
|
4239
|
+
network.bootnodes = [];
|
|
4240
|
+
network.max_outbound_peers = 0;
|
|
4241
|
+
network.whitelist_only = true;
|
|
4242
|
+
network.discovery_local_address = false;
|
|
4243
|
+
const loggerConfig = Object.assign({}, ((_b = config.logger) !== null && _b !== void 0 ? _b : {}));
|
|
4244
|
+
loggerConfig.filter = 'warn';
|
|
4245
|
+
return Object.assign(Object.assign({}, config), { network, logger: loggerConfig });
|
|
3827
4246
|
}
|
|
3828
4247
|
function runCkbInit(ckbBinPath, configPath, specFile) {
|
|
3829
4248
|
// argv form, never a shell: --spec-file is user input and quote-wrapping
|
|
@@ -3831,7 +4250,48 @@ function runCkbInit(ckbBinPath, configPath, specFile) {
|
|
|
3831
4250
|
// inside double quotes).
|
|
3832
4251
|
const args = ['init', '-C', configPath, '--chain', 'dev', '--import-spec', specFile, '--force'];
|
|
3833
4252
|
logger_1.logger.debug(`Running: ${ckbBinPath} ${args.join(' ')}`);
|
|
3834
|
-
return (0, child_process_1.execFileSync)(ckbBinPath, args, {
|
|
4253
|
+
return (0, child_process_1.execFileSync)(ckbBinPath, args, {
|
|
4254
|
+
encoding: 'utf8',
|
|
4255
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
4256
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4257
|
+
});
|
|
4258
|
+
}
|
|
4259
|
+
function migrationNeededFromExitCode(status) {
|
|
4260
|
+
if (status === 0)
|
|
4261
|
+
return true;
|
|
4262
|
+
if (status === 64)
|
|
4263
|
+
return false;
|
|
4264
|
+
throw new Error(`ckb migrate --check failed with exit code ${status !== null && status !== void 0 ? status : 'unknown'}`);
|
|
4265
|
+
}
|
|
4266
|
+
function isDatabaseMigrationNeeded(ckbBinPath, configPath) {
|
|
4267
|
+
const result = (0, child_process_1.spawnSync)(ckbBinPath, ['migrate', '--check', '-C', configPath], {
|
|
4268
|
+
encoding: 'utf8',
|
|
4269
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4270
|
+
});
|
|
4271
|
+
if (result.error) {
|
|
4272
|
+
throw new Error(`Could not check the CKB database version: ${result.error.message}`);
|
|
4273
|
+
}
|
|
4274
|
+
try {
|
|
4275
|
+
return migrationNeededFromExitCode(result.status);
|
|
4276
|
+
}
|
|
4277
|
+
catch (error) {
|
|
4278
|
+
const details = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
|
|
4279
|
+
throw new Error(`${error.message}${details ? `: ${details}` : ''}`);
|
|
4280
|
+
}
|
|
4281
|
+
}
|
|
4282
|
+
function migrateDatabaseCopy(ckbBinPath, configPath) {
|
|
4283
|
+
var _a, _b;
|
|
4284
|
+
logger_1.logger.info('Migrating the copied database (the source directory remains untouched) ..');
|
|
4285
|
+
const result = (0, child_process_1.spawnSync)(ckbBinPath, ['migrate', '--force', '-C', configPath], {
|
|
4286
|
+
encoding: 'utf8',
|
|
4287
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4288
|
+
});
|
|
4289
|
+
if (result.error || result.status !== 0) {
|
|
4290
|
+
const details = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
|
|
4291
|
+
throw new Error(`Failed to migrate the copied database: ${(_b = (_a = result.error) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : `exit code ${result.status}`}` +
|
|
4292
|
+
`${details ? `\n${details}` : ''}`);
|
|
4293
|
+
}
|
|
4294
|
+
logger_1.logger.success('Copied database migration completed.');
|
|
3835
4295
|
}
|
|
3836
4296
|
// Best-effort read of the source directory's own chain identity. `ckb
|
|
3837
4297
|
// list-hashes` resolves the chain spec of the given config dir (no database
|
|
@@ -3850,6 +4310,34 @@ function readSourceGenesisHash(ckbBinPath, sourceDir) {
|
|
|
3850
4310
|
return null;
|
|
3851
4311
|
}
|
|
3852
4312
|
}
|
|
4313
|
+
function validateForkSpec(ckbBinPath, sourceDir, specFile, declaredSource) {
|
|
4314
|
+
var _a;
|
|
4315
|
+
const tempConfigPath = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'offckb-fork-preflight-'));
|
|
4316
|
+
try {
|
|
4317
|
+
const initOutput = runCkbInit(ckbBinPath, tempConfigPath, specFile);
|
|
4318
|
+
const genesisHash = parseGenesisHashFromInitOutput(initOutput);
|
|
4319
|
+
if (!genesisHash) {
|
|
4320
|
+
throw new Error(`Could not parse the genesis hash from ckb init output:\n${initOutput}`);
|
|
4321
|
+
}
|
|
4322
|
+
const source = (_a = declaredSource !== null && declaredSource !== void 0 ? declaredSource : (0, const_1.identifyPublicChainByGenesisHash)(genesisHash)) !== null && _a !== void 0 ? _a : 'custom';
|
|
4323
|
+
if (source !== 'custom') {
|
|
4324
|
+
const expected = expectedGenesisHash(source);
|
|
4325
|
+
if (genesisHash !== expected) {
|
|
4326
|
+
throw new Error(`Genesis hash mismatch: expected ${expected} for ${source}, got ${genesisHash}. ` +
|
|
4327
|
+
'This usually means the chain spec does not match the source data.');
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
const sourceGenesisHash = readSourceGenesisHash(ckbBinPath, sourceDir);
|
|
4331
|
+
if (sourceGenesisHash && sourceGenesisHash !== genesisHash) {
|
|
4332
|
+
throw new Error(`The source directory is configured for a different chain (genesis ${sourceGenesisHash}) ` +
|
|
4333
|
+
`than the imported spec (genesis ${genesisHash}). Pass a matching --source or --spec-file.`);
|
|
4334
|
+
}
|
|
4335
|
+
return { source, genesisHash };
|
|
4336
|
+
}
|
|
4337
|
+
finally {
|
|
4338
|
+
fs_1.default.rmSync(tempConfigPath, { recursive: true, force: true });
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
3853
4341
|
// Overwrite the ckb-init-generated configs with offckb's own devnet configs so
|
|
3854
4342
|
// the fork behaves like a normal offckb devnet (miner account as block
|
|
3855
4343
|
// assembler, RPC on 8114 with the Indexer module, proxy on 28114). The
|
|
@@ -3857,29 +4345,26 @@ function readSourceGenesisHash(ckbBinPath, sourceDir) {
|
|
|
3857
4345
|
function alignConfigsWithOffckb(configPath) {
|
|
3858
4346
|
const settings = (0, setting_1.readSettings)();
|
|
3859
4347
|
const devnetSourcePath = path_1.default.resolve(setting_1.packageRootPath, './ckb/devnet');
|
|
3860
|
-
|
|
4348
|
+
const ckbConfigPath = path_1.default.join(configPath, 'ckb.toml');
|
|
4349
|
+
const parsedCkbConfig = toml_1.default.parse(fs_1.default.readFileSync(path_1.default.join(devnetSourcePath, 'ckb.toml'), 'utf8'));
|
|
4350
|
+
const ckbConfig = isolateForkCkbConfig(parsedCkbConfig);
|
|
4351
|
+
fs_1.default.writeFileSync(ckbConfigPath, toml_1.default.stringify(ckbConfig));
|
|
3861
4352
|
fs_1.default.copyFileSync(path_1.default.join(devnetSourcePath, 'default.db-options'), path_1.default.join(configPath, 'default.db-options'));
|
|
3862
4353
|
const minerToml = fs_1.default.readFileSync(path_1.default.join(devnetSourcePath, 'ckb-miner.toml'), 'utf8');
|
|
3863
4354
|
fs_1.default.writeFileSync(path_1.default.join(configPath, 'ckb-miner.toml'), minerToml.replace('http://ckb:8114/', settings.devnet.rpcUrl));
|
|
3864
4355
|
}
|
|
3865
4356
|
function forkDevnet(options) {
|
|
3866
4357
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3867
|
-
var _a
|
|
4358
|
+
var _a;
|
|
4359
|
+
if (!options.from) {
|
|
4360
|
+
throw new Error('Database fork requires a source CKB directory: --from <dir>.');
|
|
4361
|
+
}
|
|
3868
4362
|
const settings = (0, setting_1.readSettings)();
|
|
3869
4363
|
const configPath = settings.devnet.configPath;
|
|
3870
4364
|
const ckbVersion = settings.bins.defaultCKBVersion;
|
|
3871
4365
|
const sourceDir = path_1.default.resolve(options.from);
|
|
3872
4366
|
validateSourceDir(sourceDir);
|
|
3873
4367
|
assertSourceNodeStopped(sourceDir);
|
|
3874
|
-
assertOffckbDevnetStopped();
|
|
3875
|
-
if ((0, fs_2.isFolderExists)(configPath)) {
|
|
3876
|
-
if (!options.force) {
|
|
3877
|
-
throw new Error(`A devnet already exists at ${configPath}. Re-run with --force to replace it, ` +
|
|
3878
|
-
`or reset it first with: offckb clean`);
|
|
3879
|
-
}
|
|
3880
|
-
logger_1.logger.info(`Removing existing devnet at ${configPath} ..`);
|
|
3881
|
-
fs_1.default.rmSync(configPath, { recursive: true, force: true });
|
|
3882
|
-
}
|
|
3883
4368
|
// Identify the source chain: explicit flag > ckb.toml bundled spec.
|
|
3884
4369
|
let source = (_a = options.source) !== null && _a !== void 0 ? _a : null;
|
|
3885
4370
|
if (source == null) {
|
|
@@ -3894,41 +4379,55 @@ function forkDevnet(options) {
|
|
|
3894
4379
|
}
|
|
3895
4380
|
yield (0, install_1.installCKBBinary)(ckbVersion);
|
|
3896
4381
|
const ckbBinPath = (0, setting_1.getCKBBinaryPath)(ckbVersion);
|
|
4382
|
+
const databaseMigrationNeeded = isDatabaseMigrationNeeded(ckbBinPath, sourceDir);
|
|
4383
|
+
const sourcePeerStoreDetected = fs_1.default.existsSync(path_1.default.join(sourceDir, 'data', 'network', 'peer_store'));
|
|
4384
|
+
const specFile = yield resolveSpecFile(options, source !== null && source !== void 0 ? source : 'mainnet', ckbVersion);
|
|
4385
|
+
const specPreflight = validateForkSpec(ckbBinPath, sourceDir, specFile, source);
|
|
4386
|
+
source = specPreflight.source;
|
|
4387
|
+
logger_1.logger.info(`Fork preflight: source=${source}, genesis=${specPreflight.genesisHash}, CKB=${ckbVersion}, migration=${databaseMigrationNeeded ? 'required' : 'not required'}.`);
|
|
4388
|
+
if (sourcePeerStoreDetected) {
|
|
4389
|
+
logger_1.logger.info('A persisted source peer store was detected and will be excluded from the fork.');
|
|
4390
|
+
}
|
|
4391
|
+
if (options.dryRun) {
|
|
4392
|
+
logger_1.logger.success('Fork preflight passed. No devnet files were changed.');
|
|
4393
|
+
logger_1.logger.result({
|
|
4394
|
+
command: 'devnet.fork.preflight',
|
|
4395
|
+
sourceDir,
|
|
4396
|
+
source,
|
|
4397
|
+
genesisHash: specPreflight.genesisHash,
|
|
4398
|
+
ckbVersion,
|
|
4399
|
+
databaseMigrationNeeded,
|
|
4400
|
+
sourcePeerStoreDetected,
|
|
4401
|
+
sourcePeerStoreWillBeExcluded: true,
|
|
4402
|
+
targetDir: configPath,
|
|
4403
|
+
});
|
|
4404
|
+
return;
|
|
4405
|
+
}
|
|
4406
|
+
assertOffckbDevnetStopped();
|
|
4407
|
+
if (databaseMigrationNeeded && !options.migrate) {
|
|
4408
|
+
throw new Error(`The source database requires migration for CKB v${ckbVersion}. ` +
|
|
4409
|
+
'Re-run with --migrate to migrate only the copied devnet, leaving the source untouched.');
|
|
4410
|
+
}
|
|
4411
|
+
if ((0, fs_2.isFolderExists)(configPath)) {
|
|
4412
|
+
if (!options.force) {
|
|
4413
|
+
throw new Error(`A devnet already exists at ${configPath}. Re-run with --force to replace it, ` +
|
|
4414
|
+
`or reset it first with: offckb clean`);
|
|
4415
|
+
}
|
|
4416
|
+
logger_1.logger.info(`Removing existing devnet at ${configPath} ..`);
|
|
4417
|
+
fs_1.default.rmSync(configPath, { recursive: true, force: true });
|
|
4418
|
+
}
|
|
3897
4419
|
try {
|
|
3898
4420
|
// Inside the try so a failed copy (disk full, permissions, I/O) gets the
|
|
3899
4421
|
// same rollback: a partial configPath would otherwise block the next
|
|
3900
4422
|
// attempt as an "existing devnet".
|
|
3901
4423
|
copySourceData(sourceDir, configPath);
|
|
3902
|
-
const specFile = yield resolveSpecFile(options, source !== null && source !== void 0 ? source : 'mainnet', ckbVersion);
|
|
3903
4424
|
const initOutput = runCkbInit(ckbBinPath, configPath, specFile);
|
|
3904
4425
|
const genesisHash = parseGenesisHashFromInitOutput(initOutput);
|
|
3905
4426
|
if (!genesisHash) {
|
|
3906
4427
|
throw new Error(`Could not parse the genesis hash from ckb init output:\n${initOutput}`);
|
|
3907
4428
|
}
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
if (source == null) {
|
|
3911
|
-
source = (_b = (0, const_1.identifyPublicChainByGenesisHash)(genesisHash)) !== null && _b !== void 0 ? _b : 'custom';
|
|
3912
|
-
}
|
|
3913
|
-
if (source !== 'custom') {
|
|
3914
|
-
const expected = expectedGenesisHash(source);
|
|
3915
|
-
if (genesisHash !== expected) {
|
|
3916
|
-
throw new Error(`Genesis hash mismatch: expected ${expected} for ${source}, got ${genesisHash}. ` +
|
|
3917
|
-
`This usually means the chain spec does not match the source data. ` +
|
|
3918
|
-
`(Importing a testnet spec with a CKB older than v0.207.0 sets a wrong genesis_epoch_length, ` +
|
|
3919
|
-
`see nervosnetwork/ckb#5205.)`);
|
|
3920
|
-
}
|
|
3921
|
-
}
|
|
3922
|
-
// The genesis above comes from the imported spec alone — it cannot see
|
|
3923
|
-
// that --source/--spec-file contradicts the copied data (e.g. a mainnet
|
|
3924
|
-
// spec over testnet data would pass and only fail when the node boots).
|
|
3925
|
-
// Cross-check the source directory's own configured genesis and reject
|
|
3926
|
-
// mismatches now. Skipped (null) when the source is not a standard
|
|
3927
|
-
// config dir; the node's boot-time genesis check remains the backstop.
|
|
3928
|
-
const sourceGenesisHash = readSourceGenesisHash(ckbBinPath, sourceDir);
|
|
3929
|
-
if (sourceGenesisHash && sourceGenesisHash !== genesisHash) {
|
|
3930
|
-
throw new Error(`The source directory is configured for a different chain (genesis ${sourceGenesisHash}) ` +
|
|
3931
|
-
`than the imported spec (genesis ${genesisHash}). Pass a matching --source or --spec-file.`);
|
|
4429
|
+
if (genesisHash !== specPreflight.genesisHash) {
|
|
4430
|
+
throw new Error(`Chain spec changed after preflight: expected genesis ${specPreflight.genesisHash}, got ${genesisHash}.`);
|
|
3932
4431
|
}
|
|
3933
4432
|
const devSpecPath = path_1.default.join(configPath, 'specs', 'dev.toml');
|
|
3934
4433
|
const spec = toml_1.default.parse(fs_1.default.readFileSync(devSpecPath, 'utf8'));
|
|
@@ -3937,6 +4436,9 @@ function forkDevnet(options) {
|
|
|
3937
4436
|
fs_1.default.writeFileSync(tempSpecPath, toml_1.default.stringify(patched));
|
|
3938
4437
|
fs_1.default.renameSync(tempSpecPath, devSpecPath);
|
|
3939
4438
|
alignConfigsWithOffckb(configPath);
|
|
4439
|
+
if (databaseMigrationNeeded) {
|
|
4440
|
+
migrateDatabaseCopy(ckbBinPath, configPath);
|
|
4441
|
+
}
|
|
3940
4442
|
const state = {
|
|
3941
4443
|
source,
|
|
3942
4444
|
sourceDir,
|
|
@@ -3944,11 +4446,28 @@ function forkDevnet(options) {
|
|
|
3944
4446
|
genesisHash,
|
|
3945
4447
|
forkedAt: new Date().toISOString(),
|
|
3946
4448
|
firstRunPending: true,
|
|
4449
|
+
databaseMigrated: databaseMigrationNeeded,
|
|
4450
|
+
networkIsolated: true,
|
|
3947
4451
|
};
|
|
3948
4452
|
writeForkState(configPath, state);
|
|
3949
4453
|
logger_1.logger.success(`Devnet forked from ${sourceDir} (${source}, genesis ${genesisHash}).`);
|
|
3950
|
-
logger_1.logger.info('Start it with: offckb node');
|
|
4454
|
+
logger_1.logger.info('Start it with: offckb node --daemon');
|
|
3951
4455
|
logger_1.logger.info('The first run applies --skip-spec-check --overwrite-spec automatically.');
|
|
4456
|
+
logger_1.logger.info('Then inspect node, Indexer, and network isolation with: offckb devnet info');
|
|
4457
|
+
logger_1.logger.info('List source-prefix dev addresses with: offckb accounts');
|
|
4458
|
+
if (source === 'mainnet') {
|
|
4459
|
+
logger_1.logger.warn('MAINNET FORK REPLAY RISK: only sign with built-in dev keys and fork-mined cells.');
|
|
4460
|
+
}
|
|
4461
|
+
logger_1.logger.result({
|
|
4462
|
+
command: 'devnet.fork',
|
|
4463
|
+
sourceDir,
|
|
4464
|
+
source,
|
|
4465
|
+
genesisHash,
|
|
4466
|
+
ckbVersion,
|
|
4467
|
+
databaseMigrated: databaseMigrationNeeded,
|
|
4468
|
+
networkIsolated: true,
|
|
4469
|
+
next: ['offckb node --daemon', 'offckb devnet info', 'offckb accounts'],
|
|
4470
|
+
});
|
|
3952
4471
|
}
|
|
3953
4472
|
catch (error) {
|
|
3954
4473
|
// Leave no half-forked devnet behind.
|
|
@@ -3959,6 +4478,124 @@ function forkDevnet(options) {
|
|
|
3959
4478
|
}
|
|
3960
4479
|
|
|
3961
4480
|
|
|
4481
|
+
/***/ }),
|
|
4482
|
+
|
|
4483
|
+
/***/ 19326:
|
|
4484
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
4485
|
+
|
|
4486
|
+
"use strict";
|
|
4487
|
+
|
|
4488
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4489
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4490
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4491
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
4492
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
4493
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
4494
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
4495
|
+
});
|
|
4496
|
+
};
|
|
4497
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
4498
|
+
exports.checkNodeReadiness = checkNodeReadiness;
|
|
4499
|
+
exports.waitForNodeReady = waitForNodeReady;
|
|
4500
|
+
exports.checkConfiguredDevnetReadiness = checkConfiguredDevnetReadiness;
|
|
4501
|
+
exports.warnIfForkIndexerIsBehind = warnIfForkIndexerIsBehind;
|
|
4502
|
+
const setting_1 = __nccwpck_require__(25546);
|
|
4503
|
+
const base_1 = __nccwpck_require__(69951);
|
|
4504
|
+
const logger_1 = __nccwpck_require__(65370);
|
|
4505
|
+
const json_rpc_1 = __nccwpck_require__(51726);
|
|
4506
|
+
const fork_1 = __nccwpck_require__(77912);
|
|
4507
|
+
function parseHexNumber(value) {
|
|
4508
|
+
if (typeof value !== 'string' || !/^0x[0-9a-f]+$/i.test(value))
|
|
4509
|
+
return undefined;
|
|
4510
|
+
return BigInt(value);
|
|
4511
|
+
}
|
|
4512
|
+
function checkNodeReadiness(rpcUrl_1) {
|
|
4513
|
+
return __awaiter(this, arguments, void 0, function* (rpcUrl, timeoutMs = 3000) {
|
|
4514
|
+
try {
|
|
4515
|
+
const [nodeInfo, tipValue] = yield Promise.all([
|
|
4516
|
+
(0, json_rpc_1.callJsonRpc)(rpcUrl, 'local_node_info', [], timeoutMs),
|
|
4517
|
+
(0, json_rpc_1.callJsonRpc)(rpcUrl, 'get_tip_block_number', [], timeoutMs),
|
|
4518
|
+
]);
|
|
4519
|
+
const nodeTip = parseHexNumber(tipValue);
|
|
4520
|
+
if (nodeTip == null) {
|
|
4521
|
+
throw new Error(`Invalid node tip returned by ${rpcUrl}`);
|
|
4522
|
+
}
|
|
4523
|
+
let indexerTip;
|
|
4524
|
+
try {
|
|
4525
|
+
const value = yield (0, json_rpc_1.callJsonRpc)(rpcUrl, 'get_indexer_tip', [], timeoutMs);
|
|
4526
|
+
indexerTip = parseHexNumber(value === null || value === void 0 ? void 0 : value.block_number);
|
|
4527
|
+
}
|
|
4528
|
+
catch (_a) {
|
|
4529
|
+
// Indexer readiness is reported separately and does not make the node RPC unhealthy.
|
|
4530
|
+
}
|
|
4531
|
+
let peers;
|
|
4532
|
+
try {
|
|
4533
|
+
const value = yield (0, json_rpc_1.callJsonRpc)(rpcUrl, 'get_peers', [], timeoutMs);
|
|
4534
|
+
peers = Array.isArray(value) ? value.length : undefined;
|
|
4535
|
+
}
|
|
4536
|
+
catch (_b) {
|
|
4537
|
+
// The Net RPC module can be disabled on custom nodes.
|
|
4538
|
+
}
|
|
4539
|
+
const indexerLag = indexerTip == null ? undefined : indexerTip >= nodeTip ? BigInt(0) : nodeTip - indexerTip;
|
|
4540
|
+
return {
|
|
4541
|
+
ready: true,
|
|
4542
|
+
rpcUrl,
|
|
4543
|
+
version: typeof (nodeInfo === null || nodeInfo === void 0 ? void 0 : nodeInfo.version) === 'string' ? nodeInfo.version : undefined,
|
|
4544
|
+
nodeTip,
|
|
4545
|
+
indexerTip,
|
|
4546
|
+
indexerLag,
|
|
4547
|
+
peers,
|
|
4548
|
+
};
|
|
4549
|
+
}
|
|
4550
|
+
catch (error) {
|
|
4551
|
+
return {
|
|
4552
|
+
ready: false,
|
|
4553
|
+
rpcUrl,
|
|
4554
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4555
|
+
};
|
|
4556
|
+
}
|
|
4557
|
+
});
|
|
4558
|
+
}
|
|
4559
|
+
function waitForNodeReady(rpcUrl_1, timeoutMs_1) {
|
|
4560
|
+
return __awaiter(this, arguments, void 0, function* (rpcUrl, timeoutMs, isProcessAlive = () => true) {
|
|
4561
|
+
const start = Date.now();
|
|
4562
|
+
let last = yield checkNodeReadiness(rpcUrl);
|
|
4563
|
+
while (!last.ready && isProcessAlive() && Date.now() - start < timeoutMs) {
|
|
4564
|
+
yield new Promise((resolve) => setTimeout(resolve, 500));
|
|
4565
|
+
last = yield checkNodeReadiness(rpcUrl);
|
|
4566
|
+
}
|
|
4567
|
+
return last;
|
|
4568
|
+
});
|
|
4569
|
+
}
|
|
4570
|
+
function checkConfiguredDevnetReadiness() {
|
|
4571
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
4572
|
+
return checkNodeReadiness((0, setting_1.readSettings)().devnet.rpcUrl);
|
|
4573
|
+
});
|
|
4574
|
+
}
|
|
4575
|
+
function warnIfForkIndexerIsBehind(network) {
|
|
4576
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
4577
|
+
var _a;
|
|
4578
|
+
if (network !== base_1.Network.devnet)
|
|
4579
|
+
return undefined;
|
|
4580
|
+
const settings = (0, setting_1.readSettings)();
|
|
4581
|
+
if (!(0, fork_1.readForkState)(settings.devnet.configPath))
|
|
4582
|
+
return undefined;
|
|
4583
|
+
const readiness = yield checkNodeReadiness(settings.devnet.rpcUrl);
|
|
4584
|
+
if (!readiness.ready) {
|
|
4585
|
+
logger_1.logger.warn(`The forked devnet is not RPC-ready: ${(_a = readiness.error) !== null && _a !== void 0 ? _a : 'health check failed'}`);
|
|
4586
|
+
}
|
|
4587
|
+
else if (readiness.indexerTip == null) {
|
|
4588
|
+
logger_1.logger.warn('The CKB indexer is not ready yet; cell and balance lookups may be incomplete. Check `offckb devnet info`.');
|
|
4589
|
+
}
|
|
4590
|
+
else if (readiness.indexerLag && readiness.indexerLag > BigInt(0)) {
|
|
4591
|
+
logger_1.logger.warn(`The CKB indexer is ${readiness.indexerLag} blocks behind the node; cell and balance lookups may be incomplete. ` +
|
|
4592
|
+
'Check `offckb devnet info`.');
|
|
4593
|
+
}
|
|
4594
|
+
return readiness;
|
|
4595
|
+
});
|
|
4596
|
+
}
|
|
4597
|
+
|
|
4598
|
+
|
|
3962
4599
|
/***/ }),
|
|
3963
4600
|
|
|
3964
4601
|
/***/ 1360:
|
|
@@ -3990,19 +4627,27 @@ function initChainIfNeeded() {
|
|
|
3990
4627
|
const settings = (0, setting_1.readSettings)();
|
|
3991
4628
|
const devnetSourcePath = path_1.default.resolve(setting_1.packageRootPath, './ckb/devnet');
|
|
3992
4629
|
const devnetConfigPath = settings.devnet.configPath;
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
4630
|
+
const requiredConfigFiles = ['ckb.toml', 'ckb-miner.toml', path_1.default.join('specs', 'dev.toml')];
|
|
4631
|
+
const isInitialized = (0, fs_2.isFolderExists)(devnetConfigPath) &&
|
|
4632
|
+
requiredConfigFiles.every((relativePath) => fs_1.default.existsSync(path_1.default.join(devnetConfigPath, relativePath)));
|
|
4633
|
+
const minerConfigPath = path_1.default.join(devnetConfigPath, 'ckb-miner.toml');
|
|
4634
|
+
const minerConfigWasMissing = !fs_1.default.existsSync(minerConfigPath);
|
|
4635
|
+
// Daemon mode creates data/logs before the child starts. A directory-only
|
|
4636
|
+
// check therefore mistakes a fresh install for an initialized chain. Check
|
|
4637
|
+
// the files CKB actually needs instead, and repair an incomplete directory.
|
|
4638
|
+
if (!isInitialized) {
|
|
4639
|
+
yield (0, fs_2.copyFilesWithExclusion)(devnetSourcePath, devnetConfigPath, ['data'], false);
|
|
3996
4640
|
logger_1.logger.debug(`init devnet config folder: ${devnetConfigPath}`);
|
|
3997
4641
|
// copy and edit ckb-miner.toml
|
|
3998
4642
|
const minerToml = path_1.default.join(devnetSourcePath, 'ckb-miner.toml');
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4643
|
+
if (minerConfigWasMissing) {
|
|
4644
|
+
// Read the content of the ckb-miner.toml file
|
|
4645
|
+
const data = fs_1.default.readFileSync(minerToml, 'utf8');
|
|
4646
|
+
// Replace the URL
|
|
4647
|
+
const modifiedData = data.replace('http://ckb:8114/', settings.devnet.rpcUrl);
|
|
4648
|
+
// Write the modified content back to the file
|
|
4649
|
+
fs_1.default.writeFileSync(minerConfigPath, modifiedData, 'utf8');
|
|
4650
|
+
}
|
|
4006
4651
|
}
|
|
4007
4652
|
});
|
|
4008
4653
|
}
|
|
@@ -4132,6 +4777,7 @@ function downloadCKBBinaryAndUnzip(version) {
|
|
|
4132
4777
|
}
|
|
4133
4778
|
catch (error) {
|
|
4134
4779
|
logger_1.logger.error('Error installing dependency binary:', error.message);
|
|
4780
|
+
throw error;
|
|
4135
4781
|
}
|
|
4136
4782
|
});
|
|
4137
4783
|
}
|
|
@@ -5193,7 +5839,7 @@ class CKB {
|
|
|
5193
5839
|
});
|
|
5194
5840
|
}
|
|
5195
5841
|
transfer(_a) {
|
|
5196
|
-
return __awaiter(this, arguments, void 0, function* ({ privateKey, toAddress, amountInCKB }) {
|
|
5842
|
+
return __awaiter(this, arguments, void 0, function* ({ privateKey, toAddress, amountInCKB, rejectInputsAtOrBeforeBlock, }) {
|
|
5197
5843
|
const signer = this.buildSigner(privateKey);
|
|
5198
5844
|
const to = yield core_1.ccc.Address.fromString(toAddress, this.client);
|
|
5199
5845
|
const tx = core_1.ccc.Transaction.from({
|
|
@@ -5206,6 +5852,7 @@ class CKB {
|
|
|
5206
5852
|
});
|
|
5207
5853
|
yield tx.completeInputsByCapacity(signer);
|
|
5208
5854
|
yield tx.completeFeeBy(signer, this.feeRate);
|
|
5855
|
+
yield this.assertInputsCreatedAfter(tx, rejectInputsAtOrBeforeBlock);
|
|
5209
5856
|
const txHash = yield signer.sendTransaction(tx);
|
|
5210
5857
|
return txHash;
|
|
5211
5858
|
});
|
|
@@ -5375,7 +6022,7 @@ class CKB {
|
|
|
5375
6022
|
});
|
|
5376
6023
|
}
|
|
5377
6024
|
udtTransfer(_a) {
|
|
5378
|
-
return __awaiter(this, arguments, void 0, function* ({ privateKey, toAddress, amount, udtType, kind }) {
|
|
6025
|
+
return __awaiter(this, arguments, void 0, function* ({ privateKey, toAddress, amount, udtType, kind, rejectInputsAtOrBeforeBlock, }) {
|
|
5379
6026
|
const signer = this.buildSigner(privateKey);
|
|
5380
6027
|
const to = yield core_1.ccc.Address.fromString(toAddress, this.client);
|
|
5381
6028
|
const amountBigInt = (0, validator_1.validateUdtAmount)(amount);
|
|
@@ -5406,10 +6053,28 @@ class CKB {
|
|
|
5406
6053
|
tx.outputsData.push(core_1.ccc.hexFrom(core_1.ccc.numToBytes(changeAmount, 16)));
|
|
5407
6054
|
}
|
|
5408
6055
|
yield tx.completeFeeBy(signer, this.feeRate);
|
|
6056
|
+
yield this.assertInputsCreatedAfter(tx, rejectInputsAtOrBeforeBlock);
|
|
5409
6057
|
const txHash = yield signer.sendTransaction(tx);
|
|
5410
6058
|
return txHash;
|
|
5411
6059
|
});
|
|
5412
6060
|
}
|
|
6061
|
+
assertInputsCreatedAfter(tx, blockNumber) {
|
|
6062
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6063
|
+
if (blockNumber == null)
|
|
6064
|
+
return;
|
|
6065
|
+
for (const input of tx.inputs) {
|
|
6066
|
+
const outPoint = input.previousOutput;
|
|
6067
|
+
const origin = yield this.client.getTransactionNoCache(outPoint.txHash);
|
|
6068
|
+
if ((origin === null || origin === void 0 ? void 0 : origin.blockNumber) == null) {
|
|
6069
|
+
throw new Error(`Refusing to sign: could not verify the origin block of input ${outPoint.txHash}:${outPoint.index}.`);
|
|
6070
|
+
}
|
|
6071
|
+
if (origin.blockNumber <= blockNumber) {
|
|
6072
|
+
throw new Error(`Refusing to sign: input ${outPoint.txHash}:${outPoint.index} was created at block ${origin.blockNumber}, ` +
|
|
6073
|
+
`at or before the Mainnet fork boundary ${blockNumber}.`);
|
|
6074
|
+
}
|
|
6075
|
+
}
|
|
6076
|
+
});
|
|
6077
|
+
}
|
|
5413
6078
|
udtIssue(_a) {
|
|
5414
6079
|
return __awaiter(this, arguments, void 0, function* ({ privateKey, kind, amount, typeArgs, toAddress }) {
|
|
5415
6080
|
const signer = this.buildSigner(privateKey);
|
|
@@ -5422,7 +6087,7 @@ class CKB {
|
|
|
5422
6087
|
logger_1.logger.warn('SUDT type args are derived from the issuer lock hash; --type-args is ignored');
|
|
5423
6088
|
}
|
|
5424
6089
|
const issuerLockHash = signerAddress.script.hash();
|
|
5425
|
-
resolvedTypeArgs =
|
|
6090
|
+
resolvedTypeArgs = issuerLockHash;
|
|
5426
6091
|
}
|
|
5427
6092
|
else {
|
|
5428
6093
|
if (typeArgs) {
|
|
@@ -5450,7 +6115,7 @@ class CKB {
|
|
|
5450
6115
|
yield tx.completeInputsByCapacity(signer);
|
|
5451
6116
|
yield tx.completeFeeBy(signer, this.feeRate);
|
|
5452
6117
|
const txHash = yield signer.sendTransaction(tx);
|
|
5453
|
-
return txHash;
|
|
6118
|
+
return { txHash, typeArgs: resolvedTypeArgs, receiver: to.toString() };
|
|
5454
6119
|
});
|
|
5455
6120
|
}
|
|
5456
6121
|
udtDestroy(_a) {
|
|
@@ -6917,6 +7582,16 @@ const DOWNLOAD_TIMEOUT_MS = 120000;
|
|
|
6917
7582
|
const EXTRACT_TIMEOUT_MS = 60000;
|
|
6918
7583
|
// Strict semver regex: v<major>.<minor>.<patch> (no leading zeros on digits)
|
|
6919
7584
|
const STRICT_VERSION_REGEX = /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
7585
|
+
// Independently pinned digests for the default release. Keeping these in
|
|
7586
|
+
// offckb makes the default installation verifiable even though ckb-tui v0.1.3
|
|
7587
|
+
// did not upload a checksums-sha256.txt asset.
|
|
7588
|
+
const KNOWN_SHA256 = {
|
|
7589
|
+
'v0.1.3': {
|
|
7590
|
+
'ckb-tui-with-node-linux-amd64.tar.gz': '33455cefe2c016149fa8fa3abde7960b348d4606afef9279d787ac8a8b59956f',
|
|
7591
|
+
'ckb-tui-with-node-macos-aarch64.tar.gz': 'de18107ec179ced03608da956013e38ae82e6c1fae588f12c17d138ee6ee072c',
|
|
7592
|
+
'ckb-tui-with-node-windows-amd64.zip': '749d8e09fd5d23fc8af12892b7d197add5aae004f7438678023e4a973f3fd58b',
|
|
7593
|
+
},
|
|
7594
|
+
};
|
|
6920
7595
|
class CKBTui {
|
|
6921
7596
|
/**
|
|
6922
7597
|
* Pure lookup — returns the expected binary path without triggering any
|
|
@@ -7033,7 +7708,7 @@ class CKBTui {
|
|
|
7033
7708
|
if (curlResult.status !== 0) {
|
|
7034
7709
|
throw new Error(`curl exited with code ${curlResult.status}`);
|
|
7035
7710
|
}
|
|
7036
|
-
// 2. Verify checksum
|
|
7711
|
+
// 2. Verify checksum. Installation fails closed if no trusted digest exists.
|
|
7037
7712
|
this.verifyChecksum(version, assetName, archivePath);
|
|
7038
7713
|
// 3. Extract to temp directory
|
|
7039
7714
|
logger_1.logger.info('Extracting...');
|
|
@@ -7071,64 +7746,24 @@ class CKBTui {
|
|
|
7071
7746
|
}
|
|
7072
7747
|
}
|
|
7073
7748
|
}
|
|
7074
|
-
/**
|
|
7075
|
-
* Best-effort SHA-256 checksum verification.
|
|
7076
|
-
* Downloads the checksum file published alongside the release asset and
|
|
7077
|
-
* verifies the downloaded archive. Logs a warning (but does not fail) if
|
|
7078
|
-
* the checksum file is unavailable — this maintains compatibility while
|
|
7079
|
-
* the upstream project adopts checksum publishing.
|
|
7080
|
-
*/
|
|
7749
|
+
/** Verify against an independently pinned digest. */
|
|
7081
7750
|
static verifyChecksum(version, assetName, archivePath) {
|
|
7082
|
-
|
|
7083
|
-
const
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
});
|
|
7088
|
-
if (fetchResult.status !== 0) {
|
|
7089
|
-
logger_1.logger.warn(`SHA-256 checksum file not available for version ${version}. ` +
|
|
7090
|
-
'Skipping integrity verification. Consider asking the upstream maintainer to publish checksum files.');
|
|
7091
|
-
return;
|
|
7092
|
-
}
|
|
7093
|
-
try {
|
|
7094
|
-
const checksumContent = fs.readFileSync(checksumPath, 'utf8');
|
|
7095
|
-
const expectedHash = this.parseChecksumFile(checksumContent, assetName);
|
|
7096
|
-
if (!expectedHash) {
|
|
7097
|
-
logger_1.logger.warn(`Checksum entry for "${assetName}" not found in checksums file. Skipping verification.`);
|
|
7098
|
-
return;
|
|
7099
|
-
}
|
|
7100
|
-
const actualHash = crypto.createHash('sha256').update(fs.readFileSync(archivePath)).digest('hex');
|
|
7101
|
-
if (actualHash !== expectedHash) {
|
|
7102
|
-
throw new Error(`SHA-256 checksum mismatch for ${assetName}.\n` +
|
|
7103
|
-
`Expected: ${expectedHash}\nActual: ${actualHash}\n` +
|
|
7104
|
-
'The downloaded file may be corrupted or tampered with.');
|
|
7105
|
-
}
|
|
7106
|
-
logger_1.logger.info('SHA-256 checksum verified successfully.');
|
|
7107
|
-
}
|
|
7108
|
-
finally {
|
|
7109
|
-
try {
|
|
7110
|
-
fs.unlinkSync(checksumPath);
|
|
7111
|
-
}
|
|
7112
|
-
catch (_a) {
|
|
7113
|
-
// Best effort
|
|
7114
|
-
}
|
|
7751
|
+
var _a;
|
|
7752
|
+
const pinnedHash = (_a = KNOWN_SHA256[version]) === null || _a === void 0 ? void 0 : _a[assetName];
|
|
7753
|
+
if (!pinnedHash) {
|
|
7754
|
+
throw new Error(`No trusted SHA-256 checksum is pinned for ckb-tui ${version} (${assetName}). ` +
|
|
7755
|
+
'Refusing to install an unverified binary.');
|
|
7115
7756
|
}
|
|
7757
|
+
this.assertChecksum(archivePath, assetName, pinnedHash);
|
|
7116
7758
|
}
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
const trimmed = line.trim();
|
|
7124
|
-
if (!trimmed || trimmed.startsWith('#'))
|
|
7125
|
-
continue;
|
|
7126
|
-
const match = trimmed.match(/^([0-9a-fA-F]{64})\s+[*]?(.+)$/);
|
|
7127
|
-
if (match && match[2] === assetName) {
|
|
7128
|
-
return match[1].toLowerCase();
|
|
7129
|
-
}
|
|
7759
|
+
static assertChecksum(archivePath, assetName, expectedHash) {
|
|
7760
|
+
const actualHash = crypto.createHash('sha256').update(fs.readFileSync(archivePath)).digest('hex');
|
|
7761
|
+
if (actualHash !== expectedHash) {
|
|
7762
|
+
throw new Error(`SHA-256 checksum mismatch for ${assetName}.\n` +
|
|
7763
|
+
`Expected: ${expectedHash}\nActual: ${actualHash}\n` +
|
|
7764
|
+
'The downloaded file may be corrupted or tampered with.');
|
|
7130
7765
|
}
|
|
7131
|
-
|
|
7766
|
+
logger_1.logger.info('SHA-256 checksum verified successfully.');
|
|
7132
7767
|
}
|
|
7133
7768
|
/**
|
|
7134
7769
|
* Extract a downloaded archive to the given directory.
|
|
@@ -9380,6 +10015,77 @@ function encodeBinPathForTerminal(path) {
|
|
|
9380
10015
|
}
|
|
9381
10016
|
|
|
9382
10017
|
|
|
10018
|
+
/***/ }),
|
|
10019
|
+
|
|
10020
|
+
/***/ 47625:
|
|
10021
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
10022
|
+
|
|
10023
|
+
"use strict";
|
|
10024
|
+
|
|
10025
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10026
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10027
|
+
};
|
|
10028
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
10029
|
+
exports.warnIfMainnetForkSigning = warnIfMainnetForkSigning;
|
|
10030
|
+
exports.validateMainnetForkSigning = validateMainnetForkSigning;
|
|
10031
|
+
const account_json_1 = __importDefault(__nccwpck_require__(13849));
|
|
10032
|
+
const account_1 = __nccwpck_require__(59489);
|
|
10033
|
+
const setting_1 = __nccwpck_require__(25546);
|
|
10034
|
+
const fork_1 = __nccwpck_require__(77912);
|
|
10035
|
+
const base_1 = __nccwpck_require__(69951);
|
|
10036
|
+
const logger_1 = __nccwpck_require__(65370);
|
|
10037
|
+
const BUILT_IN_DEV_KEYS = new Set([...account_json_1.default.map((account) => account.privkey), account_1.ckbDevnetMinerAccount.privkey].map((key) => key.toLowerCase()));
|
|
10038
|
+
function warnIfMainnetForkSigning(network, privateKey) {
|
|
10039
|
+
if (!readMainnetForkState(network))
|
|
10040
|
+
return;
|
|
10041
|
+
logMainnetForkSigningWarning(privateKey);
|
|
10042
|
+
}
|
|
10043
|
+
/**
|
|
10044
|
+
* Fail closed before a Mainnet-fork transfer is constructed or signed.
|
|
10045
|
+
* Returns the copied-chain tip so the transaction layer can reject inputs
|
|
10046
|
+
* created at or before the fork boundary after input selection.
|
|
10047
|
+
*/
|
|
10048
|
+
function validateMainnetForkSigning(network, privateKey, allowMainnetReplayRisk = false) {
|
|
10049
|
+
const fork = readMainnetForkState(network);
|
|
10050
|
+
if (!fork)
|
|
10051
|
+
return undefined;
|
|
10052
|
+
logMainnetForkSigningWarning(privateKey);
|
|
10053
|
+
if (!BUILT_IN_DEV_KEYS.has(privateKey.trim().toLowerCase()) && !allowMainnetReplayRisk) {
|
|
10054
|
+
throw new Error('Refusing to sign with a non-built-in private key on a Mainnet fork. ' +
|
|
10055
|
+
'Use --allow-mainnet-replay-risk only after verifying that no copied Mainnet input will be selected.');
|
|
10056
|
+
}
|
|
10057
|
+
if (fork.forkBlockNumber == null) {
|
|
10058
|
+
throw new Error('Mainnet fork boundary metadata is missing. Restart or recreate the fork before signing so input origins can be verified.');
|
|
10059
|
+
}
|
|
10060
|
+
try {
|
|
10061
|
+
const blockNumber = BigInt(fork.forkBlockNumber);
|
|
10062
|
+
if (blockNumber < BigInt(0))
|
|
10063
|
+
throw new Error('negative block number');
|
|
10064
|
+
return blockNumber;
|
|
10065
|
+
}
|
|
10066
|
+
catch (_a) {
|
|
10067
|
+
throw new Error(`Invalid Mainnet fork boundary metadata: ${fork.forkBlockNumber}`);
|
|
10068
|
+
}
|
|
10069
|
+
}
|
|
10070
|
+
function readMainnetForkState(network) {
|
|
10071
|
+
if (network !== base_1.Network.devnet)
|
|
10072
|
+
return null;
|
|
10073
|
+
const settings = (0, setting_1.readSettings)();
|
|
10074
|
+
const fork = (0, fork_1.readForkState)(settings.devnet.configPath);
|
|
10075
|
+
return (fork === null || fork === void 0 ? void 0 : fork.source) === 'mainnet' ? fork : null;
|
|
10076
|
+
}
|
|
10077
|
+
function logMainnetForkSigningWarning(privateKey) {
|
|
10078
|
+
logger_1.logger.warn([
|
|
10079
|
+
'MAINNET FORK REPLAY RISK: CKB transactions have no chain id.',
|
|
10080
|
+
'A transaction spending cells copied from Mainnet can also be valid on Mainnet.',
|
|
10081
|
+
'Use only built-in dev keys and fork-mined cells unless you explicitly accept that risk.',
|
|
10082
|
+
]);
|
|
10083
|
+
if (!BUILT_IN_DEV_KEYS.has(privateKey.trim().toLowerCase())) {
|
|
10084
|
+
logger_1.logger.warn('A non-built-in private key is being used on a Mainnet fork. Verify every input before signing.');
|
|
10085
|
+
}
|
|
10086
|
+
}
|
|
10087
|
+
|
|
10088
|
+
|
|
9383
10089
|
/***/ }),
|
|
9384
10090
|
|
|
9385
10091
|
/***/ 37293:
|
|
@@ -9485,22 +10191,23 @@ function copyFileSync(source, target) {
|
|
|
9485
10191
|
}
|
|
9486
10192
|
fs.writeFileSync(targetFile, fs.readFileSync(source));
|
|
9487
10193
|
}
|
|
9488
|
-
function copyFilesWithExclusion(
|
|
9489
|
-
return __awaiter(this,
|
|
10194
|
+
function copyFilesWithExclusion(sourceDir_1, destinationDir_1, excludedFolders_1) {
|
|
10195
|
+
return __awaiter(this, arguments, void 0, function* (sourceDir, destinationDir, excludedFolders, overwrite = true) {
|
|
9490
10196
|
try {
|
|
9491
10197
|
// Ensure the destination directory exists
|
|
9492
10198
|
yield fs.promises.mkdir(destinationDir, { recursive: true });
|
|
9493
10199
|
// Start copying recursively from the source directory
|
|
9494
|
-
yield copyRecursive(sourceDir, destinationDir, excludedFolders);
|
|
10200
|
+
yield copyRecursive(sourceDir, destinationDir, excludedFolders, overwrite);
|
|
9495
10201
|
}
|
|
9496
10202
|
catch (error) {
|
|
9497
10203
|
logger_1.logger.error('An error occurred during copying files:', error);
|
|
10204
|
+
throw error;
|
|
9498
10205
|
}
|
|
9499
10206
|
});
|
|
9500
10207
|
}
|
|
9501
10208
|
// Function to recursively copy files and directories
|
|
9502
|
-
function copyRecursive(
|
|
9503
|
-
return __awaiter(this,
|
|
10209
|
+
function copyRecursive(source_1, destination_1, excludedFolders_1) {
|
|
10210
|
+
return __awaiter(this, arguments, void 0, function* (source, destination, excludedFolders, overwrite = true) {
|
|
9504
10211
|
// Get a list of all files and directories in the source directory
|
|
9505
10212
|
const files = yield fs.promises.readdir(source);
|
|
9506
10213
|
// Iterate through each file or directory
|
|
@@ -9517,12 +10224,14 @@ function copyRecursive(source, destination, excludedFolders) {
|
|
|
9517
10224
|
else {
|
|
9518
10225
|
// Ensure destination directory exists before copying
|
|
9519
10226
|
yield fs.promises.mkdir(destPath, { recursive: true });
|
|
9520
|
-
yield copyRecursive(sourcePath, destPath, excludedFolders);
|
|
10227
|
+
yield copyRecursive(sourcePath, destPath, excludedFolders, overwrite);
|
|
9521
10228
|
}
|
|
9522
10229
|
}
|
|
9523
10230
|
else {
|
|
9524
10231
|
// Otherwise, copy the file
|
|
9525
|
-
|
|
10232
|
+
if (overwrite || !fs.existsSync(destPath)) {
|
|
10233
|
+
yield fs.promises.copyFile(sourcePath, destPath);
|
|
10234
|
+
}
|
|
9526
10235
|
}
|
|
9527
10236
|
}
|
|
9528
10237
|
});
|
|
@@ -9767,6 +10476,7 @@ const levelColors = {
|
|
|
9767
10476
|
};
|
|
9768
10477
|
class UnifiedLogger {
|
|
9769
10478
|
constructor(options = {}) {
|
|
10479
|
+
this.resultEmitted = false;
|
|
9770
10480
|
this.enableColors = options.enableColors !== false;
|
|
9771
10481
|
this.showLevel = options.showLevel !== false;
|
|
9772
10482
|
this.jsonMode = options.jsonMode === true;
|
|
@@ -9776,8 +10486,8 @@ class UnifiedLogger {
|
|
|
9776
10486
|
levels: {
|
|
9777
10487
|
error: 0,
|
|
9778
10488
|
warn: 1,
|
|
9779
|
-
|
|
9780
|
-
|
|
10489
|
+
success: 2,
|
|
10490
|
+
info: 3,
|
|
9781
10491
|
debug: 4,
|
|
9782
10492
|
},
|
|
9783
10493
|
format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.errors({ stack: true }), winston_1.default.format.printf(({ level, message, timestamp }) => {
|
|
@@ -9789,6 +10499,7 @@ class UnifiedLogger {
|
|
|
9789
10499
|
}),
|
|
9790
10500
|
],
|
|
9791
10501
|
});
|
|
10502
|
+
this.setJsonMode(this.jsonMode);
|
|
9792
10503
|
}
|
|
9793
10504
|
/**
|
|
9794
10505
|
* Toggle JSON output mode. When enabled, every log line is emitted as a
|
|
@@ -9796,6 +10507,36 @@ class UnifiedLogger {
|
|
|
9796
10507
|
*/
|
|
9797
10508
|
setJsonMode(enabled) {
|
|
9798
10509
|
this.jsonMode = enabled;
|
|
10510
|
+
// In machine mode stdout is reserved for command result records. Progress
|
|
10511
|
+
// and diagnostics remain NDJSON, but go to stderr so callers can parse one
|
|
10512
|
+
// stable stdout object without filtering human-oriented logs.
|
|
10513
|
+
for (const transport of this.logger.transports) {
|
|
10514
|
+
if (transport instanceof winston_1.default.transports.Console) {
|
|
10515
|
+
const consoleTransport = transport;
|
|
10516
|
+
consoleTransport.stderrLevels = Object.fromEntries((enabled ? Object.keys(levelColors) : ['error', 'warn']).map((level) => [level, true]));
|
|
10517
|
+
}
|
|
10518
|
+
}
|
|
10519
|
+
}
|
|
10520
|
+
isJsonMode() {
|
|
10521
|
+
return this.jsonMode;
|
|
10522
|
+
}
|
|
10523
|
+
/** Emit one stable, command-level result record for programmatic callers. */
|
|
10524
|
+
result(result) {
|
|
10525
|
+
if (this.jsonMode && !this.resultEmitted) {
|
|
10526
|
+
this.resultEmitted = true;
|
|
10527
|
+
node_process_1.default.stdout.write(`${JSON.stringify(Object.assign({ ok: true }, result))}\n`);
|
|
10528
|
+
}
|
|
10529
|
+
}
|
|
10530
|
+
hasResult() {
|
|
10531
|
+
return this.resultEmitted;
|
|
10532
|
+
}
|
|
10533
|
+
/** Emit a stable error record without exposing internal stack traces. */
|
|
10534
|
+
failure(code, message, details) {
|
|
10535
|
+
if (this.jsonMode) {
|
|
10536
|
+
node_process_1.default.stderr.write(`${JSON.stringify(Object.assign({ ok: false, code, message }, (details == null ? {} : { details })))}\n`);
|
|
10537
|
+
return;
|
|
10538
|
+
}
|
|
10539
|
+
this.error(message);
|
|
9799
10540
|
}
|
|
9800
10541
|
/**
|
|
9801
10542
|
* Format the message with appropriate colors and structure
|
|
@@ -9978,6 +10719,43 @@ const logger = UnifiedLogger.create({ showLevel: false });
|
|
|
9978
10719
|
exports.logger = logger;
|
|
9979
10720
|
|
|
9980
10721
|
|
|
10722
|
+
/***/ }),
|
|
10723
|
+
|
|
10724
|
+
/***/ 26241:
|
|
10725
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
10726
|
+
|
|
10727
|
+
"use strict";
|
|
10728
|
+
|
|
10729
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10730
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10731
|
+
};
|
|
10732
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
10733
|
+
exports.resolvePrivateKey = resolvePrivateKey;
|
|
10734
|
+
const fs_1 = __importDefault(__nccwpck_require__(79896));
|
|
10735
|
+
const path_1 = __importDefault(__nccwpck_require__(16928));
|
|
10736
|
+
function resolvePrivateKey(input, defaultKey) {
|
|
10737
|
+
if (input.privkey && input.privkeyFile) {
|
|
10738
|
+
throw new Error('Use only one of --privkey or --privkey-file.');
|
|
10739
|
+
}
|
|
10740
|
+
if (input.privkey)
|
|
10741
|
+
return input.privkey;
|
|
10742
|
+
if (input.privkeyFile) {
|
|
10743
|
+
const filePath = path_1.default.resolve(input.privkeyFile);
|
|
10744
|
+
try {
|
|
10745
|
+
return fs_1.default.readFileSync(filePath, 'utf8').trim();
|
|
10746
|
+
}
|
|
10747
|
+
catch (error) {
|
|
10748
|
+
throw new Error(`Could not read private key file ${filePath}: ${error.message}`);
|
|
10749
|
+
}
|
|
10750
|
+
}
|
|
10751
|
+
if (process.env.OFFCKB_PRIVATE_KEY)
|
|
10752
|
+
return process.env.OFFCKB_PRIVATE_KEY;
|
|
10753
|
+
if (defaultKey)
|
|
10754
|
+
return defaultKey;
|
|
10755
|
+
throw new Error('--privkey, --privkey-file, or OFFCKB_PRIVATE_KEY is required!');
|
|
10756
|
+
}
|
|
10757
|
+
|
|
10758
|
+
|
|
9981
10759
|
/***/ }),
|
|
9982
10760
|
|
|
9983
10761
|
/***/ 65897:
|
|
@@ -10072,7 +10850,6 @@ exports.validateUdtTypeArgs = validateUdtTypeArgs;
|
|
|
10072
10850
|
const path_1 = __importDefault(__nccwpck_require__(16928));
|
|
10073
10851
|
const fs_1 = __importDefault(__nccwpck_require__(79896));
|
|
10074
10852
|
const base_1 = __nccwpck_require__(69951);
|
|
10075
|
-
const logger_1 = __nccwpck_require__(65370);
|
|
10076
10853
|
function validateTypescriptWorkspace() {
|
|
10077
10854
|
const cwd = process.cwd();
|
|
10078
10855
|
// Check if package.json exists
|
|
@@ -10114,8 +10891,7 @@ function validateNetworkOpt(network) {
|
|
|
10114
10891
|
throw new Error('invalid network option, ' + network);
|
|
10115
10892
|
}
|
|
10116
10893
|
if (network === base_1.Network.mainnet) {
|
|
10117
|
-
|
|
10118
|
-
process.exit(1);
|
|
10894
|
+
throw new Error('Mainnet not support yet. Please use CKB-CLI to operate on mainnet for better security. Check https://github.com/nervosnetwork/ckb-cli');
|
|
10119
10895
|
}
|
|
10120
10896
|
}
|
|
10121
10897
|
function isValidVersion(version) {
|
|
@@ -10171,9 +10947,12 @@ function validateUdtKind(kind) {
|
|
|
10171
10947
|
const U128_MAX = (BigInt(1) << BigInt(128)) - BigInt(1);
|
|
10172
10948
|
function validateUdtAmount(amount) {
|
|
10173
10949
|
if (!/^\d+$/.test(amount)) {
|
|
10174
|
-
throw new Error(`invalid UDT amount "${amount}", must be a
|
|
10950
|
+
throw new Error(`invalid UDT amount "${amount}", must be a positive decimal integer`);
|
|
10175
10951
|
}
|
|
10176
10952
|
const value = BigInt(amount);
|
|
10953
|
+
if (value === BigInt(0)) {
|
|
10954
|
+
throw new Error('invalid UDT amount "0", must be greater than zero');
|
|
10955
|
+
}
|
|
10177
10956
|
if (value > U128_MAX) {
|
|
10178
10957
|
throw new Error(`UDT amount exceeds 128-bit max: ${amount}`);
|
|
10179
10958
|
}
|
|
@@ -10189,8 +10968,8 @@ function validateHexString(value, name) {
|
|
|
10189
10968
|
function validateUdtTypeArgs(kind, typeArgs) {
|
|
10190
10969
|
const hex = validateHexString(typeArgs, 'type args');
|
|
10191
10970
|
const byteLength = (hex.length - 2) / 2;
|
|
10192
|
-
if (kind === 'sudt' && byteLength !==
|
|
10193
|
-
throw new Error(`invalid SUDT type args length: expected
|
|
10971
|
+
if (kind === 'sudt' && byteLength !== 32) {
|
|
10972
|
+
throw new Error(`invalid SUDT type args length: expected 32 bytes, got ${byteLength}`);
|
|
10194
10973
|
}
|
|
10195
10974
|
if (kind === 'xudt' && byteLength !== 32) {
|
|
10196
10975
|
throw new Error(`invalid xUDT type args length: expected 32 bytes, got ${byteLength}`);
|
|
@@ -130322,7 +131101,7 @@ module.exports = {"version":"3.17.0"};
|
|
|
130322
131101
|
/***/ ((module) => {
|
|
130323
131102
|
|
|
130324
131103
|
"use strict";
|
|
130325
|
-
module.exports = /*#__PURE__*/JSON.parse('{"rE":"0.4.9
|
|
131104
|
+
module.exports = /*#__PURE__*/JSON.parse('{"rE":"0.4.9","h_":"ckb development network for your first try"}');
|
|
130326
131105
|
|
|
130327
131106
|
/***/ })
|
|
130328
131107
|
|