mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-10 06:38:04 -05:00
190 lines
5.4 KiB
Go
190 lines
5.4 KiB
Go
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
//go:build !prod
|
|
|
|
package daemon
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"fmt"
|
|
"math/big"
|
|
"net"
|
|
"sync"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
ethcommon "github.com/ethereum/go-ethereum/common"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/athanorlabs/atomic-swap/common"
|
|
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
|
"github.com/athanorlabs/atomic-swap/ethereum/extethclient"
|
|
"github.com/athanorlabs/atomic-swap/monero"
|
|
"github.com/athanorlabs/atomic-swap/rpcclient"
|
|
"github.com/athanorlabs/atomic-swap/tests"
|
|
)
|
|
|
|
// map indexes for our mock tokens
|
|
const (
|
|
MockDAI = "DAI"
|
|
MockTether = "USDT"
|
|
)
|
|
|
|
// This file is only for test support. Use the build tag "prod" to prevent
|
|
// symbols in this file from consuming space in production binaries.
|
|
|
|
// CreateTestConf creates a localhost-only dev environment SwapdConfig config
|
|
// for testing
|
|
func CreateTestConf(t *testing.T, ethKey *ecdsa.PrivateKey) *SwapdConfig {
|
|
ctx := context.Background()
|
|
ec, err := extethclient.NewEthClient(ctx, common.Development, common.DefaultGanacheEndpoint, ethKey)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
ec.Close()
|
|
})
|
|
|
|
rpcPort, err := common.GetFreeTCPPort()
|
|
require.NoError(t, err)
|
|
|
|
// We need a copy of the environment conf, as it is no longer a singleton
|
|
// when we are testing it here.
|
|
envConf := new(common.Config)
|
|
*envConf = *common.ConfigDefaultsForEnv(common.Development)
|
|
envConf.DataDir = t.TempDir()
|
|
// Passed in ETH key may not have funds, deploy contract with the funded taker key
|
|
envConf.SwapCreatorAddr, _ = contracts.DevDeploySwapCreator(t, ec.Raw(), tests.GetTakerTestKey(t))
|
|
|
|
return &SwapdConfig{
|
|
EnvConf: envConf,
|
|
MoneroClient: monero.CreateWalletClient(t),
|
|
EthereumClient: ec,
|
|
Libp2pPort: 0,
|
|
Libp2pKeyfile: "",
|
|
RPCPort: uint16(rpcPort),
|
|
IsRelayer: false,
|
|
NoTransferBack: false,
|
|
}
|
|
}
|
|
|
|
// LaunchDaemons launches one or more swapd daemons and blocks until they are
|
|
// started. If more than one config is passed, the bootnode settings of the
|
|
// passed config are modified to make the first daemon the bootnode for the
|
|
// remaining daemons.
|
|
func LaunchDaemons(t *testing.T, timeout time.Duration, configs ...*SwapdConfig) (context.Context, context.CancelFunc) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
|
|
var wg sync.WaitGroup
|
|
t.Cleanup(func() {
|
|
cancel()
|
|
wg.Wait()
|
|
})
|
|
|
|
var bootNodes []string // First daemon to launch has no bootnodes
|
|
|
|
for n, conf := range configs {
|
|
conf.EnvConf.Bootnodes = bootNodes
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
err := RunSwapDaemon(ctx, conf)
|
|
require.ErrorIs(t, err, context.Canceled)
|
|
}()
|
|
WaitForSwapdStart(t, conf.RPCPort)
|
|
|
|
// Configure remaining daemons to use the first one a bootnode
|
|
if n == 0 {
|
|
c := rpcclient.NewClient(ctx, fmt.Sprintf("http://127.0.0.1:%d", conf.RPCPort))
|
|
addresses, err := c.Addresses()
|
|
require.NoError(t, err)
|
|
require.Greater(t, len(addresses.Addrs), 1)
|
|
bootNodes = []string{addresses.Addrs[0]}
|
|
}
|
|
}
|
|
|
|
return ctx, cancel
|
|
}
|
|
|
|
// WaitForSwapdStart takes the rpcPort of a swapd instance and waits for it to
|
|
// be in a listening state. Fails the test if the server isn't listening after a
|
|
// little over 60 seconds.
|
|
func WaitForSwapdStart(t *testing.T, rpcPort uint16) {
|
|
const maxSeconds = 60
|
|
addr := fmt.Sprintf("127.0.0.1:%d", rpcPort)
|
|
|
|
startTime := time.Now()
|
|
|
|
for i := 0; i < maxSeconds; i++ {
|
|
conn, err := net.DialTimeout("tcp", addr, time.Second)
|
|
if err == nil {
|
|
startupTime := time.Since(startTime).Round(time.Second)
|
|
t.Logf("daemon on rpc port %d started after %s", rpcPort, startupTime)
|
|
require.NoError(t, conn.Close())
|
|
return
|
|
}
|
|
// DialTimeout doesn't do retries. If the connection was refused, it happened
|
|
// almost immediately, so we still need to sleep.
|
|
require.ErrorIs(t, err, syscall.ECONNREFUSED)
|
|
time.Sleep(time.Second)
|
|
}
|
|
t.Fatalf("giving up, swapd RPC port %d is not listening after %d seconds", rpcPort, maxSeconds)
|
|
}
|
|
|
|
// these variables are only for use by GetMockTokens
|
|
var _mockTokens map[string]ethcommon.Address
|
|
var _mockTokensMu sync.Mutex
|
|
|
|
// GetMockTokens returns a symbol=>address map of our mock ERC20 tokens,
|
|
// deploying them if they haven't already been deployed. Use the constants
|
|
// defined earlier to access the map elements.
|
|
func GetMockTokens(t *testing.T, ec extethclient.EthClient) map[string]ethcommon.Address {
|
|
_mockTokensMu.Lock()
|
|
defer _mockTokensMu.Unlock()
|
|
|
|
if _mockTokens == nil {
|
|
_mockTokens = make(map[string]ethcommon.Address)
|
|
}
|
|
|
|
calcSupply := func(numStdUnits int64, decimals int64) *big.Int {
|
|
return new(big.Int).Mul(big.NewInt(numStdUnits), new(big.Int).Exp(big.NewInt(10), big.NewInt(decimals), nil))
|
|
}
|
|
|
|
ctx := context.Background()
|
|
txOpts, err := ec.TxOpts(ctx)
|
|
require.NoError(t, err)
|
|
|
|
mockDaiAddr, mockDaiTx, _, err := contracts.DeployTestERC20(
|
|
txOpts,
|
|
ec.Raw(),
|
|
"Dai Stablecoin",
|
|
"DAI",
|
|
18,
|
|
ec.Address(),
|
|
calcSupply(1000, 18),
|
|
)
|
|
require.NoError(t, err)
|
|
tests.MineTransaction(t, ec.Raw(), mockDaiTx)
|
|
_mockTokens[MockDAI] = mockDaiAddr
|
|
|
|
txOpts, err = ec.TxOpts(ctx)
|
|
require.NoError(t, err)
|
|
|
|
mockTetherAddr, mockTetherTx, _, err := contracts.DeployTestERC20(
|
|
txOpts,
|
|
ec.Raw(),
|
|
"Tether USD",
|
|
"USDT",
|
|
6,
|
|
ec.Address(),
|
|
calcSupply(1000, 6),
|
|
)
|
|
require.NoError(t, err)
|
|
tests.MineTransaction(t, ec.Raw(), mockTetherTx)
|
|
_mockTokens[MockTether] = mockTetherAddr
|
|
|
|
return _mockTokens
|
|
}
|