mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-09 14:18:03 -05:00
153 lines
4.6 KiB
Go
153 lines
4.6 KiB
Go
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
package daemon
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/athanorlabs/atomic-swap/coins"
|
|
"github.com/athanorlabs/atomic-swap/common/types"
|
|
"github.com/athanorlabs/atomic-swap/monero"
|
|
"github.com/athanorlabs/atomic-swap/rpcclient"
|
|
"github.com/athanorlabs/atomic-swap/tests"
|
|
)
|
|
|
|
// This test starts a swap between Bob and Alice. The nodes restart *twice* after the xmr is locked
|
|
// but before Bob claims. Then, on second restart, Bob should have claimed and
|
|
// Alice should be able to get the XMR.
|
|
// The test restarts twice specifically to check that the swap keys were not overwritten or
|
|
// deleted from the database.
|
|
func TestAliceDoubleRestartAfterXMRLock(t *testing.T) {
|
|
minXMR := coins.StrToDecimal("1")
|
|
maxXMR := minXMR
|
|
exRate := coins.StrToExchangeRate("300")
|
|
providesAmt, err := exRate.ToETH(minXMR)
|
|
require.NoError(t, err)
|
|
|
|
bobConf := CreateTestConf(t, tests.GetMakerTestKey(t))
|
|
monero.MineMinXMRBalance(t, bobConf.MoneroClient, coins.MoneroToPiconero(maxXMR))
|
|
|
|
aliceConf := CreateTestConf(t, tests.GetTakerTestKey(t))
|
|
|
|
timeout := 7 * time.Minute
|
|
ctx, cancel := LaunchDaemons(t, timeout, bobConf, aliceConf)
|
|
|
|
// Use an independent context for these clients that will execute across multiple runs of the daemons
|
|
bc := rpcclient.NewClient(context.Background(), bobConf.RPCPort)
|
|
ac := rpcclient.NewClient(context.Background(), aliceConf.RPCPort)
|
|
|
|
tokenAsset := getMockTetherAsset(t, aliceConf.EthereumClient)
|
|
|
|
makeResp, bobStatusCh, err := bc.MakeOfferAndSubscribe(minXMR, maxXMR, exRate, tokenAsset, false)
|
|
require.NoError(t, err)
|
|
|
|
aliceStatusCh, err := ac.TakeOfferAndSubscribe(makeResp.PeerID, makeResp.OfferID, providesAmt)
|
|
require.NoError(t, err)
|
|
|
|
var statusWG sync.WaitGroup
|
|
statusWG.Add(2)
|
|
|
|
// Test that Bob completes the swap successfully
|
|
go func() {
|
|
defer statusWG.Done()
|
|
for {
|
|
select {
|
|
case status := <-bobStatusCh:
|
|
t.Log("> Bob got status:", status)
|
|
if !status.IsOngoing() {
|
|
assert.Equal(t, types.CompletedSuccess.String(), status.String())
|
|
return
|
|
}
|
|
|
|
if status == types.XMRLocked {
|
|
cancel()
|
|
t.Log("cancelling context of Alice's and Bob's servers")
|
|
return
|
|
}
|
|
case <-ctx.Done():
|
|
t.Logf("Bob's context cancelled before she completed the swap [expected]")
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// In theory, Alice won't complete the swap in the background goroutine
|
|
// below, because we shut down her server as soon as Bob's end succeeded,
|
|
// and Bob would normally succeed first, while Alice is still sweeping XMR
|
|
// funds from the swap wallet back to her primary wallet.
|
|
go func() {
|
|
defer statusWG.Done()
|
|
for {
|
|
select {
|
|
case status := <-aliceStatusCh:
|
|
t.Log("> Alice got status:", status)
|
|
if !status.IsOngoing() {
|
|
return
|
|
}
|
|
case <-ctx.Done():
|
|
t.Logf("Alice's context cancelled before she completed the swap [expected]")
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
statusWG.Wait()
|
|
t.Logf("Both swaps completed or cancelled")
|
|
if t.Failed() {
|
|
return
|
|
}
|
|
|
|
// Make sure both servers had time to fully shut down
|
|
time.Sleep(3 * time.Second)
|
|
|
|
// relaunch the daemons (1st time)
|
|
t.Logf("daemons stopped, now re-launching them")
|
|
_, cancel = LaunchDaemons(t, 3*time.Minute, bobConf, aliceConf)
|
|
|
|
t.Logf("daemons relaunched, waiting a few seconds before restarting them")
|
|
time.Sleep(3 * time.Second)
|
|
cancel()
|
|
time.Sleep(3 * time.Second) // wait for daemons to shut down
|
|
|
|
// relaunch the daemons (2nd and final time) overwriting ctx
|
|
t.Logf("daemons stopped, now re-launching them")
|
|
ctx, _ = LaunchDaemons(t, 5*time.Minute, bobConf, aliceConf)
|
|
t.Logf("daemons relaunched, checking swap status")
|
|
|
|
aliceStatusCh, err = ac.SubscribeSwapStatus(makeResp.OfferID)
|
|
require.NoError(t, err)
|
|
t.Logf("subscribed to Alice's swap status")
|
|
|
|
// In the loop below, Alice's initial state is most likely still SweepingXMR.
|
|
endLoop:
|
|
for {
|
|
select {
|
|
case status := <-aliceStatusCh:
|
|
t.Log("> Alice got status:", status)
|
|
if !status.IsOngoing() {
|
|
break endLoop
|
|
}
|
|
case <-ctx.Done():
|
|
t.Logf("Alice's context cancelled before she completed the swap [expected]")
|
|
break endLoop
|
|
}
|
|
}
|
|
|
|
pastSwap, err := ac.GetPastSwap(&makeResp.OfferID)
|
|
require.NoError(t, err)
|
|
t.Logf("Alice past status: %s", pastSwap.Swaps[0].Status)
|
|
require.Equal(t, types.CompletedSuccess.String(), pastSwap.Swaps[0].Status.String())
|
|
|
|
pastSwap, err = bc.GetPastSwap(&makeResp.OfferID)
|
|
require.NoError(t, err)
|
|
t.Logf("Bob past status: %s", pastSwap.Swaps[0].Status)
|
|
require.Equal(t, types.CompletedSuccess.String(), pastSwap.Swaps[0].Status.String())
|
|
}
|