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