beignet 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/cli/beignet-node.js +101 -11
  2. package/dist/cli/beignet-node.js.map +1 -1
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/lightning/gossip/graph-restore.js +162 -0
  5. package/dist/lightning/gossip/graph-restore.js.map +1 -0
  6. package/dist/lightning/gossip/index.js +1 -0
  7. package/dist/lightning/gossip/index.js.map +1 -1
  8. package/dist/lightning/gossip/network-graph.js +49 -20
  9. package/dist/lightning/gossip/network-graph.js.map +1 -1
  10. package/dist/lightning/gossip/rapid-sync.js +261 -86
  11. package/dist/lightning/gossip/rapid-sync.js.map +1 -1
  12. package/dist/lightning/keys/wallet-keys.js +35 -6
  13. package/dist/lightning/keys/wallet-keys.js.map +1 -1
  14. package/dist/lightning/message/codec.js +52 -1
  15. package/dist/lightning/message/codec.js.map +1 -1
  16. package/dist/lightning/node/lightning-node.js +363 -52
  17. package/dist/lightning/node/lightning-node.js.map +1 -1
  18. package/dist/lightning/node/types.js.map +1 -1
  19. package/dist/lightning/storage/encryption.js +5 -5
  20. package/dist/lightning/storage/encryption.js.map +1 -1
  21. package/dist/lightning/storage/sqlite-storage.js +42 -2
  22. package/dist/lightning/storage/sqlite-storage.js.map +1 -1
  23. package/dist/lightning/storage/types.js.map +1 -1
  24. package/dist/types/cli/beignet-node.d.ts +10 -1
  25. package/dist/types/cli/index.d.ts +1 -1
  26. package/dist/types/cli/types.d.ts +11 -0
  27. package/dist/types/lightning/gossip/graph-restore.d.ts +37 -0
  28. package/dist/types/lightning/gossip/index.d.ts +1 -0
  29. package/dist/types/lightning/gossip/network-graph.d.ts +8 -0
  30. package/dist/types/lightning/gossip/rapid-sync.d.ts +10 -0
  31. package/dist/types/lightning/keys/wallet-keys.d.ts +3 -0
  32. package/dist/types/lightning/message/codec.d.ts +4 -0
  33. package/dist/types/lightning/node/lightning-node.d.ts +36 -1
  34. package/dist/types/lightning/node/types.d.ts +22 -0
  35. package/dist/types/lightning/storage/encryption.d.ts +1 -0
  36. package/dist/types/lightning/storage/sqlite-storage.d.ts +3 -1
  37. package/dist/types/lightning/storage/types.d.ts +7 -0
  38. package/dist/types/types/wallet.d.ts +3 -1
  39. package/dist/types/wallet.js.map +1 -1
  40. package/dist/wallet/index.js +4 -2
  41. package/dist/wallet/index.js.map +1 -1
  42. package/package.json +1 -1
@@ -722,6 +722,7 @@ class BeignetNode extends events_1.EventEmitter {
722
722
  this.rapidGossipSync = true;
723
723
  this._initialGossipPrime = null;
724
724
  this._releaseBootRgs = null;
725
+ this._rapidGossipSync = null;
725
726
  this._deferredGossipSyncs = new Set();
726
727
  this.electrumServerCount = 1;
727
728
  this._failoverInProgress = false;
@@ -916,7 +917,7 @@ class BeignetNode extends events_1.EventEmitter {
916
917
  }
917
918
  let encryptionKey;
918
919
  if (opts.storageEncryption ?? true) {
919
- encryptionKey = (0, encryption_1.deriveStorageKey)(bip39.mnemonicToSeedSync(this.mnemonic));
920
+ encryptionKey = (0, encryption_1.deriveStorageKey)(this.walletSeed());
920
921
  }
921
922
  this._storageEncryptionKey = encryptionKey;
922
923
  this.finishStagedCapsuleRestore(dbPath);
@@ -977,6 +978,7 @@ class BeignetNode extends events_1.EventEmitter {
977
978
  };
978
979
  const walletResult = await wallet_1.Wallet.create({
979
980
  mnemonic: this.mnemonic,
981
+ seed: this.walletSeed(),
980
982
  network: beignetNetwork,
981
983
  disableRefreshOnCreate: true,
982
984
  ...(opts.addressType
@@ -1092,8 +1094,10 @@ class BeignetNode extends events_1.EventEmitter {
1092
1094
  });
1093
1095
  }
1094
1096
  this._nodeStorageView = (0, node_storage_view_1.nodeStorageView)(this.storage);
1097
+ const constructStarted = Date.now();
1095
1098
  this.node = lightning_node_1.LightningNode.fromMnemonic(this.mnemonic, {
1096
1099
  coinType,
1100
+ seed: this.walletSeed(),
1097
1101
  network: lnNetwork,
1098
1102
  storage: this._nodeStorageView,
1099
1103
  newChannelsRefused: () => this.newChannelRefusal(),
@@ -1262,6 +1266,7 @@ class BeignetNode extends events_1.EventEmitter {
1262
1266
  : {}
1263
1267
  },
1264
1268
  eagerGossipVerify: opts.eagerGossipVerify ?? false,
1269
+ cooperativeGraphRestore: opts.deferGraphRestore === true,
1265
1270
  localFeatures: lightning_node_1.LightningNode.defaultFeatures(),
1266
1271
  chainHashes: [chainHash],
1267
1272
  alias: opts.alias,
@@ -1306,6 +1311,12 @@ class BeignetNode extends events_1.EventEmitter {
1306
1311
  watchtowers: opts.watchtowers,
1307
1312
  recovery: this.recoveryNodeConfig
1308
1313
  });
1314
+ this._nodeConstructMs = Date.now() - constructStarted;
1315
+ this.node.on('graph:restored', () => {
1316
+ const stats = this.getGraphRestoreStats();
1317
+ if (stats)
1318
+ this.emit('graph:restored', stats);
1319
+ });
1309
1320
  this.fforReceiveService = new ffor_receive_1.FforReceiveService(this, opts.fforSettle, opts.fforReceiveFunding, opts.fforSettleConcurrent !== false);
1310
1321
  const receiveKey = 'automatic_receive_jobs_v1';
1311
1322
  const receiveJobs = this.storage.loadWalletData(receiveKey);
@@ -1783,6 +1794,10 @@ class BeignetNode extends events_1.EventEmitter {
1783
1794
  }
1784
1795
  if (this.rapidGossipSync && this.networkName === 'mainnet') {
1785
1796
  const rgs = this.syncRapidGossip().catch((err) => {
1797
+ if (err instanceof rapid_sync_1.RapidGossipCancelledError && this.destroyed) {
1798
+ this.log('debug', 'Rapid gossip sync cancelled by shutdown', {});
1799
+ return;
1800
+ }
1786
1801
  this.log('warn', 'Rapid gossip sync failed', {
1787
1802
  error: err instanceof Error ? err.message : String(err)
1788
1803
  });
@@ -3092,10 +3107,19 @@ class BeignetNode extends events_1.EventEmitter {
3092
3107
  }
3093
3108
  nodeSecret() {
3094
3109
  if (!this._nodeSecret) {
3095
- this._nodeSecret = (0, wallet_keys_1.deriveLightningKeysFromMnemonic)(this.mnemonic, undefined, this.toCoinType(this.networkName)).nodePrivateKey;
3110
+ if (!bip39.validateMnemonic(this.mnemonic)) {
3111
+ throw new Error('Invalid BIP39 mnemonic');
3112
+ }
3113
+ this._nodeSecret = (0, wallet_keys_1.deriveLightningKeysFromSeed)(this.walletSeed(), this.toCoinType(this.networkName)).nodePrivateKey;
3096
3114
  }
3097
3115
  return this._nodeSecret;
3098
3116
  }
3117
+ walletSeed() {
3118
+ if (!this._walletSeed) {
3119
+ this._walletSeed = bip39.mnemonicToSeedSync(this.mnemonic);
3120
+ }
3121
+ return Buffer.from(this._walletSeed);
3122
+ }
3099
3123
  async restoreFromGuardians() {
3100
3124
  if (this._restoreInFlight) {
3101
3125
  throw new errors_1.BeignetError('RESTORE_IN_PROGRESS', 'A guardian restore is already running; watch ' +
@@ -4192,24 +4216,62 @@ class BeignetNode extends events_1.EventEmitter {
4192
4216
  this._lastGraphSyncAt = Date.now();
4193
4217
  return synced;
4194
4218
  }
4195
- async syncRapidGossip() {
4219
+ syncRapidGossip() {
4220
+ if (this._rapidGossipSync)
4221
+ return this._rapidGossipSync;
4222
+ const run = this.runRapidGossipSync();
4223
+ this._rapidGossipSync = run;
4224
+ const clear = () => {
4225
+ if (this._rapidGossipSync === run)
4226
+ this._rapidGossipSync = null;
4227
+ };
4228
+ void run.then(clear, clear);
4229
+ return run;
4230
+ }
4231
+ async runRapidGossipSync() {
4196
4232
  if (this.networkName !== 'mainnet') {
4197
4233
  this.log('warn', 'Rapid gossip sync is only available on mainnet', {});
4198
4234
  return null;
4199
4235
  }
4200
4236
  const url = this.rapidGossipSyncUrl ?? rapid_sync_1.DEFAULT_RGS_URL;
4201
4237
  this.log('info', 'Rapid gossip sync: downloading snapshot', { url });
4238
+ const downloadStart = Date.now();
4202
4239
  const data = await (0, rapid_sync_1.fetchRapidGossipSnapshot)(url);
4203
- const result = this.node.loadRapidGossipSnapshot(data);
4240
+ const downloadMs = Date.now() - downloadStart;
4241
+ if (this.destroyed)
4242
+ throw new rapid_sync_1.RapidGossipCancelledError();
4243
+ if (this.isGraphRestoring())
4244
+ await this.whenGraphRestored();
4245
+ if (this.destroyed)
4246
+ throw new rapid_sync_1.RapidGossipCancelledError();
4247
+ let slices = 0;
4248
+ let busyMs = 0;
4249
+ const applyStart = Date.now();
4250
+ const result = await this.node.loadRapidGossipSnapshotAsync(data, {
4251
+ cancelled: () => this.destroyed,
4252
+ onSlice: (ms) => {
4253
+ slices++;
4254
+ busyMs += ms;
4255
+ }
4256
+ });
4257
+ const applyMs = Date.now() - applyStart;
4204
4258
  this._lastGraphSyncAt = Date.now();
4205
4259
  this.log('info', 'Rapid gossip sync complete', {
4206
4260
  channelsAdded: result.channelsAdded,
4207
4261
  updatesApplied: result.updatesApplied,
4208
- nodes: result.nodeCount
4262
+ nodes: result.nodeCount,
4263
+ downloadMs,
4264
+ applyMs,
4265
+ busyMs,
4266
+ slices
4209
4267
  });
4210
4268
  this.emit('gossip:synced', {
4211
4269
  channelsAdded: result.channelsAdded,
4212
- updatesApplied: result.updatesApplied
4270
+ updatesApplied: result.updatesApplied,
4271
+ downloadMs,
4272
+ applyMs,
4273
+ busyMs,
4274
+ slices
4213
4275
  });
4214
4276
  return {
4215
4277
  channelsAdded: result.channelsAdded,
@@ -4249,6 +4311,8 @@ class BeignetNode extends events_1.EventEmitter {
4249
4311
  return this.openChannel(pubkey, amountSats, opts?.pushSats, opts?.satsPerVbyte, opts?.max, opts?.trusted ?? false);
4250
4312
  }
4251
4313
  async ensureMinimumChannels(count, satsPerChannel, _opts) {
4314
+ if (this.isGraphRestoring())
4315
+ await this.whenGraphRestored();
4252
4316
  const existing = this.getReadyChannels();
4253
4317
  if (existing.length >= count)
4254
4318
  return existing;
@@ -6327,6 +6391,8 @@ class BeignetNode extends events_1.EventEmitter {
6327
6391
  }
6328
6392
  async payInvoiceAll(bolt11, debitMsat, maxFeeMsat, timeoutMs = 60000) {
6329
6393
  this._checkDraining();
6394
+ if (this.isGraphRestoring())
6395
+ await this.whenGraphRestored();
6330
6396
  const decoded = decodeInvoiceInput(bolt11);
6331
6397
  const debit = requireMsatValue(debitMsat, 'debitMsat');
6332
6398
  const feeCap = requireMsatValue(maxFeeMsat, 'maxFeeMsat');
@@ -6345,6 +6411,8 @@ class BeignetNode extends events_1.EventEmitter {
6345
6411
  }
6346
6412
  async payInvoice(bolt11, timeoutMs = 60000, maxFeeSats, amountSats, metadata, cltvLimit, maxFeeMsatCap) {
6347
6413
  this._checkDraining();
6414
+ if (this.isGraphRestoring())
6415
+ await this.whenGraphRestored();
6348
6416
  const decoded = decodeInvoiceInput(bolt11);
6349
6417
  const paymentHashHex = decoded.paymentHash.toString('hex');
6350
6418
  const amountMsat = amountSats !== undefined
@@ -6569,6 +6637,8 @@ class BeignetNode extends events_1.EventEmitter {
6569
6637
  }
6570
6638
  async sendKeysend(pubkey, amountSats, timeoutMs = 60000, maxFeeSats, metadata, onPaymentHash) {
6571
6639
  this._checkDraining();
6640
+ if (this.isGraphRestoring())
6641
+ await this.whenGraphRestored();
6572
6642
  const amountMsat = BigInt(requireNonNegativeSafeInteger(amountSats, 'amountSats')) * 1000n;
6573
6643
  const maxFeeMsat = resolveMaxFeeMsat(maxFeeSats, undefined, amountMsat);
6574
6644
  const preimage = crypto.randomBytes(32);
@@ -7156,6 +7226,8 @@ class BeignetNode extends events_1.EventEmitter {
7156
7226
  .map(({ offer, encoded }) => this.toOfferInfo(offer, encoded));
7157
7227
  }
7158
7228
  async payOffer(offerStr, amountSats, timeoutMs = 60000, maxFeeSats, maxFeeMsatCap, onPaymentHash) {
7229
+ if (this.isGraphRestoring())
7230
+ await this.whenGraphRestored();
7159
7231
  this._checkDraining();
7160
7232
  const offer = decodeOfferInput(offerStr);
7161
7233
  const callerMaxFeeMsat = explicitMaxFeeMsat(maxFeeSats, maxFeeMsatCap);
@@ -7579,6 +7651,8 @@ class BeignetNode extends events_1.EventEmitter {
7579
7651
  return { ...this.getLiquiditySnapshot(), rebalancePlan: plan };
7580
7652
  }
7581
7653
  async rebalanceChannel(fromChannelId, toChannelId, amountSats, maxFeeSats) {
7654
+ if (this.isGraphRestoring())
7655
+ await this.whenGraphRestored();
7582
7656
  if (!/^[0-9a-fA-F]{64}$/.test(fromChannelId))
7583
7657
  throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'fromChannelId must be 64 hex chars');
7584
7658
  if (!/^[0-9a-fA-F]{64}$/.test(toChannelId))
@@ -7715,6 +7789,20 @@ class BeignetNode extends events_1.EventEmitter {
7715
7789
  }))
7716
7790
  };
7717
7791
  }
7792
+ isGraphRestoring() {
7793
+ return this.node?.isGraphRestoring?.() ?? false;
7794
+ }
7795
+ whenGraphRestored() {
7796
+ return this.node?.whenGraphRestored?.() ?? Promise.resolve(true);
7797
+ }
7798
+ getGraphRestoreStats() {
7799
+ const stats = this.node?.getGraphRestoreStats() ?? null;
7800
+ if (!stats)
7801
+ return null;
7802
+ return this._nodeConstructMs === undefined
7803
+ ? stats
7804
+ : { ...stats, constructMs: this._nodeConstructMs };
7805
+ }
7718
7806
  getGraphInfo() {
7719
7807
  const graph = this.node.getGraph();
7720
7808
  const info = {
@@ -7724,6 +7812,8 @@ class BeignetNode extends events_1.EventEmitter {
7724
7812
  if (this._lastGraphSyncAt !== undefined) {
7725
7813
  info.lastSyncAt = this._lastGraphSyncAt;
7726
7814
  }
7815
+ if (this.isGraphRestoring())
7816
+ info.restoring = true;
7727
7817
  return info;
7728
7818
  }
7729
7819
  getGraphNode(pubkey) {
@@ -8199,7 +8289,7 @@ class BeignetNode extends events_1.EventEmitter {
8199
8289
  }
8200
8290
  async backup(destPath) {
8201
8291
  await this.storage.backup(destPath);
8202
- await (0, backup_mac_1.writeBackupMac)((0, backup_mac_1.deriveBackupMacKey)(bip39.mnemonicToSeedSync(this.mnemonic)), destPath);
8292
+ await (0, backup_mac_1.writeBackupMac)((0, backup_mac_1.deriveBackupMacKey)(this.walletSeed()), destPath);
8203
8293
  }
8204
8294
  storageFiles() {
8205
8295
  const dbPath = fs.realpathSync.native(path.join(this.dataDir, `${this.networkName}.db`));
@@ -8250,14 +8340,14 @@ class BeignetNode extends events_1.EventEmitter {
8250
8340
  createdAt: Date.now(),
8251
8341
  channels: data.channels
8252
8342
  };
8253
- const seed = bip39.mnemonicToSeedSync(this.mnemonic);
8343
+ const seed = this.walletSeed();
8254
8344
  const encoded = (0, scb_1.encodeScb)(backup, seed);
8255
8345
  const scbPath = path.join(this.dataDir, 'channels.scb');
8256
8346
  (0, fs_utils_1.writeFileAtomic)(scbPath, encoded);
8257
8347
  return { encoded, channelCount: data.channels.length, path: scbPath };
8258
8348
  }
8259
8349
  async restoreFromScb(encoded) {
8260
- const seed = bip39.mnemonicToSeedSync(this.mnemonic);
8350
+ const seed = this.walletSeed();
8261
8351
  const backup = (0, scb_1.decodeScb)(encoded.trim(), seed);
8262
8352
  const expectedNetwork = this.toLnNetwork(this.networkName);
8263
8353
  if (backup.network !== expectedNetwork) {
@@ -8378,14 +8468,14 @@ class BeignetNode extends events_1.EventEmitter {
8378
8468
  });
8379
8469
  }
8380
8470
  }
8381
- const seed = bip39.mnemonicToSeedSync(this.mnemonic);
8471
+ const seed = this.walletSeed();
8382
8472
  this.offerRetrievedScb((0, scb_1.encodeScb)(embedded, seed), embedded, peerPubkey, 'capsule');
8383
8473
  return;
8384
8474
  }
8385
8475
  let backup;
8386
8476
  const encoded = blob.toString('utf8');
8387
8477
  try {
8388
- const seed = bip39.mnemonicToSeedSync(this.mnemonic);
8478
+ const seed = this.walletSeed();
8389
8479
  backup = (0, scb_1.decodeScb)(encoded, seed);
8390
8480
  }
8391
8481
  catch {