beignet 0.21.2 → 0.21.3
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.
|
@@ -199,6 +199,7 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
199
199
|
this.heldInvoiceHashes = new Set();
|
|
200
200
|
this.heldHtlcs = new Map();
|
|
201
201
|
this.resolvingHeldHtlcs = new Set();
|
|
202
|
+
this.heldResolutions = new Map();
|
|
202
203
|
this.holdInvoiceEventQueue = [];
|
|
203
204
|
this.emittingHoldInvoiceEvent = false;
|
|
204
205
|
this.announcedAddresses = [];
|
|
@@ -1172,6 +1173,21 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
1172
1173
|
amountMsat: BigInt(h.amountMsat),
|
|
1173
1174
|
cltvExpiry: h.cltvExpiry
|
|
1174
1175
|
})));
|
|
1176
|
+
const r = entry.resolution;
|
|
1177
|
+
if (!r)
|
|
1178
|
+
continue;
|
|
1179
|
+
const resolved = r.resolved.map((h) => ({
|
|
1180
|
+
amountMsat: BigInt(h.amountMsat),
|
|
1181
|
+
cltvExpiry: h.cltvExpiry
|
|
1182
|
+
}));
|
|
1183
|
+
this.heldResolutions.set(entry.hashHex, r.outcome === 'settle'
|
|
1184
|
+
? { outcome: 'settle', resolved }
|
|
1185
|
+
: {
|
|
1186
|
+
outcome: 'cancel',
|
|
1187
|
+
failureCode: r.failureCode ?? types_3.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS,
|
|
1188
|
+
reason: r.reason ?? 'api',
|
|
1189
|
+
resolved
|
|
1190
|
+
});
|
|
1175
1191
|
}
|
|
1176
1192
|
}
|
|
1177
1193
|
catch {
|
|
@@ -1723,6 +1739,7 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
1723
1739
|
this.drainParkedQuiescentHtlcs(channelId.toString('hex'));
|
|
1724
1740
|
this.asyncPaymentManager.onChannelUsable(channelId.toString('hex'));
|
|
1725
1741
|
this.retryOwedHeldForwardFailures(channelId.toString('hex'));
|
|
1742
|
+
this.retryHeldResolutions(channelId);
|
|
1726
1743
|
const peer = this.channelManager.getPeerForChannel(channelId);
|
|
1727
1744
|
if (peer)
|
|
1728
1745
|
this.asyncPaymentManager.sendNotice(peer);
|
|
@@ -9372,9 +9389,10 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
9372
9389
|
const alreadyParked = parked.some((h) => h.channelId.equals(channelId) && h.htlcId === htlcId);
|
|
9373
9390
|
const parkedMsat = parked.reduce((sum, h) => sum + h.amountMsat, 0n);
|
|
9374
9391
|
if (!alreadyParked &&
|
|
9375
|
-
|
|
9376
|
-
|
|
9377
|
-
|
|
9392
|
+
(this.heldResolutions.has(hashHex) ||
|
|
9393
|
+
(finalInvoice?.amountMsat &&
|
|
9394
|
+
finalInvoice.amountMsat > 0n &&
|
|
9395
|
+
parkedMsat >= finalInvoice.amountMsat))) {
|
|
9378
9396
|
this.emitStructuredLog('htlc', 'held_set_complete', {
|
|
9379
9397
|
paymentHash: hashHex,
|
|
9380
9398
|
parkedMsat: parkedMsat.toString(),
|
|
@@ -9496,6 +9514,8 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
9496
9514
|
return false;
|
|
9497
9515
|
if (this.resolvingHeldHtlcs.has(hashHex))
|
|
9498
9516
|
return false;
|
|
9517
|
+
if (this.heldResolutions.get(hashHex)?.outcome === 'cancel')
|
|
9518
|
+
return false;
|
|
9499
9519
|
const pre = preimage ?? this.preimages.get(hashHex);
|
|
9500
9520
|
if (!pre) {
|
|
9501
9521
|
throw new Error('settleHeldHtlc: no preimage available for hold invoice');
|
|
@@ -9512,30 +9532,42 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
9512
9532
|
this.preimages.set(hashHex, pre);
|
|
9513
9533
|
this.channelManager.recordPreimage(paymentHash, pre);
|
|
9514
9534
|
this.resolvingHeldHtlcs.add(hashHex);
|
|
9515
|
-
|
|
9535
|
+
const prior = this.heldResolutions.get(hashHex);
|
|
9536
|
+
const refused = [];
|
|
9516
9537
|
try {
|
|
9538
|
+
this.heldResolutions.set(hashHex, {
|
|
9539
|
+
outcome: 'settle',
|
|
9540
|
+
resolved: prior?.resolved ?? []
|
|
9541
|
+
});
|
|
9542
|
+
this.persistHeldHtlcs();
|
|
9517
9543
|
for (const h of held) {
|
|
9518
9544
|
const key = `${h.channelId.toString('hex')}:${h.htlcId}`;
|
|
9519
9545
|
if (!this.channelManager.fulfillHtlc(h.channelId, h.htlcId, pre).ok) {
|
|
9546
|
+
refused.push(h);
|
|
9520
9547
|
continue;
|
|
9521
9548
|
}
|
|
9522
|
-
fulfilled++;
|
|
9523
9549
|
this.cleanupHtlcSharedSecret(key);
|
|
9524
9550
|
}
|
|
9525
9551
|
}
|
|
9526
9552
|
finally {
|
|
9527
9553
|
this.resolvingHeldHtlcs.delete(hashHex);
|
|
9528
9554
|
}
|
|
9529
|
-
if (
|
|
9555
|
+
if (refused.length === held.length) {
|
|
9556
|
+
if (!prior) {
|
|
9557
|
+
this.heldResolutions.delete(hashHex);
|
|
9558
|
+
this.persistHeldHtlcs();
|
|
9559
|
+
}
|
|
9530
9560
|
this.emitStructuredLog('htlc', 'held_settle_refused', {
|
|
9531
9561
|
paymentHash: hashHex,
|
|
9532
9562
|
htlcCount: held.length
|
|
9533
9563
|
});
|
|
9534
9564
|
return false;
|
|
9535
9565
|
}
|
|
9536
|
-
this.
|
|
9537
|
-
|
|
9538
|
-
|
|
9566
|
+
const settledSet = this.advanceHeldResolution(hashHex, held, refused, {
|
|
9567
|
+
outcome: 'settle'
|
|
9568
|
+
});
|
|
9569
|
+
if (!settledSet)
|
|
9570
|
+
return false;
|
|
9539
9571
|
const payment = this.payments.get(hashHex);
|
|
9540
9572
|
if (payment) {
|
|
9541
9573
|
payment.status = types_7.PaymentStatus.COMPLETED;
|
|
@@ -9553,13 +9585,59 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
9553
9585
|
const event = {
|
|
9554
9586
|
paymentHash,
|
|
9555
9587
|
state: 'SETTLED',
|
|
9556
|
-
heldAmountMsat:
|
|
9557
|
-
htlcCount:
|
|
9558
|
-
...this.heldSetExpiry(hashHex,
|
|
9588
|
+
heldAmountMsat: settledSet.reduce((sum, h) => sum + h.amountMsat, 0n),
|
|
9589
|
+
htlcCount: settledSet.length,
|
|
9590
|
+
...this.heldSetExpiry(hashHex, settledSet)
|
|
9559
9591
|
};
|
|
9560
9592
|
this.emitHoldInvoiceEvent('hold:settled', event);
|
|
9561
9593
|
return true;
|
|
9562
9594
|
}
|
|
9595
|
+
advanceHeldResolution(hashHex, held, refused, chosen) {
|
|
9596
|
+
const resolved = [
|
|
9597
|
+
...(this.heldResolutions.get(hashHex)?.resolved ?? []),
|
|
9598
|
+
...held
|
|
9599
|
+
.filter((h) => !refused.includes(h))
|
|
9600
|
+
.map((h) => ({ amountMsat: h.amountMsat, cltvExpiry: h.cltvExpiry }))
|
|
9601
|
+
];
|
|
9602
|
+
if (refused.length === 0) {
|
|
9603
|
+
this.heldHtlcs.delete(hashHex);
|
|
9604
|
+
this.heldResolutions.delete(hashHex);
|
|
9605
|
+
this.heldInvoiceHashes.delete(hashHex);
|
|
9606
|
+
this.persistHeldHtlcs();
|
|
9607
|
+
return resolved;
|
|
9608
|
+
}
|
|
9609
|
+
this.heldHtlcs.set(hashHex, refused);
|
|
9610
|
+
this.heldResolutions.set(hashHex, { ...chosen, resolved });
|
|
9611
|
+
this.persistHeldHtlcs();
|
|
9612
|
+
this.emitStructuredLog('htlc', `held_${chosen.outcome}_partial`, {
|
|
9613
|
+
paymentHash: hashHex,
|
|
9614
|
+
resolvedCount: resolved.length,
|
|
9615
|
+
refusedCount: refused.length
|
|
9616
|
+
});
|
|
9617
|
+
return null;
|
|
9618
|
+
}
|
|
9619
|
+
retryHeldResolutions(channelId) {
|
|
9620
|
+
for (const [hashHex, resolution] of [...this.heldResolutions]) {
|
|
9621
|
+
const held = this.heldHtlcs.get(hashHex) ?? [];
|
|
9622
|
+
if (channelId && !held.some((h) => h.channelId.equals(channelId))) {
|
|
9623
|
+
continue;
|
|
9624
|
+
}
|
|
9625
|
+
const paymentHash = Buffer.from(hashHex, 'hex');
|
|
9626
|
+
try {
|
|
9627
|
+
if (resolution.outcome === 'cancel')
|
|
9628
|
+
this.cancelHeldHtlc(paymentHash);
|
|
9629
|
+
else
|
|
9630
|
+
this.settleHeldHtlc(paymentHash);
|
|
9631
|
+
}
|
|
9632
|
+
catch (error) {
|
|
9633
|
+
this.emitStructuredLog('htlc', 'held_resolution_retry_failed', {
|
|
9634
|
+
paymentHash: hashHex,
|
|
9635
|
+
outcome: resolution.outcome,
|
|
9636
|
+
error: error instanceof Error ? error.message : String(error)
|
|
9637
|
+
});
|
|
9638
|
+
}
|
|
9639
|
+
}
|
|
9640
|
+
}
|
|
9563
9641
|
emitInvoiceSettled(paymentHash, payment) {
|
|
9564
9642
|
const invoice = this.invoices.get(paymentHash.toString('hex'));
|
|
9565
9643
|
if (!invoice)
|
|
@@ -9581,9 +9659,23 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
9581
9659
|
return false;
|
|
9582
9660
|
if (this.resolvingHeldHtlcs.has(hashHex))
|
|
9583
9661
|
return false;
|
|
9662
|
+
const chosen = this.heldResolutions.get(hashHex);
|
|
9663
|
+
if (chosen?.outcome === 'settle')
|
|
9664
|
+
return false;
|
|
9665
|
+
if (chosen) {
|
|
9666
|
+
failureCode = chosen.failureCode;
|
|
9667
|
+
cancelReason = chosen.reason;
|
|
9668
|
+
}
|
|
9584
9669
|
this.resolvingHeldHtlcs.add(hashHex);
|
|
9585
|
-
|
|
9670
|
+
const refused = [];
|
|
9586
9671
|
try {
|
|
9672
|
+
this.heldResolutions.set(hashHex, {
|
|
9673
|
+
outcome: 'cancel',
|
|
9674
|
+
failureCode,
|
|
9675
|
+
reason: cancelReason,
|
|
9676
|
+
resolved: chosen?.resolved ?? []
|
|
9677
|
+
});
|
|
9678
|
+
this.persistHeldHtlcs();
|
|
9587
9679
|
for (const h of held) {
|
|
9588
9680
|
const key = `${h.channelId.toString('hex')}:${h.htlcId}`;
|
|
9589
9681
|
const ss = this.receivedHtlcSharedSecrets.get(key);
|
|
@@ -9593,16 +9685,20 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
9593
9685
|
: undefined)
|
|
9594
9686
|
: Buffer.alloc(failures_1.FAILURE_MESSAGE_LENGTH);
|
|
9595
9687
|
if (!this.channelManager.failHtlc(h.channelId, h.htlcId, reason).ok) {
|
|
9688
|
+
refused.push(h);
|
|
9596
9689
|
continue;
|
|
9597
9690
|
}
|
|
9598
|
-
failed++;
|
|
9599
9691
|
this.cleanupHtlcSharedSecret(key);
|
|
9600
9692
|
}
|
|
9601
9693
|
}
|
|
9602
9694
|
finally {
|
|
9603
9695
|
this.resolvingHeldHtlcs.delete(hashHex);
|
|
9604
9696
|
}
|
|
9605
|
-
if (
|
|
9697
|
+
if (refused.length === held.length) {
|
|
9698
|
+
if (!chosen) {
|
|
9699
|
+
this.heldResolutions.delete(hashHex);
|
|
9700
|
+
this.persistHeldHtlcs();
|
|
9701
|
+
}
|
|
9606
9702
|
this.emitStructuredLog('htlc', 'held_cancel_refused', {
|
|
9607
9703
|
paymentHash: hashHex,
|
|
9608
9704
|
reason: cancelReason,
|
|
@@ -9610,10 +9706,14 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
9610
9706
|
});
|
|
9611
9707
|
return false;
|
|
9612
9708
|
}
|
|
9613
|
-
this.
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9709
|
+
const failedSet = this.advanceHeldResolution(hashHex, held, refused, {
|
|
9710
|
+
outcome: 'cancel',
|
|
9711
|
+
failureCode,
|
|
9712
|
+
reason: cancelReason
|
|
9713
|
+
});
|
|
9714
|
+
if (!failedSet)
|
|
9715
|
+
return false;
|
|
9716
|
+
this.markHoldInvoiceCancelled(hashHex, cancelReason, failedSet.length, failedSet.reduce((sum, h) => sum + h.amountMsat, 0n), failedSet);
|
|
9617
9717
|
this.emitStructuredLog('htlc', 'held_cancelled', {
|
|
9618
9718
|
paymentHash: hashHex,
|
|
9619
9719
|
reason: cancelReason
|
|
@@ -9660,7 +9760,7 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
9660
9760
|
const hashHex = paymentHash.toString('hex');
|
|
9661
9761
|
const held = this.heldHtlcs.get(hashHex);
|
|
9662
9762
|
if (held && held.length > 0) {
|
|
9663
|
-
const count = held.length;
|
|
9763
|
+
const count = held.length + (this.heldResolutions.get(hashHex)?.resolved.length ?? 0);
|
|
9664
9764
|
if (!this.cancelHeldHtlc(paymentHash))
|
|
9665
9765
|
return null;
|
|
9666
9766
|
return { htlcsFailed: count };
|
|
@@ -9704,7 +9804,10 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
9704
9804
|
scanExpiringHeldHtlcs(height) {
|
|
9705
9805
|
if (height <= 0)
|
|
9706
9806
|
return;
|
|
9807
|
+
this.retryHeldResolutions();
|
|
9707
9808
|
for (const [hashHex, held] of this.heldHtlcs) {
|
|
9809
|
+
if (this.heldResolutions.has(hashHex))
|
|
9810
|
+
continue;
|
|
9708
9811
|
const soon = held.some((h) => h.cltvExpiry > 0 && h.cltvExpiry - height <= exports.HELD_HTLC_EXPIRY_MARGIN);
|
|
9709
9812
|
if (soon) {
|
|
9710
9813
|
this.cancelHeldHtlc(Buffer.from(hashHex, 'hex'), types_3.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, 'expiry-scan');
|
|
@@ -9716,6 +9819,7 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
9716
9819
|
return;
|
|
9717
9820
|
const serial = [];
|
|
9718
9821
|
for (const [hashHex, held] of this.heldHtlcs) {
|
|
9822
|
+
const r = this.heldResolutions.get(hashHex);
|
|
9719
9823
|
serial.push({
|
|
9720
9824
|
hashHex,
|
|
9721
9825
|
htlcs: held.map((h) => ({
|
|
@@ -9723,7 +9827,16 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
9723
9827
|
htlcId: h.htlcId.toString(),
|
|
9724
9828
|
amountMsat: h.amountMsat.toString(),
|
|
9725
9829
|
cltvExpiry: h.cltvExpiry
|
|
9726
|
-
}))
|
|
9830
|
+
})),
|
|
9831
|
+
...(r && {
|
|
9832
|
+
resolution: {
|
|
9833
|
+
...r,
|
|
9834
|
+
resolved: r.resolved.map((h) => ({
|
|
9835
|
+
amountMsat: h.amountMsat.toString(),
|
|
9836
|
+
cltvExpiry: h.cltvExpiry
|
|
9837
|
+
}))
|
|
9838
|
+
}
|
|
9839
|
+
})
|
|
9727
9840
|
});
|
|
9728
9841
|
}
|
|
9729
9842
|
this.safeStorage(() => this.storage.saveMetadata('held_htlcs', JSON.stringify(serial)), 'persistHeldHtlcs');
|
|
@@ -13226,9 +13339,13 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
13226
13339
|
if (!key.startsWith('received-'))
|
|
13227
13340
|
continue;
|
|
13228
13341
|
const paymentHashHex = htlc.paymentHash?.toString('hex');
|
|
13342
|
+
const resolution = paymentHashHex !== undefined
|
|
13343
|
+
? this.heldResolutions.get(paymentHashHex)
|
|
13344
|
+
: undefined;
|
|
13229
13345
|
const parkedHold = htlc.state !== types_1.HtlcState.FULFILLED &&
|
|
13230
13346
|
paymentHashHex !== undefined &&
|
|
13231
|
-
this.heldInvoiceHashes.has(paymentHashHex)
|
|
13347
|
+
this.heldInvoiceHashes.has(paymentHashHex) &&
|
|
13348
|
+
resolution?.outcome !== 'settle';
|
|
13232
13349
|
const haveClaim = !parkedHold &&
|
|
13233
13350
|
(htlc.state === types_1.HtlcState.FULFILLED ||
|
|
13234
13351
|
(paymentHashHex !== undefined &&
|
|
@@ -13277,6 +13394,12 @@ class LightningNode extends events_1.EventEmitter {
|
|
|
13277
13394
|
this._forceCloseWithReason(channelId, this.getSweepDestinationScript(), this.resolveForceCloseFeeRatePerVbyte(), 'FORWARD_TIMEOUT_FORCE_CLOSE');
|
|
13278
13395
|
break;
|
|
13279
13396
|
}
|
|
13397
|
+
if (resolution?.outcome === 'cancel' &&
|
|
13398
|
+
this.heldHtlcs
|
|
13399
|
+
.get(paymentHashHex)
|
|
13400
|
+
?.some((h) => h.channelId.equals(channelId) && h.htlcId === htlc.id)) {
|
|
13401
|
+
continue;
|
|
13402
|
+
}
|
|
13280
13403
|
const htlcSecretKey = `${channelId.toString('hex')}:${htlc.id}`;
|
|
13281
13404
|
const blindedRole = this.blindedRoleFor(channelId, htlc.id);
|
|
13282
13405
|
if (blindedRole) {
|