Skip to content

Commit aa3027a

Browse files
authored
fix(connext): prevent duplicate htlcAccepted evts (#1854)
* fix(connext): prevent duplicate htlcAccepted evts This prevents trying to settle an invoice more than once in case Connext sends us duplicate transfer received events. Previously, ConnextClient could emit the `htlcAccepted` event multiple times, resulting in one successful settleInvoice call and at least one failed call, which would then attempt to fail a completed deal and crash xud. Closes #1851. * refactor(swaps): relax faildeal complete check This relaxes the assertion in `failDeal` to ensure we don't "fail" a deal that has already been completed. Previously such a case would crash xud, now it only prints an error message and does nothing. Although this shouldn't happen and indicates a flaw with xud code should it arise, it is not a critical error and does not risk funds or indicate that xud is inoperable.
1 parent 6f97783 commit aa3027a

File tree

3 files changed

+31
-20
lines changed

3 files changed

+31
-20
lines changed

Diff for: lib/connextclient/ConnextClient.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ class ConnextClient extends SwapClient {
167167
}
168168
const expectedIncomingTransfer = this.expectedIncomingTransfers.get(rHash);
169169
if (!expectedIncomingTransfer) {
170-
this.logger.warn(`received unexpected incoming transfer created event with rHash ${rHash}`);
170+
this.logger.warn(`received unexpected incoming transfer created event with rHash ${rHash}, units: ${units}, timelock ${timelock}, token address ${tokenAddress}, and paymentId ${paymentId}`);
171171
return;
172172
}
173173

@@ -184,7 +184,18 @@ class ConnextClient extends SwapClient {
184184
) {
185185
expectedIncomingTransfer.paymentId = paymentId;
186186
this.logger.debug(`accepting incoming transfer with rHash: ${rHash}, units: ${units}, timelock ${timelock}, currency ${currency}, and paymentId ${paymentId}`);
187+
this.expectedIncomingTransfers.delete(rHash);
187188
this.emit('htlcAccepted', rHash, units, currency);
189+
} else {
190+
if (tokenAddress !== expectedTokenAddress) {
191+
this.logger.warn(`incoming transfer for rHash ${rHash} with token address ${tokenAddress} does not match expected ${expectedTokenAddress}`);
192+
}
193+
if (units !== expectedUnits) {
194+
this.logger.warn(`incoming transfer for rHash ${rHash} with value ${units} does not match expected ${expectedUnits}`);
195+
}
196+
if (timelock !== expectedTimelock) {
197+
this.logger.warn(`incoming transfer for rHash ${rHash} with time lock ${timelock} does not match expected ${expectedTimelock}`);
198+
}
188199
}
189200
}
190201

@@ -413,7 +424,6 @@ class ConnextClient extends SwapClient {
413424
assetId,
414425
preImage: `0x${rPreimage}`,
415426
});
416-
this.expectedIncomingTransfers.delete(rHash);
417427
}
418428

419429
public removeInvoice = async (rHash: string) => {

Diff for: lib/swaps/Swaps.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -726,8 +726,6 @@ class Swaps extends EventEmitter {
726726
await swapClient.settleInvoice(rHash, rPreimage, currency).catch(this.logger.error);
727727
} else if (deal.state === SwapState.Active) {
728728
// we check that the deal is still active before we try to settle the invoice
729-
// if the swap has already been failed, then we leave the swap recovery module
730-
// to attempt to settle the invoice and claim funds rather than do it here
731729
try {
732730
await swapClient.settleInvoice(rHash, rPreimage, currency);
733731
} catch (err) {
@@ -1226,7 +1224,10 @@ class Swaps extends EventEmitter {
12261224
/** An optional reqId in case the SwapFailedPacket is in response to a swap request. */
12271225
reqId?: string,
12281226
}) => {
1229-
assert(deal.state !== SwapState.Completed, 'Can not fail a completed deal.');
1227+
if (deal.state === SwapState.Completed) {
1228+
this.logger.error(`Can not fail completed deal ${deal.rHash}`);
1229+
return;
1230+
}
12301231

12311232
// If we are already in error state and got another error report we
12321233
// aggregate all error reasons by concatenation

Diff for: test/simulation/custom-xud.patch

+15-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
diff --git a/lib/Xud.ts b/lib/Xud.ts
2-
index 52ed8b5b4..93c5029b3 100644
2+
index 52ed8b5b..93c5029b 100644
33
--- a/lib/Xud.ts
44
+++ b/lib/Xud.ts
55
@@ -87,6 +87,11 @@ class Xud extends EventEmitter {
@@ -15,7 +15,7 @@ index 52ed8b5b4..93c5029b3 100644
1515
if (!this.config.rpc.disable) {
1616
// start rpc server first, it will respond with UNAVAILABLE error
1717
diff --git a/lib/swaps/SwapRecovery.ts b/lib/swaps/SwapRecovery.ts
18-
index 3759f6a35..4089dc944 100644
18+
index 3759f6a3..4089dc94 100644
1919
--- a/lib/swaps/SwapRecovery.ts
2020
+++ b/lib/swaps/SwapRecovery.ts
2121
@@ -29,7 +29,18 @@ class SwapRecovery extends EventEmitter {
@@ -39,12 +39,12 @@ index 3759f6a35..4089dc944 100644
3939
}
4040

4141
diff --git a/lib/swaps/Swaps.ts b/lib/swaps/Swaps.ts
42-
index 3a2367f52..ec1712119 100644
42+
index 0018b386..388e1638 100644
4343
--- a/lib/swaps/Swaps.ts
4444
+++ b/lib/swaps/Swaps.ts
45-
@@ -729,6 +729,24 @@ class Swaps extends EventEmitter {
46-
// if the swap has already been failed, then we leave the swap recovery module
47-
// to attempt to settle the invoice and claim funds rather than do it here
45+
@@ -727,6 +727,24 @@ class Swaps extends EventEmitter {
46+
} else if (deal.state === SwapState.Active) {
47+
// we check that the deal is still active before we try to settle the invoice
4848
try {
4949
+ if (rPreimage === '') {
5050
+ this.logger.info('NOT SETTLING INVOICE');
@@ -67,7 +67,7 @@ index 3a2367f52..ec1712119 100644
6767
await swapClient.settleInvoice(rHash, rPreimage, currency);
6868
} catch (err) {
6969
this.logger.error(`could not settle invoice for deal ${rHash}`, err);
70-
@@ -749,7 +767,9 @@ class Swaps extends EventEmitter {
70+
@@ -747,7 +765,9 @@ class Swaps extends EventEmitter {
7171
} catch (err) {
7272
this.logger.error(`could not settle invoice for deal ${rHash}`, err);
7373
}
@@ -78,7 +78,7 @@ index 3a2367f52..ec1712119 100644
7878
});
7979
await settleRetryPromise;
8080
} else {
81-
@@ -773,6 +793,16 @@ class Swaps extends EventEmitter {
81+
@@ -771,6 +791,16 @@ class Swaps extends EventEmitter {
8282
* accepted, initiates the swap.
8383
*/
8484
private handleSwapAccepted = async (responsePacket: packets.SwapAcceptedPacket, peer: Peer) => {
@@ -95,7 +95,7 @@ index 3a2367f52..ec1712119 100644
9595
assert(responsePacket.body, 'SwapAcceptedPacket does not contain a body');
9696
const { quantity, rHash, makerCltvDelta } = responsePacket.body;
9797
const deal = this.getDeal(rHash);
98-
@@ -860,6 +890,11 @@ class Swaps extends EventEmitter {
98+
@@ -858,6 +888,11 @@ class Swaps extends EventEmitter {
9999

100100
try {
101101
await makerSwapClient.sendPayment(deal);
@@ -107,7 +107,7 @@ index 3a2367f52..ec1712119 100644
107107
} catch (err) {
108108
// first we must handle the edge case where the maker has paid us but failed to claim our payment
109109
// in this case, we've already marked the swap as having been paid and completed
110-
@@ -1041,6 +1076,18 @@ class Swaps extends EventEmitter {
110+
@@ -1039,6 +1074,18 @@ class Swaps extends EventEmitter {
111111

112112
this.logger.debug('Executing maker code to resolve hash');
113113

@@ -126,7 +126,7 @@ index 3a2367f52..ec1712119 100644
126126
const swapClient = this.swapClientManager.get(deal.takerCurrency)!;
127127

128128
// we update the phase persist the deal to the database before we attempt to send payment
129-
@@ -1051,6 +1098,13 @@ class Swaps extends EventEmitter {
129+
@@ -1049,6 +1096,13 @@ class Swaps extends EventEmitter {
130130
assert(deal.state !== SwapState.Error, `cannot send payment for failed swap ${deal.rHash}`);
131131

132132
try {
@@ -140,7 +140,7 @@ index 3a2367f52..ec1712119 100644
140140
deal.rPreimage = await swapClient.sendPayment(deal);
141141
} catch (err) {
142142
this.logger.debug(`sendPayment in resolveHash for swap ${deal.rHash} failed due to ${err.message}`);
143-
@@ -1128,10 +1182,21 @@ class Swaps extends EventEmitter {
143+
@@ -1126,10 +1180,21 @@ class Swaps extends EventEmitter {
144144
}
145145
}
146146

@@ -163,7 +163,7 @@ index 3a2367f52..ec1712119 100644
163163
return deal.rPreimage;
164164
} else {
165165
// If we are here we are the taker
166-
@@ -1139,6 +1204,16 @@ class Swaps extends EventEmitter {
166+
@@ -1137,6 +1202,16 @@ class Swaps extends EventEmitter {
167167
assert(htlcCurrency === undefined || htlcCurrency === deal.takerCurrency, 'incoming htlc does not match expected deal currency');
168168
this.logger.debug('Executing taker code to resolve hash');
169169

@@ -180,7 +180,7 @@ index 3a2367f52..ec1712119 100644
180180
return deal.rPreimage;
181181
}
182182
}
183-
@@ -1302,8 +1377,11 @@ class Swaps extends EventEmitter {
183+
@@ -1305,8 +1380,11 @@ class Swaps extends EventEmitter {
184184
swapClient.removeInvoice(deal.rHash).catch(this.logger.error); // we don't need to await the remove invoice call
185185
}
186186
} else if (deal.phase === SwapPhase.SendingPayment) {
@@ -194,7 +194,7 @@ index 3a2367f52..ec1712119 100644
194194
}
195195

196196
this.logger.trace(`emitting swap.failed event for ${deal.rHash}`);
197-
@@ -1367,9 +1445,14 @@ class Swaps extends EventEmitter {
197+
@@ -1370,9 +1448,14 @@ class Swaps extends EventEmitter {
198198

199199
if (deal.role === SwapRole.Maker) {
200200
// the maker begins execution of the swap upon accepting the deal

0 commit comments

Comments
 (0)