Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(sim): maker connext-client crash before taker order settlement #1758

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 37 additions & 14 deletions test/simulation/custom-xud.patch
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
diff --git a/lib/Xud.ts b/lib/Xud.ts
index 52ed8b5b..93c5029b 100644
index 489a50a4..f9391527 100644
--- a/lib/Xud.ts
+++ b/lib/Xud.ts
@@ -87,6 +87,11 @@ class Xud extends EventEmitter {
Expand All @@ -15,10 +15,10 @@ index 52ed8b5b..93c5029b 100644
if (!this.config.rpc.disable) {
// start rpc server first, it will respond with UNAVAILABLE error
diff --git a/lib/swaps/SwapRecovery.ts b/lib/swaps/SwapRecovery.ts
index 3759f6a3..4089dc94 100644
index 3759f6a3..fecb63b0 100644
--- a/lib/swaps/SwapRecovery.ts
+++ b/lib/swaps/SwapRecovery.ts
@@ -29,7 +29,18 @@ class SwapRecovery extends EventEmitter {
@@ -29,7 +29,21 @@ class SwapRecovery extends EventEmitter {

public beginTimer = () => {
if (!this.pendingSwapsTimer) {
Expand All @@ -27,6 +27,9 @@ index 3759f6a3..4089dc94 100644
+ if (process.env.CUSTOM_SCENARIO === 'INSTABILITY::MAKER_CRASH_WHILE_SENDING') {
+ interval = 2000;
+ }
+ if (process.env.CUSTOM_SCENARIO === 'INSTABILITY::MAKER_CLIENT_CRASH_WHILE_SENDING') {
+ interval = 2000;
+ }
+ if (process.env.CUSTOM_SCENARIO === 'INSTABILITY::MAKER_CRASH_AFTER_SEND_BEFORE_PREIMAGE_RESOLVED') {
+ interval = 2000;
+ }
Expand All @@ -39,10 +42,10 @@ index 3759f6a3..4089dc94 100644
}

diff --git a/lib/swaps/Swaps.ts b/lib/swaps/Swaps.ts
index 0018b386..388e1638 100644
index 9648e02b..6017aaaa 100644
--- a/lib/swaps/Swaps.ts
+++ b/lib/swaps/Swaps.ts
@@ -727,6 +727,24 @@ class Swaps extends EventEmitter {
@@ -730,6 +730,24 @@ class Swaps extends EventEmitter {
} else if (deal.state === SwapState.Active) {
// we check that the deal is still active before we try to settle the invoice
try {
Expand All @@ -67,7 +70,7 @@ index 0018b386..388e1638 100644
await swapClient.settleInvoice(rHash, rPreimage, currency);
} catch (err) {
this.logger.error(`could not settle invoice for deal ${rHash}`, err);
@@ -747,7 +765,9 @@ class Swaps extends EventEmitter {
@@ -750,7 +768,9 @@ class Swaps extends EventEmitter {
} catch (err) {
this.logger.error(`could not settle invoice for deal ${rHash}`, err);
}
Expand All @@ -78,7 +81,7 @@ index 0018b386..388e1638 100644
});
await settleRetryPromise;
} else {
@@ -771,6 +791,16 @@ class Swaps extends EventEmitter {
@@ -774,6 +794,16 @@ class Swaps extends EventEmitter {
* accepted, initiates the swap.
*/
private handleSwapAccepted = async (responsePacket: packets.SwapAcceptedPacket, peer: Peer) => {
Expand All @@ -95,7 +98,7 @@ index 0018b386..388e1638 100644
assert(responsePacket.body, 'SwapAcceptedPacket does not contain a body');
const { quantity, rHash, makerCltvDelta } = responsePacket.body;
const deal = this.getDeal(rHash);
@@ -858,6 +888,11 @@ class Swaps extends EventEmitter {
@@ -861,6 +891,11 @@ class Swaps extends EventEmitter {

try {
await makerSwapClient.sendPayment(deal);
Expand All @@ -107,7 +110,7 @@ index 0018b386..388e1638 100644
} catch (err) {
// first we must handle the edge case where the maker has paid us but failed to claim our payment
// in this case, we've already marked the swap as having been paid and completed
@@ -1039,6 +1074,18 @@ class Swaps extends EventEmitter {
@@ -1042,6 +1077,18 @@ class Swaps extends EventEmitter {

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

Expand All @@ -126,7 +129,7 @@ index 0018b386..388e1638 100644
const swapClient = this.swapClientManager.get(deal.takerCurrency)!;

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

try {
Expand All @@ -136,11 +139,18 @@ index 0018b386..388e1638 100644
+ process.exit();
+ }, 2000);
+ }
+ if (process.env.CUSTOM_SCENARIO === 'INSTABILITY::MAKER_CLIENT_CRASH_WHILE_SENDING') {
+ setTimeout(() => {
+ this.logger.info(`CUSTOM_SCENARIO: ${process.env.CUSTOM_SCENARIO}`);
+ this.logger.info(`CLIENT_TYPE ${process.env.CLIENT_TYPE}, CLIENT_PID: ${process.env.CLIENT_PID}`);
+ process.kill(parseInt(process.env.CLIENT_PID!, 10));
+ }, 2000);
+ }
+
deal.rPreimage = await swapClient.sendPayment(deal);
} catch (err) {
this.logger.debug(`sendPayment in resolveHash for swap ${deal.rHash} failed due to ${err.message}`);
@@ -1126,10 +1180,21 @@ class Swaps extends EventEmitter {
@@ -1129,10 +1190,21 @@ class Swaps extends EventEmitter {
}
}

Expand All @@ -163,7 +173,7 @@ index 0018b386..388e1638 100644
return deal.rPreimage;
} else {
// If we are here we are the taker
@@ -1137,6 +1202,16 @@ class Swaps extends EventEmitter {
@@ -1140,6 +1212,16 @@ class Swaps extends EventEmitter {
assert(htlcCurrency === undefined || htlcCurrency === deal.takerCurrency, 'incoming htlc does not match expected deal currency');
this.logger.debug('Executing taker code to resolve hash');

Expand All @@ -180,7 +190,7 @@ index 0018b386..388e1638 100644
return deal.rPreimage;
}
}
@@ -1305,8 +1380,11 @@ class Swaps extends EventEmitter {
@@ -1308,8 +1390,11 @@ class Swaps extends EventEmitter {
swapClient.removeInvoice(deal.rHash).catch(this.logger.error); // we don't need to await the remove invoice call
}
} else if (deal.phase === SwapPhase.SendingPayment) {
Expand All @@ -194,7 +204,7 @@ index 0018b386..388e1638 100644
}

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

if (deal.role === SwapRole.Maker) {
// the maker begins execution of the swap upon accepting the deal
Expand All @@ -210,3 +220,16 @@ index 0018b386..388e1638 100644
rHash,
SwapFailureReason.SwapTimedOut,
));
diff --git a/test/simulation/go.mod b/test/simulation/go.mod
index 77cfa21e..f6891710 100644
--- a/test/simulation/go.mod
+++ b/test/simulation/go.mod
@@ -12,6 +12,8 @@ require (
github.com/ltcsuite/ltcd v0.0.0-20191214120725-004941532b74
github.com/ltcsuite/ltcutil v0.0.0-20190507133322-23cdfa9fcc3d
github.com/miguelmota/go-ethereum-hdwallet v0.0.0-20200123000308-a60dcd172b4c
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
github.com/roasbeef/btcd v0.0.0-20180418012700-a03db407e40d
github.com/roasbeef/btcutil v0.0.0-20180406014609-dfb640c57141
111 changes: 104 additions & 7 deletions test/simulation/tests-instability.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ var instabilityTestCases = []*testCase{
test: testMakerCrashedAfterSendAfterPreimageResolvedConnextIn,
},
{
name: "maker lnd crashed before order settlement", // replacing Alice
test: testMakerLndCrashedBeforeSettlement,
name: "maker lnd crashed before maker order settlement", // replacing Alice
test: testMakerLndCrashedBeforeMakerSettlement,
},
{
name: "maker connext client crashed before order settlement", // replacing Alice
test: testMakerConnextClientCrashedBeforeSettlement,
name: "maker connext client crashed before maker order settlement", // replacing Alice
test: testMakerConnextClientCrashedBeforeMakerSettlement,
},
{
name: "maker crashed after send payment with delayed settlement; incoming: lnd, outgoing: lnd", // replacing Alice + Bob
Expand All @@ -54,6 +54,10 @@ var instabilityTestCases = []*testCase{
name: "maker crashed after send payment with delayed settlement; incoming: lnd, outgoing: connext", // replacing Alice + Bob
test: testMakerCrashedAfterSendDelayedSettlementConnextOut,
},
{
name: "maker connext client crashed before taker order settlement", // replacing Alice + Bob
test: testMakerConnextClientCrashedBeforeTakerSettlement,
},
}

func testMakerCrashedAfterSendBeforePreimageResolved(net *xudtest.NetworkHarness, ht *harnessTest) {
Expand Down Expand Up @@ -185,7 +189,7 @@ func testMakerCrashedDuringSwapConnextIn(net *xudtest.NetworkHarness, ht *harnes
ht.assert.Equal(alicePrevEthBalance.ChannelBalance+diff, aliceEthBalance.ChannelBalance, "alice did not recover ETH funds")
}

func testMakerLndCrashedBeforeSettlement(net *xudtest.NetworkHarness, ht *harnessTest) {
func testMakerLndCrashedBeforeMakerSettlement(net *xudtest.NetworkHarness, ht *harnessTest) {
var err error
net.Alice, err = net.SetCustomXud(ht.ctx, ht, net.Alice, []string{
"CUSTOM_SCENARIO=INSTABILITY::MAKER_CLIENT_CRASHED_BEFORE_SETTLE",
Expand Down Expand Up @@ -246,12 +250,105 @@ func testMakerLndCrashedBeforeSettlement(net *xudtest.NetworkHarness, ht *harnes
ht.assert.Equal(alicePrevLtcBalance+ltcQuantity, aliceLtcBalance, "alice did not recover LTC funds")
}

func testMakerConnextClientCrashedBeforeSettlement(net *xudtest.NetworkHarness, ht *harnessTest) {
func testMakerConnextClientCrashedBeforeTakerSettlement(net *xudtest.NetworkHarness, ht *harnessTest) {
var err error
net.Alice, err = net.SetCustomXud(ht.ctx, ht, net.Alice, []string{
"CUSTOM_SCENARIO=INSTABILITY::MAKER_CLIENT_CRASH_WHILE_SENDING",
"CLIENT_TYPE=ConnextClient",
// connext-client would be replaced, so we're not specifying its current PID,
// as in other client types.
})

net.Bob, err = net.SetCustomXud(ht.ctx, ht, net.Bob, []string{"CUSTOM_SCENARIO=INSTABILITY::TAKER_DELAY_BEFORE_SETTLE"})
ht.assert.NoError(err)

ht.act.init(net.Alice)
ht.act.initConnext(net, net.Alice, true)

ht.act.init(net.Bob)
ht.act.initConnext(net, net.Bob, false)

// Connect Alice to Bob.
ht.act.connect(net.Alice, net.Bob)
ht.act.verifyConnectivity(net.Alice, net.Bob)

err = openETHChannel(ht.ctx, net.Alice, 40000, 0)
ht.assert.NoError(err)

// Save the initial balances.
alicePrevBalance, err := net.Alice.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "BTC"})
ht.assert.NoError(err)
alicePrevBtcBalance := alicePrevBalance.Balances["BTC"]

bobPrevBalance, err := net.Bob.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "ETH"})
ht.assert.NoError(err)
bobPrevEthBalance := bobPrevBalance.Balances["ETH"]

// Place an order on Alice.
aliceOrderReq := &xudrpc.PlaceOrderRequest{
OrderId: "maker_order_id",
Price: 40,
Quantity: 100,
PairId: "BTC/ETH",
Side: xudrpc.OrderSide_BUY,
}
ht.act.placeOrderAndBroadcast(net.Alice, net.Bob, aliceOrderReq)

// Place a matching order on Bob.
bobOrderReq := &xudrpc.PlaceOrderRequest{
OrderId: "taker_order_id",
Price: aliceOrderReq.Price,
Quantity: aliceOrderReq.Quantity,
PairId: aliceOrderReq.PairId,
Side: xudrpc.OrderSide_SELL,
}
go net.Bob.Client.PlaceOrderSync(ht.ctx, bobOrderReq)

// Alice's connext-client is expected to be killed by Alice's custom xud.
<-net.Alice.ConnextClient.ProcessExit

// Verify that both Alice and Bob didn't claim their payments yet, since Bob
// settlement is being intentionally delayed.
aliceIntermediateBalance, err := net.Alice.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "BTC"})
ht.assert.NoError(err)
aliceIntermediateBtcBalance := aliceIntermediateBalance.Balances["BTC"]
ht.assert.Equal(alicePrevBtcBalance.ChannelBalance, aliceIntermediateBtcBalance.ChannelBalance)

bobIntermediateBalance, err := net.Bob.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "ETH"})
ht.assert.NoError(err)
bobIntermediateEthBalance := bobIntermediateBalance.Balances["ETH"]
ht.assert.Equal(bobPrevEthBalance.ChannelBalance, bobIntermediateEthBalance.ChannelBalance)

// Wait a bit and verify that Bob has settled and claimed his payment.
time.Sleep(5 * time.Second)
bobBalance, err := net.Bob.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "ETH"})
ht.assert.NoError(err)
bobEthBalance := bobBalance.Balances["ETH"]
diff := uint64(float64(bobOrderReq.Quantity) * bobOrderReq.Price)
ht.assert.Equal(bobPrevEthBalance.ChannelBalance+diff, bobEthBalance.ChannelBalance)

// Restart Alice's connext-client.
err = net.Alice.ConnextClient.Start(nil)
ht.assert.NoError(err)
err = waitConnextReady(net.Alice)
ht.assert.NoError(err)

// Wait to allow the Alice to recover.
time.Sleep(30 * time.Second)

aliceBalance, err := net.Alice.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "BTC"})
ht.assert.NoError(err)
aliceBtcBalance := aliceBalance.Balances["BTC"]
diff = aliceOrderReq.Quantity
ht.assert.Equal(alicePrevBtcBalance.ChannelBalance+diff, aliceBtcBalance.ChannelBalance, "alice did not recover BTC funds")
}

func testMakerConnextClientCrashedBeforeMakerSettlement(net *xudtest.NetworkHarness, ht *harnessTest) {
var err error
net.Alice, err = net.SetCustomXud(ht.ctx, ht, net.Alice, []string{
"CUSTOM_SCENARIO=INSTABILITY::MAKER_CLIENT_CRASHED_BEFORE_SETTLE",
"CLIENT_TYPE=ConnextClient",
// connext-client should be replaced, so we're not specifying its current PID,
// connext-client would be replaced, so we're not specifying its current PID,
// as in other client types.
})

Expand Down