Files
atomic-swap/protocol/xmrtaker/swap_state_test.go
Dmitry Holodov adfb0fa602 Test cleanup (#135)
* 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>
2022-06-29 13:36:03 -04:00

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())
}