Files
atomic-swap/monero/test_support.go

151 lines
4.6 KiB
Go

// Copyright 2023 The AthanorLabs/atomic-swap Authors
// SPDX-License-Identifier: LGPL-3.0-only
//go:build !prod
package monero
//
// This file is only for test support when working with monerod in regtest mode. Use the build
// tag "prod" to prevent symbols in this file from consuming space (or mentioning mining) in
// production binaries.
//
import (
"context"
"path"
"runtime"
"sync"
"testing"
"time"
"github.com/MarinX/monerorpc"
"github.com/MarinX/monerorpc/daemon"
"github.com/MarinX/monerorpc/wallet"
"github.com/stretchr/testify/require"
"github.com/athanorlabs/atomic-swap/coins"
"github.com/athanorlabs/atomic-swap/common"
)
const (
// Mastering monero example address (we don't use the background mining block rewards in tests)
blockRewardAddress = "4BKjy1uVRTPiz4pHyaXXawb82XpzLiowSDd8rEQJGqvN6AD6kWosLQ6VJXW9sghopxXgQSh1RTd54JdvvCRsXiF41xvfeW5"
)
// GetWalletRPCDirectory returns the directory path of monero-wallet-rpc.
func GetWalletRPCDirectory(t *testing.T) string {
_, filename, _, ok := runtime.Caller(0) // this test file path
require.True(t, ok)
packageDir := path.Dir(filename)
repoBaseDir := path.Dir(packageDir)
return path.Join(repoBaseDir, "monero-bin", "monero-wallet-rpc")
}
// CreateWalletClientWithWalletDir creates a WalletClient with the given wallet directory.
func CreateWalletClientWithWalletDir(t *testing.T, walletDir string) WalletClient {
moneroWalletRPCPath := GetWalletRPCDirectory(t)
c, err := NewWalletClient(&WalletClientConf{
Env: common.Development,
WalletFilePath: path.Join(walletDir, "test-wallet"),
MoneroWalletRPCPath: moneroWalletRPCPath,
})
require.NoError(t, err)
t.Cleanup(func() {
c.Close()
})
TestBackgroundMineBlocks(t)
return c
}
// CreateWalletClient starts a monero-wallet-rpc listening on a random port for tests and
// returns the client interface for using it. Background mining is initiated so created transactions
// get mined into blocks.
func CreateWalletClient(t *testing.T) WalletClient {
return CreateWalletClientWithWalletDir(t, t.TempDir())
}
// GetBalance is a convenience method for tests that assumes you want the primary
// address and that errors should fail the test.
func GetBalance(t *testing.T, wc WalletClient) *wallet.GetBalanceResponse {
balance, err := wc.GetBalance(0)
require.NoError(t, err)
return balance
}
// TestBackgroundMineBlocks starts a background go routine to mine blocks in a monerod instance
// that is in regtest mode. If there is an existing go routine that is already mining from
// a previous call, no new go routine is created.
func TestBackgroundMineBlocks(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
ctx, cancelFunc := context.WithCancel(context.Background())
t.Cleanup(func() {
cancelFunc()
wg.Wait()
})
// Lower the sleep duration used by WaitForBlock
blockSleepDuration = backgroundMineInterval / 3
go func() {
defer wg.Done()
if !mineMu.TryLock() {
return // If there are multiple clients in a test, only let one of them mine.
}
defer mineMu.Unlock()
for {
select {
case <-ctx.Done():
return
case <-time.After(backgroundMineInterval):
// not cancelled, mine another block below
}
daemonCli := monerorpc.New(MonerodRegtestEndpoint, nil).Daemon
resp, err := daemonCli.GenerateBlocks(&daemon.GenerateBlocksRequest{
AmountOfBlocks: 1,
WalletAddress: blockRewardAddress,
})
if err != nil && err.Error() == errBlockNotAccepted {
// This probably happens when something else is simultaneously generating
// blocks, not an error that matters unless it is happening frequently.
t.Logf("Background mining had non-accepted block")
continue
}
require.NoError(t, err)
if false { // change to true if debugging and you want to see when new blocks are generated
t.Logf("Block generated height=%d", resp.Height)
}
}
}()
}
// MineMinXMRBalance enables mining for the passed wc wallet until it has an unlocked balance greater
// than or equal to minBalance.
func MineMinXMRBalance(t *testing.T, wc WalletClient, minBalance *coins.PiconeroAmount) {
daemonCli := monerorpc.New(MonerodRegtestEndpoint, nil).Daemon
addr, err := wc.GetAddress(0)
require.NoError(t, err)
t.Log("mining to address:", addr.Address)
minBalU64, err := minBalance.Uint64()
require.NoError(t, err)
for {
balance, err := wc.GetBalance(0)
require.NoError(t, err)
if balance.UnlockedBalance > minBalU64 {
break
}
_, err = daemonCli.GenerateBlocks(&daemon.GenerateBlocksRequest{
AmountOfBlocks: 32,
WalletAddress: addr.Address,
})
if err != nil && err.Error() == errBlockNotAccepted {
continue
}
require.NoError(t, err)
}
}