decred.org/dcrdex@v1.0.5/client/asset/dcr/simnet_test.go (about)

     1  //go:build harness
     2  
     3  package dcr
     4  
     5  // Simnet tests expect the DCR test harness to be running.
     6  //
     7  // Sim harness info:
     8  // The harness has two wallets, alpha and beta
     9  // Both wallets have confirmed UTXOs.
    10  // The alpha wallet has large coinbase outputs and smaller change outputs.
    11  // The beta wallet has mature vote outputs and regular transaction outputs of
    12  // varying size and confirmation count. Value:Confirmations =
    13  // 10:8, 18:7, 5:6, 7:5, 1:4, 15:3, 3:2, 25:1
    14  
    15  import (
    16  	"bytes"
    17  	"context"
    18  	"crypto/sha256"
    19  	"encoding/hex"
    20  	"errors"
    21  	"fmt"
    22  	"math"
    23  	"math/rand"
    24  	"os"
    25  	"os/exec"
    26  	"os/user"
    27  	"path/filepath"
    28  	"testing"
    29  	"time"
    30  
    31  	"decred.org/dcrdex/client/asset"
    32  	"decred.org/dcrdex/dex"
    33  	"decred.org/dcrdex/dex/config"
    34  	"decred.org/dcrdex/dex/encode"
    35  	dexdcr "decred.org/dcrdex/dex/networks/dcr"
    36  	"decred.org/dcrwallet/v5/wallet"
    37  	"github.com/davecgh/go-spew/spew"
    38  	"github.com/decred/dcrd/chaincfg/v3"
    39  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    40  	"github.com/decred/dcrd/dcrutil/v4"
    41  	"github.com/decred/dcrd/wire"
    42  )
    43  
    44  const (
    45  	alphaAddress = "SsWKp7wtdTZYabYFYSc9cnxhwFEjA5g4pFc"
    46  	betaAddress  = "Ssge52jCzbixgFC736RSTrwAnvH3a4hcPRX"
    47  	gammaSeed    = "1285a47d6a59f9c548b2a72c2c34a2de97967bede3844090102bbba76707fe9d"
    48  	vspAddr      = "http://127.0.0.1:19591"
    49  )
    50  
    51  var (
    52  	tLogger   dex.Logger
    53  	tCtx      context.Context
    54  	tLotSize  uint64 = 1e7
    55  	tRateStep uint64 = 100
    56  	tDCR             = &dex.Asset{
    57  		ID:         42,
    58  		Symbol:     "dcr",
    59  		Version:    version,
    60  		MaxFeeRate: 10,
    61  		SwapConf:   1,
    62  	}
    63  	walletPassword = []byte("abc")
    64  )
    65  
    66  func mineAlpha() error {
    67  	return exec.Command("tmux", "send-keys", "-t", "dcr-harness:0", "./mine-alpha 1", "C-m").Run()
    68  }
    69  
    70  func tBackend(t *testing.T, name string, isInternal bool, blkFunc func(string)) (*ExchangeWallet, *dex.ConnectionMaster) {
    71  	t.Helper()
    72  	user, err := user.Current()
    73  	if err != nil {
    74  		t.Fatalf("error getting current user: %v", err)
    75  	}
    76  	settings := make(map[string]string)
    77  	if !isInternal {
    78  		cfgPath := filepath.Join(user.HomeDir, "dextest", "dcr", name, name+".conf")
    79  		var err error
    80  		settings, err = config.Parse(cfgPath)
    81  		if err != nil {
    82  			t.Fatalf("error reading config options: %v", err)
    83  		}
    84  	}
    85  	notes := make(chan asset.WalletNotification, 1)
    86  	walletCfg := &asset.WalletConfig{
    87  		Settings: settings,
    88  		Emit:     asset.NewWalletEmitter(notes, BipID, tLogger),
    89  		PeersChange: func(num uint32, err error) {
    90  			t.Logf("peer count = %d, err = %v", num, err)
    91  		},
    92  	}
    93  	if isInternal {
    94  		seed, err := hex.DecodeString(gammaSeed)
    95  		if err != nil {
    96  			t.Fatal(err)
    97  		}
    98  		dataDir := t.TempDir()
    99  		createSPVWallet(walletPassword, seed, dataDir, 0, 0, wallet.DefaultGapLimit, chaincfg.SimNetParams())
   100  		walletCfg.Type = walletTypeSPV
   101  		walletCfg.DataDir = dataDir
   102  	}
   103  	var backend asset.Wallet
   104  	backend, err = NewWallet(walletCfg, tLogger, dex.Simnet)
   105  	if err != nil {
   106  		t.Fatalf("error creating backend: %v", err)
   107  	}
   108  	cm := dex.NewConnectionMaster(backend)
   109  	err = cm.Connect(tCtx)
   110  	if err != nil {
   111  		t.Fatalf("error connecting backend: %v", err)
   112  	}
   113  
   114  	go func() {
   115  		for {
   116  			select {
   117  			case ni := <-notes:
   118  				switch ni.(type) {
   119  				case *asset.TipChangeNote:
   120  					blkFunc(name)
   121  				}
   122  			case <-tCtx.Done():
   123  				return
   124  			}
   125  		}
   126  	}()
   127  
   128  	if isInternal {
   129  		i := 0
   130  		for {
   131  			ss, err := backend.SyncStatus()
   132  			if err != nil {
   133  				t.Fatal(err)
   134  			}
   135  			if ss.Synced {
   136  				break
   137  			}
   138  			if i == 5 {
   139  				t.Fatal("spv wallet not synced after 5 seconds")
   140  			}
   141  			i++
   142  			time.Sleep(time.Second)
   143  		}
   144  	}
   145  
   146  	if nativeWallet, is := backend.(*NativeWallet); is {
   147  		return nativeWallet.ExchangeWallet, cm
   148  	}
   149  
   150  	return backend.(*ExchangeWallet), cm
   151  }
   152  
   153  type testRig struct {
   154  	backends          map[string]*ExchangeWallet
   155  	connectionMasters map[string]*dex.ConnectionMaster
   156  }
   157  
   158  func newTestRig(t *testing.T, blkFunc func(string)) *testRig {
   159  	t.Helper()
   160  	rig := &testRig{
   161  		backends:          make(map[string]*ExchangeWallet),
   162  		connectionMasters: make(map[string]*dex.ConnectionMaster, 3),
   163  	}
   164  	rig.backends["alpha"], rig.connectionMasters["alpha"] = tBackend(t, "alpha", false, blkFunc)
   165  	rig.backends["beta"], rig.connectionMasters["beta"] = tBackend(t, "beta", false, blkFunc)
   166  	rig.backends["gamma"], rig.connectionMasters["gamma"] = tBackend(t, "gamma", true, blkFunc)
   167  	return rig
   168  }
   169  
   170  // alpha is an external wallet connected to dcrd.
   171  func (rig *testRig) alpha() *ExchangeWallet {
   172  	return rig.backends["alpha"]
   173  }
   174  
   175  // beta is an external spv wallet.
   176  func (rig *testRig) beta() *ExchangeWallet {
   177  	return rig.backends["beta"]
   178  }
   179  
   180  // gamma is an internal spv wallet.
   181  func (rig *testRig) gamma() *ExchangeWallet {
   182  	return rig.backends["gamma"]
   183  }
   184  
   185  func (rig *testRig) close(t *testing.T) {
   186  	t.Helper()
   187  	for name, cm := range rig.connectionMasters {
   188  		closed := make(chan struct{})
   189  		go func() {
   190  			cm.Disconnect()
   191  			close(closed)
   192  		}()
   193  		select {
   194  		case <-closed:
   195  		case <-time.NewTimer(time.Second).C:
   196  			t.Fatalf("failed to disconnect from %s", name)
   197  		}
   198  	}
   199  }
   200  
   201  func randBytes(l int) []byte {
   202  	b := make([]byte, l)
   203  	rand.Read(b)
   204  	return b
   205  }
   206  
   207  func waitNetwork() {
   208  	time.Sleep(time.Second * 3 / 2)
   209  }
   210  
   211  func TestMain(m *testing.M) {
   212  	tLogger = dex.StdOutLogger("TEST", dex.LevelTrace)
   213  	var shutdown func()
   214  	tCtx, shutdown = context.WithCancel(context.Background())
   215  	doIt := func() int {
   216  		defer shutdown()
   217  		return m.Run()
   218  	}
   219  	os.Exit(doIt())
   220  }
   221  
   222  func TestMakeBondTx(t *testing.T) {
   223  	rig := newTestRig(t, func(name string) {
   224  		tLogger.Infof("%s has reported a new block", name)
   225  	})
   226  	defer rig.close(t)
   227  
   228  	// Get a private key for the bond script. This would come from the client's
   229  	// HD key chain.
   230  	priv, err := secp256k1.GeneratePrivateKey()
   231  	if err != nil {
   232  		t.Fatal(err)
   233  	}
   234  	pubkey := priv.PubKey()
   235  
   236  	acctID := randBytes(32)
   237  	fee := uint64(10_2030_4050) //  ~10.2 DCR
   238  	const bondVer = 0
   239  
   240  	wallet := rig.beta()
   241  
   242  	// Unlock the wallet to sign the tx and get keys.
   243  	err = wallet.Unlock(walletPassword)
   244  	if err != nil {
   245  		t.Fatalf("error unlocking beta wallet: %v", err)
   246  	}
   247  
   248  	lockTime := time.Now().Add(5 * time.Minute)
   249  	bond, _, err := wallet.MakeBondTx(bondVer, fee, 10, lockTime, priv, acctID)
   250  	if err != nil {
   251  		t.Fatal(err)
   252  	}
   253  	coinhash, _, err := decodeCoinID(bond.CoinID)
   254  	if err != nil {
   255  		t.Fatalf("decodeCoinID: %v", err)
   256  	}
   257  	t.Logf("bond txid %v\n", coinhash)
   258  	t.Logf("signed tx: %x\n", bond.SignedTx)
   259  	t.Logf("unsigned tx: %x\n", bond.UnsignedTx)
   260  	t.Logf("bond script: %x\n", bond.Data)
   261  	t.Logf("redeem tx: %x\n", bond.RedeemTx)
   262  	bondMsgTx, err := msgTxFromBytes(bond.SignedTx)
   263  	if err != nil {
   264  		t.Fatalf("invalid bond tx: %v", err)
   265  	}
   266  	bondOutVersion := bondMsgTx.TxOut[0].Version
   267  
   268  	pkh := dcrutil.Hash160(pubkey.SerializeCompressed())
   269  
   270  	lockTimeUint, pkhPush, err := dexdcr.ExtractBondDetailsV0(bondOutVersion, bond.Data)
   271  	if err != nil {
   272  		t.Fatalf("ExtractBondDetailsV0: %v", err)
   273  	}
   274  	if !bytes.Equal(pkh, pkhPush) {
   275  		t.Fatalf("mismatching pubkeyhash in bond script and signature (%x != %x)", pkh, pkhPush)
   276  	}
   277  
   278  	if lockTime.Unix() != int64(lockTimeUint) {
   279  		t.Fatalf("mismatching locktimes (%d != %d)", lockTime.Unix(), lockTimeUint)
   280  	}
   281  	lockTimePush := time.Unix(int64(lockTimeUint), 0)
   282  	t.Logf("lock time in bond script: %v", lockTimePush)
   283  
   284  	sendBondTx, err := wallet.SendTransaction(bond.SignedTx)
   285  	if err != nil {
   286  		t.Fatalf("RefundBond: %v", err)
   287  	}
   288  	sendBondTxid, _, err := decodeCoinID(sendBondTx)
   289  	if err != nil {
   290  		t.Fatalf("decodeCoinID: %v", err)
   291  	}
   292  	t.Logf("sendBondTxid: %v\n", sendBondTxid)
   293  
   294  	waitNetwork() // wait for alpha to see the txn
   295  	mineAlpha()
   296  	waitNetwork() // wait for beta to see the new block (bond must be mined for RefundBond)
   297  
   298  	refundCoin, err := wallet.RefundBond(context.Background(), bondVer, bond.CoinID,
   299  		bond.Data, bond.Amount, priv)
   300  	if err != nil {
   301  		t.Fatalf("RefundBond: %v", err)
   302  	}
   303  	t.Logf("refundCoin: %v\n", refundCoin)
   304  }
   305  
   306  func TestWallet(t *testing.T) {
   307  	tLogger.Infof("////////// WITHOUT SPLIT FUNDING TRANSACTIONS //////////")
   308  	runTest(t, false)
   309  	tLogger.Infof("////////// WITH SPLIT FUNDING TRANSACTIONS //////////")
   310  	runTest(t, true)
   311  }
   312  
   313  func runTest(t *testing.T, splitTx bool) {
   314  	tStart := time.Now()
   315  	blockReported := false
   316  	rig := newTestRig(t, func(name string) {
   317  		blockReported = true
   318  		tLogger.Infof("%s has reported a new block", name)
   319  	})
   320  	defer rig.close(t)
   321  	contractValue := toAtoms(2)
   322  	lots := contractValue / tLotSize
   323  
   324  	inUTXOs := func(utxo asset.Coin, utxos []asset.Coin) bool {
   325  		for _, u := range utxos {
   326  			if bytes.Equal(u.ID(), utxo.ID()) {
   327  				return true
   328  			}
   329  		}
   330  		return false
   331  	}
   332  
   333  	// Check available amount.
   334  	for name, wallet := range rig.backends {
   335  		bal, err := wallet.Balance()
   336  		if err != nil {
   337  			t.Fatalf("error getting available: %v", err)
   338  		}
   339  		tLogger.Debugf("%s %f available, %f unconfirmed, %f locked",
   340  			name, float64(bal.Available)/1e8, float64(bal.Immature)/1e8, float64(bal.Locked)/1e8)
   341  		wallet.config().useSplitTx = splitTx
   342  	}
   343  
   344  	// Unlock the wallet for use.
   345  	err := rig.beta().Unlock(walletPassword)
   346  	if err != nil {
   347  		t.Fatalf("error unlocking beta wallet: %v", err)
   348  	}
   349  
   350  	ord := &asset.Order{
   351  		Version:      tDCR.Version,
   352  		Value:        contractValue * 3,
   353  		MaxSwapCount: lots * 3,
   354  		MaxFeeRate:   tDCR.MaxFeeRate,
   355  	}
   356  	setOrderValue := func(v uint64) {
   357  		ord.Value = v
   358  		ord.MaxSwapCount = v / tLotSize
   359  	}
   360  
   361  	// Grab some coins.
   362  	utxos, _, _, err := rig.beta().FundOrder(ord)
   363  	if err != nil {
   364  		t.Fatalf("Funding error: %v", err)
   365  	}
   366  	utxo := utxos[0]
   367  
   368  	// Coins should be locked
   369  	utxos, _, _, _ = rig.beta().FundOrder(ord)
   370  	if !splitTx && inUTXOs(utxo, utxos) {
   371  		t.Fatalf("received locked output")
   372  	}
   373  	rig.beta().ReturnCoins([]asset.Coin{utxo})
   374  	rig.beta().ReturnCoins(utxos)
   375  	// Make sure we get the first utxo back with Fund.
   376  	utxos, _, _, _ = rig.beta().FundOrder(ord)
   377  	if !splitTx && !inUTXOs(utxo, utxos) {
   378  		t.Fatalf("unlocked output not returned")
   379  	}
   380  	rig.beta().ReturnCoins(utxos)
   381  
   382  	// Get a separate set of UTXOs for each contract.
   383  	setOrderValue(contractValue)
   384  	utxos1, _, _, err := rig.beta().FundOrder(ord)
   385  	if err != nil {
   386  		t.Fatalf("error funding first contract: %v", err)
   387  	}
   388  	// Get a separate set of UTXOs for each contract.
   389  	setOrderValue(contractValue * 2)
   390  	utxos2, _, _, err := rig.beta().FundOrder(ord)
   391  	if err != nil {
   392  		t.Fatalf("error funding second contract: %v", err)
   393  	}
   394  
   395  	secretKey1 := randBytes(32)
   396  	keyHash1 := sha256.Sum256(secretKey1)
   397  	secretKey2 := randBytes(32)
   398  	keyHash2 := sha256.Sum256(secretKey2)
   399  	lockTime := time.Now().Add(time.Hour * 8).UTC()
   400  	// Have beta send a swap contract to the alpha address.
   401  	contract1 := &asset.Contract{
   402  		Address:    alphaAddress,
   403  		Value:      contractValue,
   404  		SecretHash: keyHash1[:],
   405  		LockTime:   uint64(lockTime.Unix()),
   406  	}
   407  	contract2 := &asset.Contract{
   408  		Address:    alphaAddress,
   409  		Value:      contractValue * 2,
   410  		SecretHash: keyHash2[:],
   411  		LockTime:   uint64(lockTime.Unix()),
   412  	}
   413  	swaps := &asset.Swaps{
   414  		Inputs:    append(utxos1, utxos2...),
   415  		Contracts: []*asset.Contract{contract1, contract2},
   416  		FeeRate:   tDCR.MaxFeeRate,
   417  	}
   418  
   419  	receipts, _, _, err := rig.beta().Swap(swaps)
   420  	if err != nil {
   421  		t.Fatalf("error sending swap transaction: %v", err)
   422  	}
   423  
   424  	if len(receipts) != 2 {
   425  		t.Fatalf("expected 1 receipt, got %d", len(receipts))
   426  	}
   427  
   428  	confCoin := receipts[0].Coin()
   429  	confContract := receipts[0].Contract()
   430  	checkConfs := func(minN uint32, expSpent bool) {
   431  		t.Helper()
   432  		confs, spent, err := rig.beta().SwapConfirmations(tCtx, confCoin.ID(), confContract, tStart)
   433  		if err != nil {
   434  			t.Fatalf("error getting %d confs: %v", minN, err)
   435  		}
   436  		if confs < minN {
   437  			t.Fatalf("expected %d confs, got %d", minN, confs)
   438  		}
   439  		// Not using checkConfs until after redemption, so expect spent.
   440  		if spent != expSpent {
   441  			t.Fatalf("checkConfs: expected spent = %t, got %t", expSpent, spent)
   442  		}
   443  	}
   444  	// Let alpha get and process that transaction.
   445  	waitNetwork()
   446  	// Check that there are 0 confirmations.
   447  	checkConfs(0, false)
   448  
   449  	// Unlock the wallet for use.
   450  	err = rig.alpha().Unlock(walletPassword)
   451  	if err != nil {
   452  		t.Fatalf("error unlocking alpha wallet: %v", err)
   453  	}
   454  
   455  	makeRedemption := func(swapVal uint64, receipt asset.Receipt, secret []byte) *asset.Redemption {
   456  		t.Helper()
   457  		swapOutput := receipt.Coin()
   458  		op := swapOutput.(*output)
   459  		tx, err := rig.beta().wallet.GetTransaction(tCtx, op.txHash())
   460  		if err != nil || tx == nil {
   461  			t.Fatalf("GetTransaction: %v", err)
   462  		}
   463  		txData, err := tx.MsgTx.Bytes()
   464  		if err != nil {
   465  			t.Fatalf("msgTx.Bytes: %v", err)
   466  		}
   467  		ci, err := rig.alpha().AuditContract(swapOutput.ID(), receipt.Contract(), txData, false)
   468  		if err != nil {
   469  			t.Fatalf("error auditing contract: %v", err)
   470  		}
   471  		swapOutput = ci.Coin
   472  		if ci.Recipient != alphaAddress {
   473  			t.Fatalf("wrong address. %s != %s", ci.Recipient, alphaAddress)
   474  		}
   475  		if swapOutput.Value() != swapVal {
   476  			t.Fatalf("wrong contract value. wanted %d, got %d", swapVal, swapOutput.Value())
   477  		}
   478  		_, spent, err := rig.alpha().SwapConfirmations(context.TODO(), swapOutput.ID(), receipt.Contract(), tStart)
   479  		if err != nil {
   480  			t.Fatalf("error getting confirmations: %v", err)
   481  		}
   482  		if spent {
   483  			t.Fatalf("swap spent")
   484  		}
   485  		if ci.Expiration.Equal(lockTime) {
   486  			t.Fatalf("wrong lock time. wanted %s, got %s", lockTime, ci.Expiration)
   487  		}
   488  		return &asset.Redemption{
   489  			Spends: ci,
   490  			Secret: secret,
   491  		}
   492  	}
   493  
   494  	redemptions := []*asset.Redemption{
   495  		makeRedemption(contractValue, receipts[0], secretKey1),
   496  		makeRedemption(contractValue*2, receipts[1], secretKey2),
   497  	}
   498  
   499  	_, _, _, err = rig.alpha().Redeem(&asset.RedeemForm{
   500  		Redemptions: redemptions,
   501  	})
   502  	if err != nil {
   503  		t.Fatalf("redemption error: %v", err)
   504  	}
   505  
   506  	betaSPV := rig.beta().wallet.SpvMode()
   507  
   508  	// Find the redemption
   509  	swapReceipt := receipts[0]
   510  	// The mempool find redemption request does not work in SPV mode.
   511  	if !betaSPV {
   512  		waitNetwork()
   513  		ctx, cancel := context.WithDeadline(tCtx, time.Now().Add(time.Second*5))
   514  		defer cancel()
   515  		_, checkKey, err := rig.beta().FindRedemption(ctx, swapReceipt.Coin().ID(), nil)
   516  		if err != nil {
   517  			t.Fatalf("error finding unconfirmed redemption: %v", err)
   518  		}
   519  		if !bytes.Equal(checkKey, secretKey1) {
   520  			t.Fatalf("findRedemption (unconfirmed) key mismatch. %x != %x", checkKey, secretKey1)
   521  		}
   522  	}
   523  
   524  	// Mine a block and find the redemption again.
   525  	mineAlpha()
   526  	waitNetwork()
   527  	// Check that the swap has one confirmation.
   528  	checkConfs(1, true)
   529  	if !blockReported {
   530  		t.Fatalf("no block reported")
   531  	}
   532  	ctx, cancel2 := context.WithDeadline(tCtx, time.Now().Add(time.Second*5))
   533  	defer cancel2()
   534  	_, _, err = rig.beta().FindRedemption(ctx, swapReceipt.Coin().ID(), nil)
   535  	if err != nil {
   536  		t.Fatalf("error finding confirmed redemption: %v", err)
   537  	}
   538  
   539  	// Now send another one with lockTime = now and try to refund it.
   540  	secretKey := randBytes(32)
   541  	keyHash := sha256.Sum256(secretKey)
   542  	lockTime = time.Now().Add(-8 * time.Hour)
   543  
   544  	// Have beta send a swap contract to the alpha address.
   545  	setOrderValue(contractValue)
   546  	utxos, _, _, _ = rig.beta().FundOrder(ord)
   547  	contract := &asset.Contract{
   548  		Address:    alphaAddress,
   549  		Value:      contractValue,
   550  		SecretHash: keyHash[:],
   551  		LockTime:   uint64(lockTime.Unix()),
   552  	}
   553  	swaps = &asset.Swaps{
   554  		Inputs:    utxos,
   555  		Contracts: []*asset.Contract{contract},
   556  		FeeRate:   tDCR.MaxFeeRate,
   557  	}
   558  
   559  	receipts, _, _, err = rig.beta().Swap(swaps)
   560  	if err != nil {
   561  		t.Fatalf("error sending swap transaction: %v", err)
   562  	}
   563  
   564  	if len(receipts) != 1 {
   565  		t.Fatalf("expected 1 receipt, got %d", len(receipts))
   566  	}
   567  	swapReceipt = receipts[0]
   568  
   569  	waitNetwork()
   570  	_, err = rig.beta().Refund(swapReceipt.Coin().ID(), swapReceipt.Contract(), tDCR.MaxFeeRate/4)
   571  	if err != nil {
   572  		t.Fatalf("refund error: %v", err)
   573  	}
   574  
   575  	// Test Send
   576  	coin, err := rig.beta().Send(alphaAddress, 1e8, defaultFee)
   577  	if err != nil {
   578  		t.Fatalf("error sending fees: %v", err)
   579  	}
   580  	tLogger.Infof("fee paid with tx %s", coin.String())
   581  
   582  	// Test Withdraw
   583  	coin, err = rig.beta().Withdraw(alphaAddress, 5e7, tDCR.MaxFeeRate/4)
   584  	if err != nil {
   585  		t.Fatalf("error withdrawing: %v", err)
   586  	}
   587  	tLogger.Infof("withdrew with tx %s", coin.String())
   588  
   589  	// Lock the wallet
   590  	err = rig.beta().Lock()
   591  	if err != nil {
   592  		t.Fatalf("error locking wallet: %v", err)
   593  	}
   594  }
   595  
   596  func TestTickets(t *testing.T) {
   597  	rig := newTestRig(t, func(name string) {
   598  		tLogger.Infof("%s has reported a new block", name)
   599  	})
   600  	defer rig.close(t)
   601  	tLogger.Info("Testing ticket methods with the alpha rig.")
   602  	testTickets(t, false, rig.alpha())
   603  	// TODO: beta wallet is spv but has no vsp in its config. Add it and
   604  	// test here.
   605  	tLogger.Info("Testing ticket methods with the gamma rig.")
   606  	testTickets(t, true, rig.gamma())
   607  }
   608  
   609  func testTickets(t *testing.T, isInternal bool, ew *ExchangeWallet) {
   610  
   611  	// Test StakeStatus.
   612  	ss, err := ew.StakeStatus()
   613  	if err != nil {
   614  		t.Fatalf("unable to get stake status: %v", err)
   615  	}
   616  	tLogger.Info("The following are stake status before setting vsp or purchasing tickets.")
   617  	spew.Dump(ss)
   618  
   619  	// Test SetVSP.
   620  	err = ew.SetVSP(vspAddr)
   621  	if isInternal {
   622  		if err != nil {
   623  			t.Fatalf("unexpected error setting vsp for internal wallet: %v", err)
   624  		}
   625  	} else {
   626  		if err == nil {
   627  			t.Fatal("expected error setting vsp for external wallet")
   628  		}
   629  	}
   630  
   631  	// Test PurchaseTickets.
   632  	if err := ew.Unlock(walletPassword); err != nil {
   633  		t.Fatalf("unable to unlock wallet: %v", err)
   634  	}
   635  
   636  	if err := ew.PurchaseTickets(3, 20); err != nil {
   637  		t.Fatalf("error purchasing tickets: %v", err)
   638  	}
   639  
   640  	var currentDeployments []chaincfg.ConsensusDeployment
   641  	var bestVer uint32
   642  	for ver, deps := range ew.chainParams.Deployments {
   643  		if bestVer == 0 || ver > bestVer {
   644  			currentDeployments = deps
   645  			bestVer = ver
   646  		}
   647  	}
   648  
   649  	choices := make(map[string]string)
   650  	for _, d := range currentDeployments {
   651  		choices[d.Vote.Id] = d.Vote.Choices[rand.Int()%len(d.Vote.Choices)].Id
   652  	}
   653  
   654  	aPubKey := "034a43df1b95bf1b0dd77b53b8d880d4a5f47376fb036a5be5c1f3ba8d12ef65d7"
   655  	bPubKey := "02028dd2bbabbcd262c3e2326ae27a61fcc935beb2bbc2f0f76f3f5025ba4a3c5d"
   656  
   657  	treasuryPolicy := map[string]string{
   658  		aPubKey: "yes",
   659  		bPubKey: "no",
   660  	}
   661  
   662  	var tspendPolicy map[string]string
   663  	if spvw, is := ew.wallet.(*spvWallet); is {
   664  		if dcrw, is := spvw.dcrWallet.(*extendedWallet); is {
   665  			txIn := &wire.TxIn{SignatureScript: encode.RandomBytes(66 + secp256k1.PubKeyBytesLenCompressed)}
   666  			tspendA := &wire.MsgTx{Expiry: math.MaxUint32 - 1, TxIn: []*wire.TxIn{txIn}}
   667  			tspendB := &wire.MsgTx{Expiry: math.MaxUint32 - 2, TxIn: []*wire.TxIn{txIn}}
   668  			txHashA, txHashB := tspendA.TxHash(), tspendB.TxHash()
   669  			tspendPolicy = map[string]string{
   670  				txHashA.String(): "yes",
   671  				txHashB.String(): "no",
   672  			}
   673  			for _, tx := range []*wire.MsgTx{tspendA, tspendB} {
   674  				if err := dcrw.AddTSpend(*tx); err != nil {
   675  					t.Fatalf("Error adding tspend: %v", err)
   676  				}
   677  			}
   678  		}
   679  	}
   680  
   681  	// Test SetVotingPreferences.
   682  	if err := ew.SetVotingPreferences(choices, tspendPolicy, treasuryPolicy); err != nil {
   683  		t.Fatalf("Error setting voting preferences: %v", err)
   684  	}
   685  
   686  	// Test StakeStatus again.
   687  	ss, err = ew.StakeStatus()
   688  	if err != nil {
   689  		t.Fatalf("Unable to get stake status: %v", err)
   690  	}
   691  	tLogger.Info("The following are stake status after setting vsp and purchasing tickets.")
   692  	spew.Dump(ss)
   693  
   694  	if len(ss.Stances.Agendas) != len(choices) {
   695  		t.Fatalf("wrong number of vote choices. expected %d, got %d", len(choices), len(ss.Stances.Agendas))
   696  	}
   697  
   698  	for _, agenda := range ss.Stances.Agendas {
   699  		choiceID, found := choices[agenda.ID]
   700  		if !found {
   701  			t.Fatalf("unknown agenda %s", agenda.ID)
   702  		}
   703  		if agenda.CurrentChoice != choiceID {
   704  			t.Fatalf("wrong choice reported. expected %s, got %s", choiceID, agenda.CurrentChoice)
   705  		}
   706  	}
   707  
   708  	if len(ss.Stances.TreasuryKeys) != len(treasuryPolicy) {
   709  		t.Fatalf("wrong number of treasury keys. expected %d, got %d", len(treasuryPolicy), len(ss.Stances.TreasuryKeys))
   710  	}
   711  
   712  	for _, tp := range ss.Stances.TreasuryKeys {
   713  		policy, found := treasuryPolicy[tp.Key]
   714  		if !found {
   715  			t.Fatalf("unknown treasury key %s", tp.Key)
   716  		}
   717  		if tp.Policy != policy {
   718  			t.Fatalf("wrong policy reported. expected %s, got %s", policy, tp.Policy)
   719  		}
   720  	}
   721  
   722  	if len(ss.Stances.TreasurySpends) != len(tspendPolicy) {
   723  		t.Fatalf("wrong number of tspends. expected %d, got %d", len(tspendPolicy), len(ss.Stances.TreasurySpends))
   724  	}
   725  
   726  	for _, tspend := range ss.Stances.TreasurySpends {
   727  		policy, found := tspendPolicy[tspend.Hash]
   728  		if !found {
   729  			t.Fatalf("unknown tspend tx %s", tspend.Hash)
   730  		}
   731  		if tspend.CurrentPolicy != policy {
   732  			t.Fatalf("wrong policy reported. expected %s, got %s", policy, tspend.CurrentPolicy)
   733  		}
   734  	}
   735  }
   736  
   737  func TestExternalFeeRate(t *testing.T) {
   738  	fetchRateWithTimeout(t, dex.Mainnet)
   739  	fetchRateWithTimeout(t, dex.Testnet)
   740  }
   741  
   742  func fetchRateWithTimeout(t *testing.T, net dex.Network) {
   743  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
   744  	defer cancel()
   745  	feeRate, err := fetchFeeFromOracle(ctx, net, 2)
   746  	if err != nil {
   747  		t.Fatalf("error fetching %s fees: %v", net, err)
   748  	}
   749  	fmt.Printf("##### Fee rate fetched for %s! %.8f DCR/kB \n", net, feeRate)
   750  }
   751  
   752  func TestWalletTxBalanceSync(t *testing.T) {
   753  	rig := newTestRig(t, func(name string) {
   754  		tLogger.Infof("%s has reported a new block", name)
   755  	})
   756  	defer rig.close(t)
   757  
   758  	beta := rig.beta()
   759  	gamma := rig.gamma()
   760  
   761  	err := beta.Unlock(walletPassword)
   762  	if err != nil {
   763  		t.Fatalf("error unlocking beta wallet: %v", err)
   764  	}
   765  	err = gamma.Unlock(walletPassword)
   766  	if err != nil {
   767  		t.Fatalf("error unlocking gamma wallet: %v", err)
   768  	}
   769  
   770  	t.Run("rpc", func(t *testing.T) {
   771  		testWalletTxBalanceSync(t, gamma, beta)
   772  	})
   773  	t.Run("spv", func(t *testing.T) {
   774  		testWalletTxBalanceSync(t, beta, gamma)
   775  	})
   776  }
   777  
   778  // This tests that redemptions becoming available in the balance and the
   779  // asset.WalletTransaction returned from WalletTransaction becomes confirmed
   780  // at the same time.
   781  func testWalletTxBalanceSync(t *testing.T, fromWallet, toWallet *ExchangeWallet) {
   782  	receivingAddr, err := toWallet.DepositAddress()
   783  	if err != nil {
   784  		t.Fatalf("error getting deposit address: %v", err)
   785  	}
   786  
   787  	order := &asset.Order{
   788  		Value:         toAtoms(1),
   789  		FeeSuggestion: 10,
   790  		MaxSwapCount:  1,
   791  		MaxFeeRate:    20,
   792  	}
   793  	coins, _, _, err := fromWallet.FundOrder(order)
   794  	if err != nil {
   795  		t.Fatalf("error funding order: %v", err)
   796  	}
   797  
   798  	secret := randBytes(32)
   799  	secretHash := sha256.Sum256(secret)
   800  	contract := &asset.Contract{
   801  		Address:    receivingAddr,
   802  		Value:      order.Value,
   803  		SecretHash: secretHash[:],
   804  		LockTime:   uint64(time.Now().Add(-1 * time.Hour).Unix()),
   805  	}
   806  	swaps := &asset.Swaps{
   807  		Inputs:  coins,
   808  		FeeRate: 10,
   809  		Contracts: []*asset.Contract{
   810  			contract,
   811  		},
   812  	}
   813  	receipts, _, _, err := fromWallet.Swap(swaps)
   814  	if err != nil {
   815  		t.Fatalf("error swapping: %v", err)
   816  	}
   817  	receipt := receipts[0]
   818  
   819  	var auditInfo *asset.AuditInfo
   820  	for i := 0; i < 10; i++ {
   821  		auditInfo, err = toWallet.AuditContract(receipt.Coin().ID(), receipt.Contract(), []byte{}, false)
   822  		if err == nil {
   823  			break
   824  		}
   825  
   826  		time.Sleep(5 * time.Second)
   827  	}
   828  	if err != nil {
   829  		t.Fatalf("error auditing contract: %v", err)
   830  	}
   831  
   832  	balance, err := toWallet.Balance()
   833  	if err != nil {
   834  		t.Fatalf("error getting balance: %v", err)
   835  	}
   836  	_, out, _, err := toWallet.Redeem(&asset.RedeemForm{
   837  		Redemptions: []*asset.Redemption{
   838  			{
   839  				Spends: auditInfo,
   840  				Secret: secret,
   841  			},
   842  		},
   843  		FeeSuggestion: 10,
   844  	})
   845  	if err != nil {
   846  		t.Fatalf("error redeeming: %v", err)
   847  	}
   848  
   849  	confirmSync := func(originalBalance uint64, coinID []byte) {
   850  		t.Helper()
   851  
   852  		for i := 0; i < 10; i++ {
   853  			balance, err := toWallet.Balance()
   854  			if err != nil {
   855  				t.Fatalf("error getting balance: %v", err)
   856  			}
   857  			balDiff := balance.Available - originalBalance
   858  
   859  			var confirmed bool
   860  			var txDiff uint64
   861  			if wt, err := toWallet.WalletTransaction(context.Background(), hex.EncodeToString(coinID)); err == nil {
   862  				confirmed = wt.Confirmed
   863  				txDiff = wt.Amount
   864  				if wt.Type != asset.Receive {
   865  					txDiff -= wt.Fees
   866  				}
   867  			} else if !errors.Is(err, asset.CoinNotFoundError) {
   868  				t.Fatal(err)
   869  			}
   870  
   871  			balanceChanged := balance.Available != originalBalance
   872  			if confirmed != balanceChanged {
   873  				if balanceChanged && !confirmed {
   874  					for j := 0; j < 20; j++ {
   875  						if wt, err := toWallet.WalletTransaction(context.Background(), hex.EncodeToString(coinID)); err == nil && wt.Confirmed {
   876  							t.Fatalf("took %d seconds after balance changed before tx was confirmed", j/2)
   877  						}
   878  						time.Sleep(500 * time.Millisecond)
   879  					}
   880  				}
   881  				t.Fatalf("confirmed status does not match balance change. confirmed = %v, balance changed = %d", confirmed, balDiff)
   882  			}
   883  
   884  			if confirmed {
   885  				if balDiff != txDiff {
   886  					t.Fatalf("balance and transaction diffs do not match. balance diff = %d, tx diff = %d", balDiff, txDiff)
   887  				}
   888  				return
   889  			}
   890  
   891  			time.Sleep(5 * time.Second)
   892  		}
   893  
   894  		t.Fatal("timed out waiting for balance and transaction to sync")
   895  	}
   896  
   897  	confirmSync(balance.Available, out.ID())
   898  
   899  	balance, err = toWallet.Balance()
   900  	if err != nil {
   901  		t.Fatalf("error getting balance: %v", err)
   902  	}
   903  
   904  	receivingAddr, err = toWallet.DepositAddress()
   905  	if err != nil {
   906  		t.Fatalf("error getting deposit address: %v", err)
   907  	}
   908  
   909  	coin, err := fromWallet.Send(receivingAddr, toAtoms(1), 10)
   910  	if err != nil {
   911  		t.Fatalf("error sending: %v", err)
   912  	}
   913  
   914  	confirmSync(balance.Available, coin.ID())
   915  }