mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-09 14:18:03 -05:00
* version independent monerod path with localhost-only binding * fixed lint target and install script * created 3rd ETH key constant to avoid race condition in tests * updated run-integration-tests.sh with the same changes made to run-unit-tests.sh * new design for paritioning out 2 unique keys to test packages * restored accidentally deleted tests/alice.key * removed websocket connection leaks from tests * made file paths unique between tests with better file cleanup * fix for the websocket connection leak commit * reverted increase of GenerateBlocks (didn't mean to commit that) * fixed maker/taker key that I had reversed * fixed incorrect zero-balance check * fixed comment on ClaimOrRefund * added back sample script for installing go in /usr/local/go * etchclient creation cleanup using t.Cleanup() * minor cleanup to ganache_test_keys code * initial dynamic monero-wallet-rpc implementation for tests * converted monero tests to use dynamic monero-wallet-rpc services * unit tests all using dynamic monero-wallet-rpc services * fixed 2 tests that failed after moving to dynamic monero-wallet-rpc services * fixed low balance issues in TestSwapState_NotifyClaimed Co-authored-by: noot <36753753+noot@users.noreply.github.com>
446 lines
13 KiB
Go
446 lines
13 KiB
Go
package xmrtaker
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"math/big"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
|
|
"github.com/noot/atomic-swap/common"
|
|
"github.com/noot/atomic-swap/common/types"
|
|
mcrypto "github.com/noot/atomic-swap/crypto/monero"
|
|
"github.com/noot/atomic-swap/net"
|
|
"github.com/noot/atomic-swap/net/message"
|
|
pcommon "github.com/noot/atomic-swap/protocol"
|
|
"github.com/noot/atomic-swap/protocol/backend"
|
|
pswap "github.com/noot/atomic-swap/protocol/swap"
|
|
"github.com/noot/atomic-swap/swapfactory"
|
|
"github.com/noot/atomic-swap/tests"
|
|
|
|
logging "github.com/ipfs/go-log"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var infofile = os.TempDir() + "/test.keys"
|
|
|
|
var _ = logging.SetLogLevel("xmrtaker", "debug")
|
|
|
|
type mockNet struct {
|
|
msg net.Message
|
|
}
|
|
|
|
func (n *mockNet) SendSwapMessage(msg net.Message, _ types.Hash) error {
|
|
n.msg = msg
|
|
return nil
|
|
}
|
|
|
|
func newBackend(t *testing.T) backend.Backend {
|
|
pk, err := ethcrypto.HexToECDSA(tests.GetTakerTestKey(t))
|
|
require.NoError(t, err)
|
|
|
|
ec, err := ethclient.Dial(common.DefaultEthEndpoint)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
ec.Close()
|
|
})
|
|
|
|
txOpts, err := bind.NewKeyedTransactorWithChainID(pk, big.NewInt(common.DevelopmentConfig.EthereumChainID))
|
|
require.NoError(t, err)
|
|
addr, _, contract, err := swapfactory.DeploySwapFactory(txOpts, ec)
|
|
require.NoError(t, err)
|
|
|
|
bcfg := &backend.Config{
|
|
Ctx: context.Background(),
|
|
MoneroWalletEndpoint: tests.CreateWalletRPCService(t),
|
|
MoneroDaemonEndpoint: common.DefaultMoneroDaemonEndpoint,
|
|
EthereumClient: ec,
|
|
EthereumPrivateKey: pk,
|
|
Environment: common.Development,
|
|
ChainID: big.NewInt(common.DevelopmentConfig.EthereumChainID),
|
|
SwapManager: pswap.NewManager(),
|
|
SwapContract: contract,
|
|
SwapContractAddress: addr,
|
|
Net: new(mockNet),
|
|
}
|
|
|
|
b, err := backend.NewBackend(bcfg)
|
|
require.NoError(t, err)
|
|
return b
|
|
}
|
|
|
|
func newXMRMakerBackend(t *testing.T) backend.Backend {
|
|
pk, err := ethcrypto.HexToECDSA(tests.GetMakerTestKey(t))
|
|
require.NoError(t, err)
|
|
|
|
ec, err := ethclient.Dial(common.DefaultEthEndpoint)
|
|
require.NoError(t, err)
|
|
defer ec.Close()
|
|
|
|
txOpts, err := bind.NewKeyedTransactorWithChainID(pk, big.NewInt(common.DevelopmentConfig.EthereumChainID))
|
|
require.NoError(t, err)
|
|
addr, _, contract, err := swapfactory.DeploySwapFactory(txOpts, ec)
|
|
require.NoError(t, err)
|
|
|
|
bcfg := &backend.Config{
|
|
Ctx: context.Background(),
|
|
MoneroWalletEndpoint: tests.CreateWalletRPCService(t),
|
|
MoneroDaemonEndpoint: common.DefaultMoneroDaemonEndpoint,
|
|
EthereumClient: ec,
|
|
EthereumPrivateKey: pk,
|
|
Environment: common.Development,
|
|
ChainID: big.NewInt(common.DevelopmentConfig.EthereumChainID),
|
|
SwapManager: pswap.NewManager(),
|
|
SwapContract: contract,
|
|
SwapContractAddress: addr,
|
|
Net: new(mockNet),
|
|
}
|
|
|
|
b, err := backend.NewBackend(bcfg)
|
|
require.NoError(t, err)
|
|
return b
|
|
}
|
|
|
|
func newTestInstance(t *testing.T) *swapState {
|
|
b := newBackend(t)
|
|
swapState, err := newSwapState(b, types.Hash{}, infofile, false,
|
|
common.NewEtherAmount(1), common.MoneroAmount(0), 1)
|
|
require.NoError(t, err)
|
|
return swapState
|
|
}
|
|
|
|
func newTestXMRMakerSendKeysMessage(t *testing.T) (*net.SendKeysMessage, *pcommon.KeysAndProof) {
|
|
keysAndProof, err := pcommon.GenerateKeysAndProof()
|
|
require.NoError(t, err)
|
|
|
|
msg := &net.SendKeysMessage{
|
|
PublicSpendKey: keysAndProof.PublicKeyPair.SpendKey().Hex(),
|
|
PrivateViewKey: keysAndProof.PrivateKeyPair.ViewKey().Hex(),
|
|
DLEqProof: hex.EncodeToString(keysAndProof.DLEqProof.Proof()),
|
|
Secp256k1PublicKey: keysAndProof.Secp256k1PublicKey.String(),
|
|
EthAddress: "0x",
|
|
}
|
|
|
|
return msg, keysAndProof
|
|
}
|
|
|
|
func TestSwapState_HandleProtocolMessage_SendKeysMessage(t *testing.T) {
|
|
s := newTestInstance(t)
|
|
defer s.cancel()
|
|
|
|
msg := &net.SendKeysMessage{}
|
|
_, _, err := s.HandleProtocolMessage(msg)
|
|
require.Equal(t, errMissingKeys, err)
|
|
|
|
err = s.generateAndSetKeys()
|
|
require.NoError(t, err)
|
|
|
|
msg, xmrmakerKeysAndProof := newTestXMRMakerSendKeysMessage(t)
|
|
|
|
resp, done, err := s.HandleProtocolMessage(msg)
|
|
require.NoError(t, err)
|
|
require.False(t, done)
|
|
require.NotNil(t, resp)
|
|
require.Equal(t, s.SwapTimeout(), s.t1.Sub(s.t0))
|
|
require.Equal(t, xmrmakerKeysAndProof.PublicKeyPair.SpendKey().Hex(), s.xmrmakerPublicSpendKey.Hex())
|
|
require.Equal(t, xmrmakerKeysAndProof.PrivateKeyPair.ViewKey().Hex(), s.xmrmakerPrivateViewKey.Hex())
|
|
}
|
|
|
|
// test the case where XMRTaker deploys and locks her eth, but XMRMaker never locks his monero.
|
|
// XMRTaker should call refund before the timeout t0.
|
|
func TestSwapState_HandleProtocolMessage_SendKeysMessage_Refund(t *testing.T) {
|
|
s := newTestInstance(t)
|
|
defer s.cancel()
|
|
|
|
// set timeout to 2s
|
|
s.SetSwapTimeout(time.Second * 2)
|
|
|
|
err := s.generateAndSetKeys()
|
|
require.NoError(t, err)
|
|
|
|
msg, xmrmakerKeysAndProof := newTestXMRMakerSendKeysMessage(t)
|
|
|
|
resp, done, err := s.HandleProtocolMessage(msg)
|
|
require.NoError(t, err)
|
|
require.False(t, done)
|
|
require.NotNil(t, resp)
|
|
require.Equal(t, message.NotifyETHLockedType, resp.Type())
|
|
require.Equal(t, s.SwapTimeout(), s.t1.Sub(s.t0))
|
|
require.Equal(t, xmrmakerKeysAndProof.PublicKeyPair.SpendKey().Hex(), s.xmrmakerPublicSpendKey.Hex())
|
|
require.Equal(t, xmrmakerKeysAndProof.PrivateKeyPair.ViewKey().Hex(), s.xmrmakerPrivateViewKey.Hex())
|
|
|
|
for status := range s.statusCh {
|
|
if status == types.CompletedRefund {
|
|
break
|
|
} else if !status.IsOngoing() {
|
|
t.Fatalf("got wrong exit status %s, expected CompletedRefund", status)
|
|
}
|
|
}
|
|
|
|
// ensure we refund before t0
|
|
require.NotNil(t, s.Net().(*mockNet).msg)
|
|
require.Equal(t, message.NotifyRefundType, s.Net().(*mockNet).msg.Type())
|
|
|
|
// check swap is marked completed
|
|
stage, err := s.Contract().Swaps(s.CallOpts(), s.contractSwapID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, swapfactory.StageCompleted, stage)
|
|
}
|
|
|
|
func TestSwapState_NotifyXMRLock(t *testing.T) {
|
|
s := newTestInstance(t)
|
|
defer s.cancel()
|
|
s.nextExpectedMessage = &message.NotifyXMRLock{}
|
|
|
|
err := s.generateAndSetKeys()
|
|
require.NoError(t, err)
|
|
|
|
xmrmakerKeysAndProof, err := generateKeys()
|
|
require.NoError(t, err)
|
|
|
|
s.setXMRMakerKeys(xmrmakerKeysAndProof.PublicKeyPair.SpendKey(), xmrmakerKeysAndProof.PrivateKeyPair.ViewKey(),
|
|
xmrmakerKeysAndProof.Secp256k1PublicKey)
|
|
|
|
_, err = s.lockETH(common.NewEtherAmount(1))
|
|
require.NoError(t, err)
|
|
|
|
kp := mcrypto.SumSpendAndViewKeys(xmrmakerKeysAndProof.PublicKeyPair, s.pubkeys)
|
|
xmrAddr := kp.Address(common.Mainnet)
|
|
|
|
msg := &message.NotifyXMRLock{
|
|
Address: string(xmrAddr),
|
|
}
|
|
|
|
resp, done, err := s.HandleProtocolMessage(msg)
|
|
require.NoError(t, err)
|
|
require.False(t, done)
|
|
require.NotNil(t, resp)
|
|
require.Equal(t, message.NotifyReadyType, resp.Type())
|
|
}
|
|
|
|
// test the case where the monero is locked, but XMRMaker never claims.
|
|
// XMRTaker should call refund after the timeout t1.
|
|
func TestSwapState_NotifyXMRLock_Refund(t *testing.T) {
|
|
s := newTestInstance(t)
|
|
defer s.cancel()
|
|
s.nextExpectedMessage = &message.NotifyXMRLock{}
|
|
s.SetSwapTimeout(time.Second * 3)
|
|
|
|
err := s.generateAndSetKeys()
|
|
require.NoError(t, err)
|
|
|
|
xmrmakerKeysAndProof, err := generateKeys()
|
|
require.NoError(t, err)
|
|
|
|
s.setXMRMakerKeys(xmrmakerKeysAndProof.PublicKeyPair.SpendKey(), xmrmakerKeysAndProof.PrivateKeyPair.ViewKey(),
|
|
xmrmakerKeysAndProof.Secp256k1PublicKey)
|
|
|
|
_, err = s.lockETH(common.NewEtherAmount(1))
|
|
require.NoError(t, err)
|
|
|
|
kp := mcrypto.SumSpendAndViewKeys(xmrmakerKeysAndProof.PublicKeyPair, s.pubkeys)
|
|
xmrAddr := kp.Address(common.Mainnet)
|
|
|
|
msg := &message.NotifyXMRLock{
|
|
Address: string(xmrAddr),
|
|
}
|
|
|
|
resp, done, err := s.HandleProtocolMessage(msg)
|
|
require.NoError(t, err)
|
|
require.False(t, done)
|
|
require.NotNil(t, resp)
|
|
require.Equal(t, message.NotifyReadyType, resp.Type())
|
|
|
|
_, ok := resp.(*message.NotifyReady)
|
|
require.True(t, ok)
|
|
|
|
for status := range s.statusCh {
|
|
if status == types.CompletedRefund {
|
|
break
|
|
} else if !status.IsOngoing() {
|
|
t.Fatalf("got wrong exit status %s, expected CompletedRefund", status)
|
|
}
|
|
}
|
|
|
|
require.NotNil(t, s.Net().(*mockNet).msg)
|
|
require.Equal(t, message.NotifyRefundType, s.Net().(*mockNet).msg.Type())
|
|
|
|
// check balance of contract is 0
|
|
balance, err := s.BalanceAt(context.Background(), s.ContractAddr(), nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(0), balance.Uint64())
|
|
}
|
|
|
|
func TestSwapState_NotifyClaimed(t *testing.T) {
|
|
s := newTestInstance(t)
|
|
defer s.cancel()
|
|
s.SetSwapTimeout(time.Minute * 2)
|
|
|
|
// close swap-deposit-wallet
|
|
maker := newXMRMakerBackend(t)
|
|
err := maker.CreateWallet("test-wallet", "")
|
|
require.NoError(t, err)
|
|
|
|
// invalid SendKeysMessage should result in an error
|
|
msg := &net.SendKeysMessage{}
|
|
_, _, err = s.HandleProtocolMessage(msg)
|
|
require.Equal(t, errMissingKeys, err)
|
|
|
|
err = s.generateAndSetKeys()
|
|
require.NoError(t, err)
|
|
|
|
// handle valid SendKeysMessage
|
|
msg, err = s.SendKeysMessage()
|
|
require.NoError(t, err)
|
|
msg.PrivateViewKey = s.privkeys.ViewKey().Hex()
|
|
msg.EthAddress = s.EthAddress().String()
|
|
|
|
resp, done, err := s.HandleProtocolMessage(msg)
|
|
require.NoError(t, err)
|
|
require.False(t, done)
|
|
require.NotNil(t, resp)
|
|
require.Equal(t, time.Minute*2, s.t1.Sub(s.t0))
|
|
require.Equal(t, msg.PublicSpendKey, s.xmrmakerPublicSpendKey.Hex())
|
|
require.Equal(t, msg.PrivateViewKey, s.xmrmakerPrivateViewKey.Hex())
|
|
|
|
// simulate xmrmaker locking xmr
|
|
xmrmakerAddr, err := maker.GetAddress(0)
|
|
require.NoError(t, err)
|
|
|
|
// mine some blocks to get xmr first
|
|
err = maker.GenerateBlocks(xmrmakerAddr.Address, 60)
|
|
require.NoError(t, err)
|
|
err = maker.Refresh()
|
|
require.NoError(t, err)
|
|
amt := common.MoneroAmount(1000000000)
|
|
kp := mcrypto.SumSpendAndViewKeys(s.pubkeys, s.pubkeys)
|
|
xmrAddr := kp.Address(common.Mainnet)
|
|
|
|
// lock xmr
|
|
_, err = maker.Transfer(xmrAddr, 0, uint(amt))
|
|
require.NoError(t, err)
|
|
t.Log("transferred to account", xmrAddr)
|
|
|
|
_ = maker.GenerateBlocks(xmrmakerAddr.Address, 100)
|
|
|
|
// send notification that monero was locked
|
|
lmsg := &message.NotifyXMRLock{
|
|
Address: string(xmrAddr),
|
|
}
|
|
|
|
resp, done, err = s.HandleProtocolMessage(lmsg)
|
|
require.NoError(t, err)
|
|
require.False(t, done)
|
|
require.NotNil(t, resp)
|
|
require.Equal(t, message.NotifyReadyType, resp.Type())
|
|
|
|
err = maker.GenerateBlocks(xmrmakerAddr.Address, 1)
|
|
require.NoError(t, err)
|
|
|
|
// simulate xmrmaker calling claim
|
|
// call swap.Swap.Claim() w/ b.privkeys.sk, revealing XMRMaker's secret spend key
|
|
secret := s.privkeys.SpendKeyBytes()
|
|
var sc [32]byte
|
|
copy(sc[:], common.Reverse(secret))
|
|
|
|
txOpts, err := s.TxOpts()
|
|
require.NoError(t, err)
|
|
tx, err := s.Contract().Claim(txOpts, s.contractSwap, sc)
|
|
require.NoError(t, err)
|
|
|
|
// handled the claimed message should result in the monero wallet being created
|
|
cmsg := &message.NotifyClaimed{
|
|
TxHash: tx.Hash().String(),
|
|
}
|
|
|
|
resp, done, err = s.HandleProtocolMessage(cmsg)
|
|
require.NoError(t, err)
|
|
require.True(t, done)
|
|
require.Nil(t, resp)
|
|
}
|
|
|
|
func TestExit_afterSendKeysMessage(t *testing.T) {
|
|
s := newTestInstance(t)
|
|
defer s.cancel()
|
|
s.nextExpectedMessage = &message.SendKeysMessage{}
|
|
err := s.Exit()
|
|
require.NoError(t, err)
|
|
info := s.SwapManager().GetPastSwap(s.info.ID())
|
|
require.Equal(t, types.CompletedAbort, info.Status())
|
|
}
|
|
|
|
func TestExit_afterNotifyXMRLock(t *testing.T) {
|
|
s := newTestInstance(t)
|
|
defer s.cancel()
|
|
s.nextExpectedMessage = &message.NotifyXMRLock{}
|
|
|
|
err := s.generateAndSetKeys()
|
|
require.NoError(t, err)
|
|
|
|
xmrmakerKeysAndProof, err := generateKeys()
|
|
require.NoError(t, err)
|
|
|
|
s.setXMRMakerKeys(xmrmakerKeysAndProof.PublicKeyPair.SpendKey(), xmrmakerKeysAndProof.PrivateKeyPair.ViewKey(),
|
|
xmrmakerKeysAndProof.Secp256k1PublicKey)
|
|
|
|
_, err = s.lockETH(common.NewEtherAmount(1))
|
|
require.NoError(t, err)
|
|
|
|
err = s.Exit()
|
|
require.NoError(t, err)
|
|
info := s.SwapManager().GetPastSwap(s.info.ID())
|
|
require.Equal(t, types.CompletedRefund, info.Status())
|
|
}
|
|
|
|
func TestExit_afterNotifyClaimed(t *testing.T) {
|
|
s := newTestInstance(t)
|
|
defer s.cancel()
|
|
s.nextExpectedMessage = &message.NotifyClaimed{}
|
|
|
|
err := s.generateAndSetKeys()
|
|
require.NoError(t, err)
|
|
|
|
xmrmakerKeysAndProof, err := generateKeys()
|
|
require.NoError(t, err)
|
|
|
|
s.setXMRMakerKeys(xmrmakerKeysAndProof.PublicKeyPair.SpendKey(), xmrmakerKeysAndProof.PrivateKeyPair.ViewKey(),
|
|
xmrmakerKeysAndProof.Secp256k1PublicKey)
|
|
|
|
_, err = s.lockETH(common.NewEtherAmount(1))
|
|
require.NoError(t, err)
|
|
|
|
err = s.Exit()
|
|
require.NoError(t, err)
|
|
info := s.SwapManager().GetPastSwap(s.info.ID())
|
|
require.Equal(t, types.CompletedRefund, info.Status())
|
|
}
|
|
|
|
func TestExit_invalidNextMessageType(t *testing.T) {
|
|
// this case shouldn't ever really happen
|
|
s := newTestInstance(t)
|
|
defer s.cancel()
|
|
s.nextExpectedMessage = &message.NotifyETHLocked{}
|
|
|
|
err := s.generateAndSetKeys()
|
|
require.NoError(t, err)
|
|
|
|
xmrmakerKeysAndProof, err := generateKeys()
|
|
require.NoError(t, err)
|
|
|
|
s.setXMRMakerKeys(xmrmakerKeysAndProof.PublicKeyPair.SpendKey(), xmrmakerKeysAndProof.PrivateKeyPair.ViewKey(),
|
|
xmrmakerKeysAndProof.Secp256k1PublicKey)
|
|
|
|
_, err = s.lockETH(common.NewEtherAmount(1))
|
|
require.NoError(t, err)
|
|
|
|
err = s.Exit()
|
|
require.Equal(t, errUnexpectedMessageType, err)
|
|
info := s.SwapManager().GetPastSwap(s.info.ID())
|
|
require.Equal(t, types.CompletedAbort, info.Status())
|
|
}
|