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

     1  //go:build !harness && !vspd
     2  
     3  package dcr
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"crypto/sha256"
     9  	"encoding/hex"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"math"
    14  	"math/rand"
    15  	"os"
    16  	"reflect"
    17  	"sort"
    18  	"strings"
    19  	"sync"
    20  	"sync/atomic"
    21  	"testing"
    22  	"time"
    23  
    24  	"decred.org/dcrdex/client/asset"
    25  	"decred.org/dcrdex/dex"
    26  	"decred.org/dcrdex/dex/calc"
    27  	"decred.org/dcrdex/dex/config"
    28  	"decred.org/dcrdex/dex/encode"
    29  	dexdcr "decred.org/dcrdex/dex/networks/dcr"
    30  	"decred.org/dcrwallet/v5/rpc/client/dcrwallet"
    31  	walletjson "decred.org/dcrwallet/v5/rpc/jsonrpc/types"
    32  	"github.com/decred/dcrd/chaincfg/chainhash"
    33  	"github.com/decred/dcrd/chaincfg/v3"
    34  	"github.com/decred/dcrd/dcrec"
    35  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    36  	"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
    37  	"github.com/decred/dcrd/dcrjson/v4"
    38  	"github.com/decred/dcrd/dcrutil/v4"
    39  	"github.com/decred/dcrd/gcs/v4"
    40  	"github.com/decred/dcrd/gcs/v4/blockcf2"
    41  	chainjson "github.com/decred/dcrd/rpc/jsonrpc/types/v4"
    42  	"github.com/decred/dcrd/txscript/v4"
    43  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    44  	"github.com/decred/dcrd/wire"
    45  )
    46  
    47  var (
    48  	tLogger   dex.Logger
    49  	tCtx      context.Context
    50  	tLotSize  uint64 = 1e7
    51  	tRateStep uint64 = 100
    52  	tDCR             = &dex.Asset{
    53  		ID:         42,
    54  		Symbol:     "dcr",
    55  		Version:    version,
    56  		MaxFeeRate: 24, // FundOrder and swap/redeem fallback when estimation fails
    57  		SwapConf:   1,
    58  	}
    59  	optimalFeeRate uint64 = 22
    60  	tErr                  = fmt.Errorf("test error")
    61  	tTxID                 = "308e9a3675fc3ea3862b7863eeead08c621dcc37ff59de597dd3cdab41450ad9"
    62  	tTxHash        *chainhash.Hash
    63  	tPKHAddr       stdaddr.Address
    64  	tP2PKHScript   []byte
    65  	tChainParams          = chaincfg.MainNetParams()
    66  	feeSuggestion  uint64 = 10
    67  	tAcctName             = "tAcctName"
    68  )
    69  
    70  func randBytes(l int) []byte {
    71  	b := make([]byte, l)
    72  	rand.Read(b)
    73  	return b
    74  }
    75  
    76  func makeGetTxOutRes(confs, lots int64, pkScript []byte) *chainjson.GetTxOutResult {
    77  	val := dcrutil.Amount(lots * int64(tLotSize)).ToCoin()
    78  	return &chainjson.GetTxOutResult{
    79  		Confirmations: confs,
    80  		Value:         val,
    81  		ScriptPubKey: chainjson.ScriptPubKeyResult{
    82  			Hex: hex.EncodeToString(pkScript),
    83  		},
    84  	}
    85  }
    86  
    87  func makeRawTx(inputs []*wire.TxIn, outputScripts []dex.Bytes) *wire.MsgTx {
    88  	tx := wire.NewMsgTx()
    89  	for _, pkScript := range outputScripts {
    90  		tx.TxOut = append(tx.TxOut, &wire.TxOut{
    91  			PkScript: pkScript,
    92  		})
    93  	}
    94  	tx.TxIn = inputs
    95  	return tx
    96  }
    97  
    98  func makeTxHex(inputs []*wire.TxIn, pkScripts []dex.Bytes) (string, error) {
    99  	msgTx := wire.NewMsgTx()
   100  	msgTx.TxIn = inputs
   101  	for _, pkScript := range pkScripts {
   102  		txOut := wire.NewTxOut(100000000, pkScript)
   103  		msgTx.AddTxOut(txOut)
   104  	}
   105  	txBuf := bytes.NewBuffer(make([]byte, 0, msgTx.SerializeSize()))
   106  	err := msgTx.Serialize(txBuf)
   107  	if err != nil {
   108  		return "", err
   109  	}
   110  	return hex.EncodeToString(txBuf.Bytes()), nil
   111  }
   112  
   113  func makeRPCVin(txHash *chainhash.Hash, vout uint32, sigScript []byte) *wire.TxIn {
   114  	return &wire.TxIn{
   115  		PreviousOutPoint: *wire.NewOutPoint(txHash, vout, 0),
   116  		SignatureScript:  sigScript,
   117  	}
   118  }
   119  
   120  func newTxOutResult(script []byte, value uint64, confs int64) *chainjson.GetTxOutResult {
   121  	return &chainjson.GetTxOutResult{
   122  		Confirmations: confs,
   123  		Value:         float64(value) / 1e8,
   124  		ScriptPubKey: chainjson.ScriptPubKeyResult{
   125  			Hex: hex.EncodeToString(script),
   126  		},
   127  	}
   128  }
   129  
   130  func dummyInput() *wire.TxIn {
   131  	return wire.NewTxIn(wire.NewOutPoint(&chainhash.Hash{0x01}, 0, 0), 0, nil)
   132  }
   133  
   134  func dummyTx() *wire.MsgTx {
   135  	return makeRawTx([]*wire.TxIn{dummyInput()}, []dex.Bytes{})
   136  }
   137  
   138  // sometimes we want to not start monitor blocks to avoid a race condition in
   139  // case we replace the wallet.
   140  func tNewWalletMonitorBlocks(monitorBlocks bool) (*ExchangeWallet, *tRPCClient, func()) {
   141  	client := newTRPCClient()
   142  	log := tLogger.SubLogger("trpc")
   143  	walletCfg := &asset.WalletConfig{
   144  		Emit:        asset.NewWalletEmitter(client.emitC, BipID, log),
   145  		PeersChange: func(uint32, error) {},
   146  	}
   147  	walletCtx, shutdown := context.WithCancel(tCtx)
   148  
   149  	wallet, err := unconnectedWallet(walletCfg, &walletConfig{}, tChainParams, tLogger, dex.Simnet)
   150  	if err != nil {
   151  		shutdown()
   152  		panic(err.Error())
   153  	}
   154  	wallet.connected.Store(true)
   155  	rpcw := &rpcWallet{
   156  		rpcClient: client,
   157  		log:       log,
   158  	}
   159  	rpcw.accountsV.Store(XCWalletAccounts{
   160  		PrimaryAccount: tAcctName,
   161  	})
   162  	wallet.wallet = rpcw
   163  	wallet.ctx = walletCtx
   164  
   165  	// Initialize the best block.
   166  	tip, _ := wallet.getBestBlock(walletCtx)
   167  	wallet.currentTip.Store(tip)
   168  
   169  	if monitorBlocks {
   170  		go wallet.monitorBlocks(walletCtx)
   171  	}
   172  
   173  	return wallet, client, shutdown
   174  
   175  }
   176  
   177  func tNewWallet() (*ExchangeWallet, *tRPCClient, func()) {
   178  	return tNewWalletMonitorBlocks(true)
   179  }
   180  
   181  func signFunc(msgTx *wire.MsgTx, scriptSize int) (*wire.MsgTx, bool, error) {
   182  	for i := range msgTx.TxIn {
   183  		msgTx.TxIn[i].SignatureScript = randBytes(scriptSize)
   184  	}
   185  	return msgTx, true, nil
   186  }
   187  
   188  type tRPCClient struct {
   189  	sendRawHash    *chainhash.Hash
   190  	sendRawErr     error
   191  	sentRawTx      *wire.MsgTx
   192  	txOutRes       map[outPoint]*chainjson.GetTxOutResult
   193  	txOutErr       error
   194  	bestBlockErr   error
   195  	mempoolErr     error
   196  	rawTxErr       error
   197  	unspent        []walletjson.ListUnspentResult
   198  	unspentErr     error
   199  	balanceResult  *walletjson.GetBalanceResult
   200  	balanceErr     error
   201  	lockUnspentErr error
   202  	changeAddr     stdaddr.Address
   203  	changeAddrErr  error
   204  	newAddr        stdaddr.Address
   205  	newAddrErr     error
   206  	signFunc       func(tx *wire.MsgTx) (*wire.MsgTx, bool, error)
   207  	privWIF        *dcrutil.WIF
   208  	privWIFErr     error
   209  	walletTxFn     func() (*walletjson.GetTransactionResult, error)
   210  	lockErr        error
   211  	passErr        error
   212  	disconnected   bool
   213  	rawRes         map[string]json.RawMessage
   214  	rawErr         map[string]error
   215  	blockchain     *tBlockchain
   216  	lluCoins       []walletjson.ListUnspentResult // Returned from ListLockUnspent
   217  	lockedCoins    []*wire.OutPoint               // Last submitted to LockUnspent
   218  	listLockedErr  error
   219  	estFeeErr      error
   220  	emitC          chan asset.WalletNotification
   221  	// tickets
   222  	purchasedTickets   [][]*chainhash.Hash
   223  	purchaseTicketsErr error
   224  	stakeInfo          walletjson.GetStakeInfoResult
   225  	validateAddress    map[string]*walletjson.ValidateAddressResult
   226  }
   227  
   228  type wireTxWithHeight struct {
   229  	tx     *wire.MsgTx
   230  	height int64
   231  }
   232  
   233  type tBlockchain struct {
   234  	mtx               sync.RWMutex
   235  	rawTxs            map[chainhash.Hash]*wireTxWithHeight
   236  	mainchain         map[int64]*chainhash.Hash
   237  	verboseBlocks     map[chainhash.Hash]*wire.MsgBlock
   238  	blockHeaders      map[chainhash.Hash]*wire.BlockHeader
   239  	v2CFilterBuilders map[chainhash.Hash]*tV2CFilterBuilder
   240  }
   241  
   242  func (blockchain *tBlockchain) addRawTx(blockHeight int64, tx *wire.MsgTx) (*chainhash.Hash, *wire.MsgBlock) {
   243  	blockchain.mtx.Lock()
   244  	defer blockchain.mtx.Unlock()
   245  
   246  	blockchain.rawTxs[tx.TxHash()] = &wireTxWithHeight{tx, blockHeight}
   247  
   248  	if blockHeight < 0 {
   249  		return nil, nil
   250  	}
   251  
   252  	prevBlockHash := blockchain.mainchain[blockHeight-1]
   253  
   254  	// Mined tx. Add to block.
   255  	block := blockchain.blockAt(blockHeight)
   256  	if block == nil {
   257  		block = &wire.MsgBlock{
   258  			Header: wire.BlockHeader{
   259  				PrevBlock: *prevBlockHash,
   260  				Height:    uint32(blockHeight),
   261  				VoteBits:  1,
   262  				Timestamp: time.Now(),
   263  			},
   264  		}
   265  	}
   266  	block.Transactions = append(block.Transactions, tx)
   267  	blockHash := block.BlockHash()
   268  	blockFilterBuilder := blockchain.v2CFilterBuilders[blockHash]
   269  	blockchain.mainchain[blockHeight] = &blockHash
   270  	blockchain.verboseBlocks[blockHash] = block
   271  
   272  	blockchain.blockHeaders[blockHash] = &block.Header
   273  
   274  	// Save prevout and output scripts in block cfilters.
   275  	if blockFilterBuilder == nil {
   276  		blockFilterBuilder = &tV2CFilterBuilder{}
   277  		copy(blockFilterBuilder.key[:], randBytes(16))
   278  
   279  	}
   280  	blockchain.v2CFilterBuilders[blockHash] = blockFilterBuilder
   281  	for _, txIn := range tx.TxIn {
   282  		prevOut := &txIn.PreviousOutPoint
   283  		prevTx, found := blockchain.rawTxs[prevOut.Hash]
   284  		if !found || len(prevTx.tx.TxOut) <= int(prevOut.Index) {
   285  			continue
   286  		}
   287  		blockFilterBuilder.data.AddRegularPkScript(prevTx.tx.TxOut[prevOut.Index].PkScript)
   288  	}
   289  	for _, txOut := range tx.TxOut {
   290  		blockFilterBuilder.data.AddRegularPkScript(txOut.PkScript)
   291  	}
   292  
   293  	return &blockHash, block
   294  }
   295  
   296  // blockchain.mtx lock should be held for writes.
   297  func (blockchain *tBlockchain) blockAt(height int64) *wire.MsgBlock {
   298  	blkHash, found := blockchain.mainchain[height]
   299  	if found {
   300  		return blockchain.verboseBlocks[*blkHash]
   301  	}
   302  
   303  	return nil
   304  }
   305  
   306  type tV2CFilterBuilder struct {
   307  	data blockcf2.Entries
   308  	key  [gcs.KeySize]byte
   309  }
   310  
   311  func (filterBuilder *tV2CFilterBuilder) build() (*gcs.FilterV2, error) {
   312  	return gcs.NewFilterV2(blockcf2.B, blockcf2.M, filterBuilder.key, filterBuilder.data)
   313  }
   314  
   315  func defaultSignFunc(tx *wire.MsgTx) (*wire.MsgTx, bool, error) { return tx, true, nil }
   316  
   317  func newTRPCClient() *tRPCClient {
   318  	// setup genesis block, required by bestblock polling goroutine
   319  	var newHash chainhash.Hash
   320  	copy(newHash[:], randBytes(32))
   321  	return &tRPCClient{
   322  		txOutRes: make(map[outPoint]*chainjson.GetTxOutResult),
   323  		blockchain: &tBlockchain{
   324  			rawTxs: map[chainhash.Hash]*wireTxWithHeight{},
   325  			mainchain: map[int64]*chainhash.Hash{
   326  				0: &newHash,
   327  			},
   328  			verboseBlocks: map[chainhash.Hash]*wire.MsgBlock{
   329  				newHash: {},
   330  			},
   331  			blockHeaders: map[chainhash.Hash]*wire.BlockHeader{
   332  				newHash: {},
   333  			},
   334  			v2CFilterBuilders: map[chainhash.Hash]*tV2CFilterBuilder{},
   335  		},
   336  		signFunc: defaultSignFunc,
   337  		rawRes:   make(map[string]json.RawMessage),
   338  		rawErr:   make(map[string]error),
   339  		emitC:    make(chan asset.WalletNotification, 128),
   340  	}
   341  }
   342  
   343  func (c *tRPCClient) GetCurrentNet(context.Context) (wire.CurrencyNet, error) {
   344  	return tChainParams.Net, nil
   345  }
   346  
   347  func (c *tRPCClient) EstimateSmartFee(_ context.Context, confirmations int64, mode chainjson.EstimateSmartFeeMode) (*chainjson.EstimateSmartFeeResult, error) {
   348  	if c.estFeeErr != nil {
   349  		return nil, c.estFeeErr
   350  	}
   351  	optimalRate := float64(optimalFeeRate) * 1e-5 // optimalFeeRate: 22 atoms/byte = 0.00022 DCR/KB * 1e8 atoms/DCR * 1e-3 KB/Byte
   352  	// fmt.Println((float64(optimalFeeRate)*1e-5)-0.00022)
   353  	return &chainjson.EstimateSmartFeeResult{FeeRate: optimalRate}, nil
   354  }
   355  
   356  func (c *tRPCClient) SendRawTransaction(_ context.Context, tx *wire.MsgTx, allowHighFees bool) (*chainhash.Hash, error) {
   357  	c.sentRawTx = tx
   358  	if c.sendRawErr == nil && c.sendRawHash == nil {
   359  		h := tx.TxHash()
   360  		return &h, nil
   361  	}
   362  	return c.sendRawHash, c.sendRawErr
   363  }
   364  
   365  func (c *tRPCClient) GetTxOut(_ context.Context, txHash *chainhash.Hash, vout uint32, tree int8, mempool bool) (*chainjson.GetTxOutResult, error) {
   366  	return c.txOutRes[newOutPoint(txHash, vout)], c.txOutErr
   367  }
   368  
   369  func (c *tRPCClient) GetBestBlock(_ context.Context) (*chainhash.Hash, int64, error) {
   370  	if c.bestBlockErr != nil {
   371  		return nil, -1, c.bestBlockErr
   372  	}
   373  	bestHash, bestBlkHeight := c.getBestBlock()
   374  	return bestHash, bestBlkHeight, nil
   375  }
   376  
   377  func (c *tRPCClient) getBestBlock() (*chainhash.Hash, int64) {
   378  	c.blockchain.mtx.RLock()
   379  	defer c.blockchain.mtx.RUnlock()
   380  	var bestHash *chainhash.Hash
   381  	var bestBlkHeight int64
   382  	for height, hash := range c.blockchain.mainchain {
   383  		if height >= bestBlkHeight {
   384  			bestBlkHeight = height
   385  			bestHash = hash
   386  		}
   387  	}
   388  	return bestHash, bestBlkHeight
   389  }
   390  
   391  func (c *tRPCClient) GetBlockHash(_ context.Context, blockHeight int64) (*chainhash.Hash, error) {
   392  	c.blockchain.mtx.RLock()
   393  	defer c.blockchain.mtx.RUnlock()
   394  	h, found := c.blockchain.mainchain[blockHeight]
   395  	if !found {
   396  		return nil, fmt.Errorf("no test block at height %d", blockHeight)
   397  	}
   398  	return h, nil
   399  }
   400  
   401  func (c *tRPCClient) GetBlock(_ context.Context, blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
   402  	c.blockchain.mtx.RLock()
   403  	defer c.blockchain.mtx.RUnlock()
   404  	blk, found := c.blockchain.verboseBlocks[*blockHash]
   405  	if !found {
   406  		return nil, fmt.Errorf("no test block found for %s", blockHash)
   407  	}
   408  	return blk, nil
   409  }
   410  
   411  func (c *tRPCClient) GetBlockHeaderVerbose(_ context.Context, blockHash *chainhash.Hash) (*chainjson.GetBlockHeaderVerboseResult, error) {
   412  	c.blockchain.mtx.RLock()
   413  	defer c.blockchain.mtx.RUnlock()
   414  	hdr, found := c.blockchain.blockHeaders[*blockHash]
   415  	if !found {
   416  		return nil, fmt.Errorf("no test block header found for %s", blockHash)
   417  	}
   418  	return &chainjson.GetBlockHeaderVerboseResult{
   419  		Height:        hdr.Height,
   420  		Hash:          blockHash.String(),
   421  		PreviousHash:  hdr.PrevBlock.String(),
   422  		Confirmations: 1,  // just not -1, which indicates side chain
   423  		NextHash:      "", // empty string signals that it is tip
   424  	}, nil
   425  }
   426  
   427  func (c *tRPCClient) GetBlockHeader(_ context.Context, blockHash *chainhash.Hash) (*wire.BlockHeader, error) {
   428  	c.blockchain.mtx.RLock()
   429  	defer c.blockchain.mtx.RUnlock()
   430  	hdr, found := c.blockchain.blockHeaders[*blockHash]
   431  	if !found {
   432  		return nil, fmt.Errorf("no test block header found for %s", blockHash)
   433  	}
   434  	return hdr, nil
   435  }
   436  
   437  func (c *tRPCClient) GetRawMempool(_ context.Context, txType chainjson.GetRawMempoolTxTypeCmd) ([]*chainhash.Hash, error) {
   438  	if c.mempoolErr != nil {
   439  		return nil, c.mempoolErr
   440  	}
   441  	c.blockchain.mtx.RLock()
   442  	defer c.blockchain.mtx.RUnlock()
   443  	txHashes := make([]*chainhash.Hash, 0)
   444  	for _, tx := range c.blockchain.rawTxs {
   445  		if tx.height < 0 {
   446  			txHash := tx.tx.TxHash()
   447  			txHashes = append(txHashes, &txHash)
   448  		}
   449  	}
   450  	return txHashes, nil
   451  }
   452  
   453  func (c *tRPCClient) GetBalanceMinConf(_ context.Context, account string, minConfirms int) (*walletjson.GetBalanceResult, error) {
   454  	return c.balanceResult, c.balanceErr
   455  }
   456  
   457  func (c *tRPCClient) LockUnspent(_ context.Context, unlock bool, ops []*wire.OutPoint) error {
   458  	if unlock == false {
   459  		if c.lockedCoins == nil {
   460  			c.lockedCoins = ops
   461  		} else {
   462  			c.lockedCoins = append(c.lockedCoins, ops...)
   463  		}
   464  	}
   465  	return c.lockUnspentErr
   466  }
   467  
   468  func (c *tRPCClient) GetRawChangeAddress(_ context.Context, account string, net stdaddr.AddressParams) (stdaddr.Address, error) {
   469  	return c.changeAddr, c.changeAddrErr
   470  }
   471  
   472  func (c *tRPCClient) GetNewAddressGapPolicy(_ context.Context, account string, gapPolicy dcrwallet.GapPolicy) (stdaddr.Address, error) {
   473  	return c.newAddr, c.newAddrErr
   474  }
   475  
   476  func (c *tRPCClient) DumpPrivKey(_ context.Context, address stdaddr.Address) (*dcrutil.WIF, error) {
   477  	return c.privWIF, c.privWIFErr
   478  }
   479  
   480  func (c *tRPCClient) GetTransaction(_ context.Context, txHash *chainhash.Hash) (*walletjson.GetTransactionResult, error) {
   481  	if c.walletTxFn != nil {
   482  		return c.walletTxFn()
   483  	}
   484  	c.blockchain.mtx.RLock()
   485  	defer c.blockchain.mtx.RUnlock()
   486  	if rawTx, has := c.blockchain.rawTxs[*txHash]; has {
   487  		b, err := rawTx.tx.Bytes()
   488  		if err != nil {
   489  			return nil, err
   490  		}
   491  		walletTx := &walletjson.GetTransactionResult{
   492  			Hex: hex.EncodeToString(b),
   493  		}
   494  		return walletTx, nil
   495  	}
   496  	return nil, dcrjson.NewRPCError(dcrjson.ErrRPCNoTxInfo, "no test transaction")
   497  }
   498  
   499  func (c *tRPCClient) AccountUnlocked(_ context.Context, acct string) (*walletjson.AccountUnlockedResult, error) {
   500  	return &walletjson.AccountUnlockedResult{}, nil // go the walletlock/walletpassphrase route
   501  }
   502  
   503  func (c *tRPCClient) LockAccount(_ context.Context, acct string) error       { return nil }
   504  func (c *tRPCClient) UnlockAccount(_ context.Context, acct, pw string) error { return nil }
   505  
   506  func (c *tRPCClient) WalletLock(_ context.Context) error {
   507  	return c.lockErr
   508  }
   509  
   510  func (c *tRPCClient) WalletPassphrase(_ context.Context, passphrase string, timeoutSecs int64) error {
   511  	return c.passErr
   512  }
   513  
   514  func (c *tRPCClient) WalletInfo(_ context.Context) (*walletjson.WalletInfoResult, error) {
   515  	return &walletjson.WalletInfoResult{
   516  		Unlocked: true,
   517  	}, nil
   518  }
   519  
   520  func (c *tRPCClient) ValidateAddress(_ context.Context, address stdaddr.Address) (*walletjson.ValidateAddressWalletResult, error) {
   521  	if c.validateAddress != nil {
   522  		if c.validateAddress[address.String()] != nil {
   523  			return c.validateAddress[address.String()], nil
   524  		}
   525  		return &walletjson.ValidateAddressWalletResult{}, nil
   526  	}
   527  
   528  	return &walletjson.ValidateAddressWalletResult{
   529  		IsMine:  true,
   530  		Account: tAcctName,
   531  	}, nil
   532  }
   533  
   534  func (c *tRPCClient) Disconnected() bool {
   535  	return c.disconnected
   536  }
   537  
   538  func (c *tRPCClient) GetStakeInfo(ctx context.Context) (*walletjson.GetStakeInfoResult, error) {
   539  	return &c.stakeInfo, nil
   540  }
   541  
   542  func (c *tRPCClient) PurchaseTicket(ctx context.Context, fromAccount string, minConf *int,
   543  	numTickets *int,
   544  	expiry *int, ticketChange *bool, ticketFee *dcrutil.Amount) (tix []*chainhash.Hash, _ error) {
   545  
   546  	if c.purchaseTicketsErr != nil {
   547  		return nil, c.purchaseTicketsErr
   548  	}
   549  
   550  	if len(c.purchasedTickets) > 0 {
   551  		tix = c.purchasedTickets[0]
   552  		c.purchasedTickets = c.purchasedTickets[1:]
   553  	}
   554  
   555  	return tix, nil
   556  }
   557  
   558  func (c *tRPCClient) GetTickets(ctx context.Context, includeImmature bool) ([]*chainhash.Hash, error) {
   559  	return nil, nil
   560  }
   561  
   562  func (c *tRPCClient) GetVoteChoices(ctx context.Context) (*walletjson.GetVoteChoicesResult, error) {
   563  	return nil, nil
   564  }
   565  
   566  func (c *tRPCClient) SetVoteChoice(ctx context.Context, agendaID, choiceID string) error {
   567  	return nil
   568  }
   569  
   570  func (c *tRPCClient) RawRequest(_ context.Context, method string, params []json.RawMessage) (json.RawMessage, error) {
   571  	if rr, found := c.rawRes[method]; found {
   572  		return rr, c.rawErr[method] // err probably should be nil, but respect the config
   573  	}
   574  	if re, found := c.rawErr[method]; found {
   575  		return nil, re
   576  	}
   577  
   578  	switch method {
   579  	case methodGetPeerInfo:
   580  		return json.Marshal([]*walletjson.GetPeerInfoResult{
   581  			{
   582  				Addr: "127.0.0.1",
   583  			},
   584  		})
   585  	case methodGetCFilterV2:
   586  		if len(params) != 1 {
   587  			return nil, fmt.Errorf("getcfilterv2 requires 1 param, got %d", len(params))
   588  		}
   589  
   590  		var hashStr string
   591  		json.Unmarshal(params[0], &hashStr)
   592  		blkHash, _ := chainhash.NewHashFromStr(hashStr)
   593  
   594  		c.blockchain.mtx.RLock()
   595  		defer c.blockchain.mtx.RUnlock()
   596  		blockFilterBuilder := c.blockchain.v2CFilterBuilders[*blkHash]
   597  		if blockFilterBuilder == nil {
   598  			return nil, fmt.Errorf("cfilters builder not found for block %s", blkHash)
   599  		}
   600  		v2CFilter, err := blockFilterBuilder.build()
   601  		if err != nil {
   602  			return nil, err
   603  		}
   604  		res := &walletjson.GetCFilterV2Result{
   605  			BlockHash: blkHash.String(),
   606  			Filter:    hex.EncodeToString(v2CFilter.Bytes()),
   607  			Key:       hex.EncodeToString(blockFilterBuilder.key[:]),
   608  		}
   609  		return json.Marshal(res)
   610  
   611  	case methodListUnspent:
   612  		if c.unspentErr != nil {
   613  			return nil, c.unspentErr
   614  		}
   615  
   616  		var acct string
   617  		if len(params) > 3 {
   618  			// filter with provided acct param
   619  			_ = json.Unmarshal(params[3], &acct)
   620  		}
   621  		allAccts := acct == "" || acct == "*"
   622  
   623  		var unspents []walletjson.ListUnspentResult
   624  		for _, unspent := range c.unspent {
   625  			if allAccts || unspent.Account == acct {
   626  				unspents = append(unspents, unspent)
   627  			}
   628  		}
   629  
   630  		response, _ := json.Marshal(unspents)
   631  		return response, nil
   632  
   633  	case methodListLockUnspent:
   634  		if c.listLockedErr != nil {
   635  			return nil, c.listLockedErr
   636  		}
   637  
   638  		var acct string
   639  		if len(params) > 0 {
   640  			_ = json.Unmarshal(params[0], &acct)
   641  		}
   642  		allAccts := acct == "" || acct == "*"
   643  
   644  		var locked []chainjson.TransactionInput
   645  		for _, utxo := range c.lluCoins {
   646  			if allAccts || utxo.Account == acct {
   647  				locked = append(locked, chainjson.TransactionInput{
   648  					Txid:   utxo.TxID,
   649  					Amount: utxo.Amount,
   650  					Vout:   utxo.Vout,
   651  					Tree:   utxo.Tree,
   652  				})
   653  			}
   654  		}
   655  		response, _ := json.Marshal(locked)
   656  		return response, nil
   657  
   658  	case methodSignRawTransaction:
   659  		if len(params) != 1 {
   660  			return nil, fmt.Errorf("needed 1 param")
   661  		}
   662  
   663  		var msgTxHex string
   664  		err := json.Unmarshal(params[0], &msgTxHex)
   665  		if err != nil {
   666  			return nil, err
   667  		}
   668  
   669  		msgTx, err := msgTxFromHex(msgTxHex)
   670  		if err != nil {
   671  			res := walletjson.SignRawTransactionResult{
   672  				Hex: msgTxHex,
   673  				Errors: []walletjson.SignRawTransactionError{
   674  					{
   675  						TxID:  msgTx.CachedTxHash().String(),
   676  						Error: err.Error(),
   677  					},
   678  				},
   679  				// Complete stays false.
   680  			}
   681  			return json.Marshal(&res)
   682  		}
   683  
   684  		if c.signFunc == nil {
   685  			return nil, fmt.Errorf("no signFunc configured")
   686  		}
   687  
   688  		signedTx, complete, err := c.signFunc(msgTx)
   689  		if err != nil {
   690  			res := walletjson.SignRawTransactionResult{
   691  				Hex: msgTxHex,
   692  				Errors: []walletjson.SignRawTransactionError{
   693  					{
   694  						TxID:  msgTx.CachedTxHash().String(),
   695  						Error: err.Error(),
   696  					},
   697  				},
   698  				// Complete stays false.
   699  			}
   700  			return json.Marshal(&res)
   701  		}
   702  
   703  		txHex, err := msgTxToHex(signedTx)
   704  		if err != nil {
   705  			return nil, fmt.Errorf("failed to encode MsgTx: %w", err)
   706  		}
   707  
   708  		res := walletjson.SignRawTransactionResult{
   709  			Hex:      txHex,
   710  			Complete: complete,
   711  		}
   712  		return json.Marshal(&res)
   713  
   714  	case methodWalletInfo:
   715  		return json.Marshal(new(walletjson.WalletInfoResult))
   716  	}
   717  
   718  	return nil, fmt.Errorf("method %v not implemented by (*tRPCClient).RawRequest", method)
   719  }
   720  
   721  func (c *tRPCClient) SetTxFee(ctx context.Context, fee dcrutil.Amount) error {
   722  	return nil
   723  }
   724  
   725  func (c *tRPCClient) GetReceivedByAddressMinConf(ctx context.Context, address stdaddr.Address, minConfs int) (dcrutil.Amount, error) {
   726  	return 0, nil
   727  }
   728  
   729  func (c *tRPCClient) ListSinceBlock(ctx context.Context, hash *chainhash.Hash) (*walletjson.ListSinceBlockResult, error) {
   730  	return nil, nil
   731  }
   732  
   733  func TestMain(m *testing.M) {
   734  	tChainParams = chaincfg.MainNetParams()
   735  	tPKHAddr, _ = stdaddr.DecodeAddress("DsTya4cCFBgtofDLiRhkyPYEQjgs3HnarVP", tChainParams)
   736  	tLogger = dex.StdOutLogger("TEST", dex.LevelTrace)
   737  	var shutdown func()
   738  	tCtx, shutdown = context.WithCancel(context.Background())
   739  	tTxHash, _ = chainhash.NewHashFromStr(tTxID)
   740  	tP2PKHScript, _ = hex.DecodeString("76a9148fc02268f208a61767504fe0b48d228641ba81e388ac")
   741  	// tP2SH, _ = hex.DecodeString("76a91412a9abf5c32392f38bd8a1f57d81b1aeecc5699588ac")
   742  	doIt := func() int {
   743  		// Not counted as coverage, must test Archiver constructor explicitly.
   744  		defer shutdown()
   745  		return m.Run()
   746  	}
   747  	os.Exit(doIt())
   748  }
   749  
   750  func TestMaxFundingFees(t *testing.T) {
   751  	wallet, _, shutdown := tNewWallet()
   752  	defer shutdown()
   753  
   754  	maxFeeRate := uint64(100)
   755  
   756  	useSplitOptions := map[string]string{
   757  		multiSplitKey: "true",
   758  	}
   759  	noSplitOptions := map[string]string{
   760  		multiSplitKey: "false",
   761  	}
   762  
   763  	maxFundingFees := wallet.MaxFundingFees(3, maxFeeRate, useSplitOptions)
   764  	expectedFees := maxFeeRate * (dexdcr.P2PKHInputSize*12 + dexdcr.P2PKHOutputSize*4 + dexdcr.MsgTxOverhead)
   765  	if maxFundingFees != expectedFees {
   766  		t.Fatalf("unexpected max funding fees. expected %d, got %d", expectedFees, maxFundingFees)
   767  	}
   768  
   769  	maxFundingFees = wallet.MaxFundingFees(3, maxFeeRate, noSplitOptions)
   770  	if maxFundingFees != 0 {
   771  		t.Fatalf("unexpected max funding fees. expected 0, got %d", maxFundingFees)
   772  	}
   773  }
   774  
   775  func TestAvailableFund(t *testing.T) {
   776  	wallet, node, shutdown := tNewWallet()
   777  	defer shutdown()
   778  
   779  	// With an empty list returned, there should be no error, but the value zero
   780  	// should be returned.
   781  	unspents := make([]walletjson.ListUnspentResult, 0)
   782  	node.unspent = unspents
   783  	balanceResult := &walletjson.GetBalanceResult{
   784  		Balances: []walletjson.GetAccountBalanceResult{
   785  			{
   786  				AccountName: tAcctName,
   787  			},
   788  		},
   789  	}
   790  	node.balanceResult = balanceResult
   791  	bal, err := wallet.Balance()
   792  	if err != nil {
   793  		t.Fatalf("error for zero utxos: %v", err)
   794  	}
   795  	if bal.Available != 0 {
   796  		t.Fatalf("expected available = 0, got %d", bal.Available)
   797  	}
   798  	if bal.Immature != 0 {
   799  		t.Fatalf("expected unconf = 0, got %d", bal.Immature)
   800  	}
   801  
   802  	var vout uint32
   803  	addUtxo := func(atomAmt uint64, confs int64, lock bool) {
   804  		utxo := walletjson.ListUnspentResult{
   805  			TxID:          tTxID,
   806  			Vout:          vout,
   807  			Address:       tPKHAddr.String(),
   808  			Account:       tAcctName,
   809  			Amount:        float64(atomAmt) / 1e8,
   810  			Confirmations: confs,
   811  			ScriptPubKey:  hex.EncodeToString(tP2PKHScript),
   812  			Spendable:     true,
   813  		}
   814  		if lock {
   815  			node.lluCoins = append(node.lluCoins, utxo)
   816  		} else {
   817  			unspents = append(unspents, utxo)
   818  			node.unspent = unspents
   819  		}
   820  		// update balance
   821  		balanceResult.Balances[0].Spendable += utxo.Amount
   822  		vout++
   823  	}
   824  
   825  	// Add 1 unspent output and check balance
   826  	var littleLots uint64 = 6
   827  	littleOrder := tLotSize * littleLots
   828  	littleFunds := calc.RequiredOrderFunds(littleOrder, dexdcr.P2PKHInputSize, littleLots, dexdcr.InitTxSizeBase, dexdcr.InitTxSize, tDCR.MaxFeeRate)
   829  	addUtxo(littleFunds, 0, false)
   830  	bal, err = wallet.Balance()
   831  	if err != nil {
   832  		t.Fatalf("error for 1 utxo: %v", err)
   833  	}
   834  	if bal.Available != littleFunds {
   835  		t.Fatalf("expected available = %d for confirmed utxos, got %d", littleFunds, bal.Available)
   836  	}
   837  	if bal.Immature != 0 {
   838  		t.Fatalf("expected immature = %d, got %d", 0, bal.Immature)
   839  	}
   840  	if bal.Locked != 0 {
   841  		t.Fatalf("expected locked = %d, got %d", 0, bal.Locked)
   842  	}
   843  
   844  	// Add a second utxo, lock it and check balance.
   845  	lockedBit := tLotSize * 2
   846  	addUtxo(lockedBit, 1, true)
   847  	bal, err = wallet.Balance()
   848  	if err != nil {
   849  		t.Fatalf("error for 2 utxos: %v", err)
   850  	}
   851  	// Available balance should exclude locked utxo amount.
   852  	if bal.Available != littleFunds {
   853  		t.Fatalf("expected available = %d for confirmed utxos, got %d", littleFunds, bal.Available)
   854  	}
   855  	if bal.Immature != 0 {
   856  		t.Fatalf("expected immature = %d, got %d", 0, bal.Immature)
   857  	}
   858  	if bal.Locked != lockedBit {
   859  		t.Fatalf("expected locked = %d, got %d", lockedBit, bal.Locked)
   860  	}
   861  
   862  	// Add a third utxo.
   863  	var lottaLots uint64 = 100
   864  	lottaOrder := tLotSize * 100
   865  	// Add funding for an extra input to accommodate the later combined tests.
   866  	lottaFunds := calc.RequiredOrderFunds(lottaOrder, 2*dexdcr.P2PKHInputSize, lottaLots, dexdcr.InitTxSizeBase, dexdcr.InitTxSize, tDCR.MaxFeeRate)
   867  	addUtxo(lottaFunds, 1, false)
   868  	bal, err = wallet.Balance()
   869  	if err != nil {
   870  		t.Fatalf("error for 3 utxos: %v", err)
   871  	}
   872  	if bal.Available != littleFunds+lottaFunds {
   873  		t.Fatalf("expected available = %d for 2 outputs, got %d", littleFunds+lottaFunds, bal.Available)
   874  	}
   875  	if bal.Immature != 0 {
   876  		t.Fatalf("expected unconf = 0 for 2 outputs, got %d", bal.Immature)
   877  	}
   878  	// locked balance should remain same as utxo2 amount.
   879  	if bal.Locked != lockedBit {
   880  		t.Fatalf("expected locked = %d, got %d", lockedBit, bal.Locked)
   881  	}
   882  
   883  	ord := &asset.Order{
   884  		Version:       version,
   885  		Value:         0,
   886  		MaxSwapCount:  1,
   887  		MaxFeeRate:    tDCR.MaxFeeRate,
   888  		FeeSuggestion: feeSuggestion,
   889  	}
   890  
   891  	setOrderValue := func(v uint64) {
   892  		ord.Value = v
   893  		ord.MaxSwapCount = v / tLotSize
   894  	}
   895  
   896  	// Zero value
   897  	_, _, _, err = wallet.FundOrder(ord)
   898  	if err == nil {
   899  		t.Fatalf("no funding error for zero value")
   900  	}
   901  
   902  	// Nothing to spend
   903  	node.unspent = nil
   904  	setOrderValue(littleOrder)
   905  	_, _, _, err = wallet.FundOrder(ord)
   906  	if err == nil {
   907  		t.Fatalf("no error for zero utxos")
   908  	}
   909  	node.unspent = unspents
   910  
   911  	// RPC error
   912  	node.unspentErr = tErr
   913  	_, _, _, err = wallet.FundOrder(ord)
   914  	if err == nil {
   915  		t.Fatalf("no funding error for rpc error")
   916  	}
   917  	node.unspentErr = nil
   918  
   919  	// Negative response when locking outputs.
   920  	node.lockUnspentErr = tErr
   921  	_, _, _, err = wallet.FundOrder(ord)
   922  	if err == nil {
   923  		t.Fatalf("no error for lockunspent result = false: %v", err)
   924  	}
   925  	node.lockUnspentErr = nil
   926  
   927  	// Fund a little bit, but small output is unconfirmed.
   928  	spendables, _, _, err := wallet.FundOrder(ord)
   929  	if err != nil {
   930  		t.Fatalf("error funding small amount: %v", err)
   931  	}
   932  	if len(spendables) != 1 {
   933  		t.Fatalf("expected 1 spendable, got %d", len(spendables))
   934  	}
   935  	v := spendables[0].Value()
   936  	if v != lottaFunds {
   937  		t.Fatalf("expected spendable of value %d, got %d", lottaFunds, v)
   938  	}
   939  
   940  	// Now confirm the little bit and have it selected.
   941  	unspents[0].Confirmations++
   942  	spendables, _, fees, err := wallet.FundOrder(ord)
   943  	if err != nil {
   944  		t.Fatalf("error funding small amount: %v", err)
   945  	}
   946  	if len(spendables) != 1 {
   947  		t.Fatalf("expected 1 spendable, got %d", len(spendables))
   948  	}
   949  	if fees != 0 {
   950  		t.Fatalf("expected zero fees, got %d", fees)
   951  	}
   952  	v = spendables[0].Value()
   953  	if v != littleFunds {
   954  		t.Fatalf("expected spendable of value %d, got %d", littleFunds, v)
   955  	}
   956  
   957  	// Fund a lotta bit.
   958  	setOrderValue(lottaOrder)
   959  	spendables, _, _, err = wallet.FundOrder(ord)
   960  	if err != nil {
   961  		t.Fatalf("error funding large amount: %v", err)
   962  	}
   963  	if len(spendables) != 1 {
   964  		t.Fatalf("expected 1 spendable, got %d", len(spendables))
   965  	}
   966  	if fees != 0 {
   967  		t.Fatalf("expected zero fees, got %d", fees)
   968  	}
   969  	v = spendables[0].Value()
   970  	if v != lottaFunds {
   971  		t.Fatalf("expected spendable of value %d, got %d", lottaFunds, v)
   972  	}
   973  
   974  	extraLottaOrder := littleOrder + lottaOrder
   975  	extraLottaLots := littleLots + lottaLots
   976  	// Prepare for a split transaction.
   977  	baggageFees := tDCR.MaxFeeRate * splitTxBaggage
   978  	node.newAddr = tPKHAddr
   979  	node.changeAddr = tPKHAddr
   980  	wallet.config().useSplitTx = true
   981  	// No split performed due to economics is not an error.
   982  	setOrderValue(extraLottaOrder)
   983  	coins, _, _, err := wallet.FundOrder(ord)
   984  	if err != nil {
   985  		t.Fatalf("error for no-split split: %v", err)
   986  	}
   987  	if fees != 0 {
   988  		t.Fatalf("expected zero fees, got %d", fees)
   989  	}
   990  	// Should be both coins.
   991  	if len(coins) != 2 {
   992  		t.Fatalf("no-split split didn't return both coins")
   993  	}
   994  
   995  	// Not enough to cover transaction fees.
   996  	tweak := float64(littleFunds+lottaFunds-calc.RequiredOrderFunds(extraLottaOrder, 2*dexdcr.P2PKHInputSize, extraLottaLots, dexdcr.InitTxSizeBase, dexdcr.InitTxSize, tDCR.MaxFeeRate)+1) / 1e8
   997  	node.unspent[0].Amount -= tweak
   998  	setOrderValue(extraLottaOrder)
   999  	_, _, _, err = wallet.FundOrder(ord)
  1000  	if err == nil {
  1001  		t.Fatalf("no error when not enough to cover tx fees")
  1002  	}
  1003  	if fees != 0 {
  1004  		t.Fatalf("expected zero fees, got %d", fees)
  1005  	}
  1006  
  1007  	node.unspent[0].Amount += tweak
  1008  
  1009  	// No split because not standing order.
  1010  	ord.Immediate = true
  1011  	coins, _, _, err = wallet.FundOrder(ord)
  1012  	if err != nil {
  1013  		t.Fatalf("error for no-split split: %v", err)
  1014  	}
  1015  	if fees != 0 {
  1016  		t.Fatalf("expected zero fees, got %d", fees)
  1017  	}
  1018  	ord.Immediate = false
  1019  	if len(coins) != 2 {
  1020  		t.Fatalf("no-split split didn't return both coins")
  1021  	}
  1022  
  1023  	// With a little more locked, the split should be performed.
  1024  	node.unspent[1].Amount += float64(baggageFees) / 1e8
  1025  	coins, _, fees, err = wallet.FundOrder(ord)
  1026  	if err != nil {
  1027  		t.Fatalf("error for split tx: %v", err)
  1028  	}
  1029  
  1030  	inputSize := dexdcr.P2PKHInputSize - dexdcr.P2PKHSigScriptSize // no sig script
  1031  	splitTxSize := dexdcr.MsgTxOverhead + (2 * inputSize) + 2*dexdcr.P2PKHOutputSize
  1032  	expectedFees := uint64(splitTxSize) * feeSuggestion
  1033  	if fees != expectedFees {
  1034  		t.Fatalf("expected fees of %d, got %d", expectedFees, fees)
  1035  	}
  1036  
  1037  	// Should be just one coin.
  1038  	if len(coins) != 1 {
  1039  		t.Fatalf("split failed - coin count != 1")
  1040  	}
  1041  	if node.sentRawTx == nil {
  1042  		t.Fatalf("split failed - no tx sent")
  1043  	}
  1044  
  1045  	// Hit some error paths.
  1046  
  1047  	// GetNewAddressGapPolicy error
  1048  	node.newAddrErr = tErr
  1049  	_, _, _, err = wallet.FundOrder(ord)
  1050  	if err == nil {
  1051  		t.Fatalf("no error for split tx change addr error")
  1052  	}
  1053  	node.newAddrErr = nil
  1054  
  1055  	// GetRawChangeAddress error
  1056  	node.changeAddrErr = tErr
  1057  	_, _, _, err = wallet.FundOrder(ord)
  1058  	if err == nil {
  1059  		t.Fatalf("no error for split tx change addr error")
  1060  	}
  1061  	node.changeAddrErr = nil
  1062  
  1063  	// SendRawTx error
  1064  	node.sendRawErr = tErr
  1065  	_, _, _, err = wallet.FundOrder(ord)
  1066  	if err == nil {
  1067  		t.Fatalf("no error for split tx send error")
  1068  	}
  1069  	node.sendRawErr = nil
  1070  
  1071  	// Success again.
  1072  	_, _, _, err = wallet.FundOrder(ord)
  1073  	if err != nil {
  1074  		t.Fatalf("error for split tx recovery run")
  1075  	}
  1076  
  1077  	// Not enough funds, because littleUnspent is a different account.
  1078  	unspents[0].Account = "wrong account"
  1079  	setOrderValue(extraLottaOrder)
  1080  	_, _, _, err = wallet.FundOrder(ord)
  1081  	if err == nil {
  1082  		t.Fatalf("no error for wrong account")
  1083  	}
  1084  	node.unspent[0].Account = tAcctName
  1085  
  1086  	// Place locked utxo in different account, check locked balance.
  1087  	node.lluCoins[0].Account = "wrong account"
  1088  	bal, err = wallet.Balance()
  1089  	if err != nil {
  1090  		t.Fatalf("error for 3 utxos, with locked utxo in wrong acct: %v", err)
  1091  	}
  1092  	if bal.Locked != 0 {
  1093  		t.Fatalf("expected locked = %d, got %d", 0, bal.Locked)
  1094  	}
  1095  }
  1096  
  1097  // Since ReturnCoins takes the wallet.Coin interface, make sure any interface
  1098  // is acceptable.
  1099  type tCoin struct{ id []byte }
  1100  
  1101  func (c *tCoin) ID() dex.Bytes {
  1102  	if len(c.id) > 0 {
  1103  		return c.id
  1104  	}
  1105  	return make([]byte, 36)
  1106  }
  1107  func (c *tCoin) String() string                                    { return hex.EncodeToString(c.id) }
  1108  func (c *tCoin) Value() uint64                                     { return 100 }
  1109  func (c *tCoin) Confirmations(ctx context.Context) (uint32, error) { return 2, nil }
  1110  func (c *tCoin) TxID() string                                      { return hex.EncodeToString(c.id) }
  1111  
  1112  func TestReturnCoins(t *testing.T) {
  1113  	wallet, node, shutdown := tNewWallet()
  1114  	defer shutdown()
  1115  
  1116  	// Test it with the local output type.
  1117  	coins := asset.Coins{
  1118  		newOutput(tTxHash, 0, 1, wire.TxTreeRegular),
  1119  	}
  1120  	err := wallet.ReturnCoins(coins)
  1121  	if err != nil {
  1122  		t.Fatalf("error with output type coins: %v", err)
  1123  	}
  1124  
  1125  	// Should error for no coins.
  1126  	err = wallet.ReturnCoins(asset.Coins{})
  1127  	if err == nil {
  1128  		t.Fatalf("no error for zero coins")
  1129  	}
  1130  
  1131  	// nil unlocks all
  1132  	wallet.fundingCoins[outPoint{*tTxHash, 0}] = &fundingCoin{}
  1133  	err = wallet.ReturnCoins(nil)
  1134  	if err != nil {
  1135  		t.Fatalf("error for nil coins: %v", err)
  1136  	}
  1137  	if len(wallet.fundingCoins) != 0 {
  1138  		t.Errorf("all funding coins not unlocked")
  1139  	}
  1140  
  1141  	// Have the RPC return negative response.
  1142  	node.lockUnspentErr = tErr
  1143  	err = wallet.ReturnCoins(coins)
  1144  	if err == nil {
  1145  		t.Fatalf("no error for RPC failure")
  1146  	}
  1147  	node.lockUnspentErr = nil
  1148  
  1149  	// ReturnCoins should accept any type that implements wallet.Coin.
  1150  
  1151  	// Test the convertCoin method while we're here.
  1152  	badID := []byte{0x01, 0x02}
  1153  	badCoins := asset.Coins{&tCoin{id: badID}, &tCoin{id: badID}}
  1154  
  1155  	err = wallet.ReturnCoins(badCoins)
  1156  	if err == nil {
  1157  		t.Fatalf("no error for bad coins")
  1158  	}
  1159  
  1160  	coinID := toCoinID(tTxHash, 0)
  1161  	coins = asset.Coins{&tCoin{id: coinID}, &tCoin{id: coinID}}
  1162  	err = wallet.ReturnCoins(coins)
  1163  	if err != nil {
  1164  		t.Fatalf("error with custom coin type: %v", err)
  1165  	}
  1166  }
  1167  
  1168  func TestFundingCoins(t *testing.T) {
  1169  	wallet, node, shutdown := tNewWallet()
  1170  	defer shutdown()
  1171  
  1172  	vout := uint32(123)
  1173  	coinID := toCoinID(tTxHash, vout)
  1174  	p2pkhUnspent := walletjson.ListUnspentResult{
  1175  		TxID:      tTxID,
  1176  		Vout:      vout,
  1177  		Address:   tPKHAddr.String(),
  1178  		Spendable: true,
  1179  		Account:   tAcctName,
  1180  	}
  1181  
  1182  	node.unspent = []walletjson.ListUnspentResult{p2pkhUnspent}
  1183  	coinIDs := []dex.Bytes{coinID}
  1184  
  1185  	ensureGood := func() {
  1186  		t.Helper()
  1187  		coins, err := wallet.FundingCoins(coinIDs)
  1188  		if err != nil {
  1189  			t.Fatalf("FundingCoins error: %v", err)
  1190  		}
  1191  		if len(coins) != 1 {
  1192  			t.Fatalf("expected 1 coin, got %d", len(coins))
  1193  		}
  1194  	}
  1195  
  1196  	// Check initial success.
  1197  	ensureGood()
  1198  
  1199  	// Clear the RPC coins, but add a coin to the cache.
  1200  	node.unspent = nil
  1201  	opID := newOutPoint(tTxHash, vout)
  1202  	wallet.fundingCoins[opID] = &fundingCoin{
  1203  		op: newOutput(tTxHash, vout, 0, 0),
  1204  	}
  1205  	ensureGood()
  1206  
  1207  	ensureErr := func(tag string) {
  1208  		_, err := wallet.FundingCoins(coinIDs)
  1209  		if err == nil {
  1210  			t.Fatalf("%s: no error", tag)
  1211  		}
  1212  	}
  1213  
  1214  	// No coins
  1215  	delete(wallet.fundingCoins, opID)
  1216  	ensureErr("no coins")
  1217  	node.unspent = []walletjson.ListUnspentResult{p2pkhUnspent}
  1218  
  1219  	// Bad coin ID
  1220  	ogIDs := coinIDs
  1221  	coinIDs = []dex.Bytes{randBytes(35)}
  1222  	ensureErr("bad coin ID")
  1223  	coinIDs = ogIDs
  1224  
  1225  	// listunspent error
  1226  	node.unspentErr = tErr
  1227  	ensureErr("listunpent")
  1228  	node.unspentErr = nil
  1229  
  1230  	ensureGood()
  1231  }
  1232  
  1233  func checkMaxOrder(t *testing.T, wallet *ExchangeWallet, lots, swapVal, maxFees, estWorstCase, estBestCase uint64) {
  1234  	t.Helper()
  1235  	_, maxOrder, err := wallet.maxOrder(tLotSize, feeSuggestion, tDCR.MaxFeeRate)
  1236  	if err != nil {
  1237  		t.Fatalf("MaxOrder error: %v", err)
  1238  	}
  1239  	checkSwapEstimate(t, maxOrder, lots, swapVal, maxFees, estWorstCase, estBestCase)
  1240  }
  1241  
  1242  func checkSwapEstimate(t *testing.T, est *asset.SwapEstimate, lots, swapVal, maxFees, estWorstCase, estBestCase uint64) {
  1243  	t.Helper()
  1244  	if est.Lots != lots {
  1245  		t.Fatalf("MaxOrder has wrong Lots. wanted %d, got %d", lots, est.Lots)
  1246  	}
  1247  	if est.Value != swapVal {
  1248  		t.Fatalf("est has wrong Value. wanted %d, got %d", swapVal, est.Value)
  1249  	}
  1250  	if est.MaxFees != maxFees {
  1251  		t.Fatalf("est has wrong MaxFees. wanted %d, got %d", maxFees, est.MaxFees)
  1252  	}
  1253  	if est.RealisticWorstCase != estWorstCase {
  1254  		t.Fatalf("MaxOrder has wrong RealisticWorstCase. wanted %d, got %d", estWorstCase, est.RealisticWorstCase)
  1255  	}
  1256  	if est.RealisticBestCase != estBestCase {
  1257  		t.Fatalf("MaxOrder has wrong RealisticBestCase. wanted %d, got %d", estBestCase, est.RealisticBestCase)
  1258  	}
  1259  }
  1260  
  1261  func TestFundMultiOrder(t *testing.T) {
  1262  	wallet, node, shutdown := tNewWallet()
  1263  	defer shutdown()
  1264  
  1265  	maxFeeRate := uint64(80)
  1266  	feeSuggestion := uint64(60)
  1267  
  1268  	txIDs := make([]string, 0, 5)
  1269  	txHashes := make([]chainhash.Hash, 0, 5)
  1270  
  1271  	addresses := []string{
  1272  		tPKHAddr.String(),
  1273  		tPKHAddr.String(),
  1274  		tPKHAddr.String(),
  1275  		tPKHAddr.String(),
  1276  	}
  1277  	scriptPubKeys := []string{
  1278  		hex.EncodeToString(tP2PKHScript),
  1279  		hex.EncodeToString(tP2PKHScript),
  1280  		hex.EncodeToString(tP2PKHScript),
  1281  		hex.EncodeToString(tP2PKHScript),
  1282  	}
  1283  	for i := 0; i < 5; i++ {
  1284  		txIDs = append(txIDs, hex.EncodeToString(encode.RandomBytes(32)))
  1285  		h, _ := chainhash.NewHashFromStr(txIDs[i])
  1286  		txHashes = append(txHashes, *h)
  1287  	}
  1288  
  1289  	expectedSplitFee := func(numInputs, numOutputs uint64) uint64 {
  1290  		inputSize := uint64(dexdcr.P2PKHInputSize)
  1291  		outputSize := uint64(dexdcr.P2PKHOutputSize)
  1292  		return (dexdcr.MsgTxOverhead + numInputs*inputSize + numOutputs*outputSize) * feeSuggestion
  1293  	}
  1294  
  1295  	requiredForOrder := func(value, maxSwapCount uint64) int64 {
  1296  		inputSize := uint64(dexdcr.P2PKHInputSize)
  1297  		return int64(calc.RequiredOrderFunds(value, inputSize, maxSwapCount,
  1298  			dexdcr.InitTxSizeBase, dexdcr.InitTxSize, maxFeeRate))
  1299  	}
  1300  
  1301  	type test struct {
  1302  		name         string
  1303  		multiOrder   *asset.MultiOrder
  1304  		allOrNothing bool
  1305  		maxLock      uint64
  1306  		utxos        []walletjson.ListUnspentResult
  1307  		bondReserves uint64
  1308  		balance      uint64
  1309  
  1310  		// if expectedCoins is nil, all the coins are from
  1311  		// the split output. If any of the coins are nil,
  1312  		// than that output is from the split output.
  1313  		expectedCoins         []asset.Coins
  1314  		expectedRedeemScripts [][]dex.Bytes
  1315  		expectSendRawTx       bool
  1316  		expectedSplitFee      uint64
  1317  		expectedInputs        []*wire.TxIn
  1318  		expectedOutputs       []*wire.TxOut
  1319  		expectedChange        uint64
  1320  		expectedLockedCoins   []*wire.OutPoint
  1321  		expectErr             bool
  1322  	}
  1323  
  1324  	tests := []*test{
  1325  		{ // "split not allowed, utxos like split previously done"
  1326  			name: "split not allowed, utxos like split previously done",
  1327  			multiOrder: &asset.MultiOrder{
  1328  				Values: []*asset.MultiOrderValue{
  1329  					{
  1330  						Value:        1e6,
  1331  						MaxSwapCount: 1,
  1332  					},
  1333  					{
  1334  						Value:        2e6,
  1335  						MaxSwapCount: 2,
  1336  					},
  1337  				},
  1338  				MaxFeeRate:    maxFeeRate,
  1339  				FeeSuggestion: feeSuggestion,
  1340  				Options: map[string]string{
  1341  					"swapsplit": "false",
  1342  				},
  1343  			},
  1344  			utxos: []walletjson.ListUnspentResult{
  1345  				{
  1346  					Confirmations: 1,
  1347  					Spendable:     true,
  1348  					TxID:          txIDs[0],
  1349  					Account:       tAcctName,
  1350  					ScriptPubKey:  scriptPubKeys[0],
  1351  					Address:       addresses[0],
  1352  					Amount:        19e5 / 1e8,
  1353  					Vout:          0,
  1354  				},
  1355  				{
  1356  					Confirmations: 1,
  1357  					Spendable:     true,
  1358  					TxID:          txIDs[1],
  1359  					Account:       tAcctName,
  1360  					ScriptPubKey:  scriptPubKeys[1],
  1361  					Address:       addresses[1],
  1362  					Amount:        35e5 / 1e8,
  1363  					Vout:          0,
  1364  				},
  1365  			},
  1366  			balance: 35e5,
  1367  			expectedCoins: []asset.Coins{
  1368  				{newOutput(&txHashes[0], 0, 19e5, wire.TxTreeRegular)},
  1369  				{newOutput(&txHashes[1], 0, 35e5, wire.TxTreeRegular)},
  1370  			},
  1371  			expectedRedeemScripts: [][]dex.Bytes{
  1372  				{nil},
  1373  				{nil},
  1374  			},
  1375  		},
  1376  		{ // "split not allowed, require multiple utxos per order"
  1377  			name: "split not allowed, require multiple utxos per order",
  1378  			multiOrder: &asset.MultiOrder{
  1379  				Values: []*asset.MultiOrderValue{
  1380  					{
  1381  						Value:        1e6,
  1382  						MaxSwapCount: 1,
  1383  					},
  1384  					{
  1385  						Value:        2e6,
  1386  						MaxSwapCount: 2,
  1387  					},
  1388  				},
  1389  				MaxFeeRate:    maxFeeRate,
  1390  				FeeSuggestion: feeSuggestion,
  1391  				Options: map[string]string{
  1392  					"swapsplit": "false",
  1393  				},
  1394  			},
  1395  			utxos: []walletjson.ListUnspentResult{
  1396  				{
  1397  					Confirmations: 1,
  1398  					Spendable:     true,
  1399  					TxID:          txIDs[0],
  1400  					Account:       tAcctName,
  1401  					ScriptPubKey:  scriptPubKeys[0],
  1402  					Address:       addresses[0],
  1403  					Amount:        6e5 / 1e8,
  1404  					Vout:          0,
  1405  				},
  1406  				{
  1407  					Confirmations: 1,
  1408  					Spendable:     true,
  1409  					TxID:          txIDs[1],
  1410  					Account:       tAcctName,
  1411  					ScriptPubKey:  scriptPubKeys[1],
  1412  					Address:       addresses[1],
  1413  					Amount:        5e5 / 1e8,
  1414  					Vout:          0,
  1415  				},
  1416  				{
  1417  					Confirmations: 1,
  1418  					Spendable:     true,
  1419  					TxID:          txIDs[2],
  1420  					Account:       tAcctName,
  1421  					ScriptPubKey:  scriptPubKeys[2],
  1422  					Address:       addresses[2],
  1423  					Amount:        22e5 / 1e8,
  1424  					Vout:          0,
  1425  				},
  1426  			},
  1427  			balance: 33e5,
  1428  			expectedCoins: []asset.Coins{
  1429  				{newOutput(&txHashes[0], 0, 6e5, wire.TxTreeRegular), newOutput(&txHashes[1], 0, 5e5, wire.TxTreeRegular)},
  1430  				{newOutput(&txHashes[2], 0, 22e5, wire.TxTreeRegular)},
  1431  			},
  1432  			expectedRedeemScripts: [][]dex.Bytes{
  1433  				{nil, nil},
  1434  				{nil},
  1435  			},
  1436  			expectedLockedCoins: []*wire.OutPoint{
  1437  				wire.NewOutPoint(&txHashes[0], 0, wire.TxTreeRegular),
  1438  				wire.NewOutPoint(&txHashes[1], 0, wire.TxTreeRegular),
  1439  				wire.NewOutPoint(&txHashes[2], 0, wire.TxTreeRegular),
  1440  			},
  1441  		},
  1442  		{ // "split not allowed, can only fund first order and respect maxLock"
  1443  			name: "split not allowed, can only fund first order and respect maxLock",
  1444  			multiOrder: &asset.MultiOrder{
  1445  				Values: []*asset.MultiOrderValue{
  1446  					{
  1447  						Value:        1e6,
  1448  						MaxSwapCount: 1,
  1449  					},
  1450  					{
  1451  						Value:        2e6,
  1452  						MaxSwapCount: 2,
  1453  					},
  1454  				},
  1455  				MaxFeeRate:    maxFeeRate,
  1456  				FeeSuggestion: feeSuggestion,
  1457  				Options: map[string]string{
  1458  					"swapsplit": "false",
  1459  				},
  1460  			},
  1461  			maxLock: 32e5,
  1462  			utxos: []walletjson.ListUnspentResult{
  1463  				{
  1464  					Confirmations: 1,
  1465  					Spendable:     true,
  1466  					TxID:          txIDs[2],
  1467  					Account:       tAcctName,
  1468  					ScriptPubKey:  scriptPubKeys[2],
  1469  					Address:       addresses[2],
  1470  					Amount:        1e6 / 1e8,
  1471  					Vout:          0,
  1472  				},
  1473  				{
  1474  					Confirmations: 1,
  1475  					Spendable:     true,
  1476  					TxID:          txIDs[0],
  1477  					Account:       tAcctName,
  1478  					ScriptPubKey:  scriptPubKeys[0],
  1479  					Address:       addresses[0],
  1480  					Amount:        11e5 / 1e8,
  1481  					Vout:          0,
  1482  				},
  1483  				{
  1484  					Confirmations: 1,
  1485  					Spendable:     true,
  1486  					TxID:          txIDs[1],
  1487  					Account:       tAcctName,
  1488  					ScriptPubKey:  scriptPubKeys[1],
  1489  					Address:       addresses[1],
  1490  					Amount:        25e5 / 1e8,
  1491  					Vout:          0,
  1492  				},
  1493  			},
  1494  			balance: 46e5,
  1495  			expectedCoins: []asset.Coins{
  1496  				{newOutput(&txHashes[0], 0, 11e5, wire.TxTreeRegular)},
  1497  			},
  1498  			expectedRedeemScripts: [][]dex.Bytes{
  1499  				{nil},
  1500  			},
  1501  			expectedLockedCoins: []*wire.OutPoint{
  1502  				wire.NewOutPoint(&txHashes[0], 0, wire.TxTreeRegular),
  1503  			},
  1504  		},
  1505  		{ // "split not allowed, can only fund first order and respect bond reserves"
  1506  			name: "no split allowed, can only fund first order and respect bond reserves",
  1507  			multiOrder: &asset.MultiOrder{
  1508  				Values: []*asset.MultiOrderValue{
  1509  					{
  1510  						Value:        1e6,
  1511  						MaxSwapCount: 1,
  1512  					},
  1513  					{
  1514  						Value:        2e6,
  1515  						MaxSwapCount: 2,
  1516  					},
  1517  				},
  1518  				MaxFeeRate:    maxFeeRate,
  1519  				FeeSuggestion: feeSuggestion,
  1520  				Options: map[string]string{
  1521  					multiSplitKey: "false",
  1522  				},
  1523  			},
  1524  			maxLock:      46e5,
  1525  			bondReserves: 12e5,
  1526  			utxos: []walletjson.ListUnspentResult{
  1527  				{
  1528  					Confirmations: 1,
  1529  					Spendable:     true,
  1530  					TxID:          txIDs[2],
  1531  					Account:       tAcctName,
  1532  					ScriptPubKey:  scriptPubKeys[2],
  1533  					Address:       addresses[2],
  1534  					Amount:        1e6 / 1e8,
  1535  					Vout:          0,
  1536  				},
  1537  				{
  1538  					Confirmations: 1,
  1539  					Spendable:     true,
  1540  					TxID:          txIDs[0],
  1541  					Account:       tAcctName,
  1542  					ScriptPubKey:  scriptPubKeys[0],
  1543  					Address:       addresses[0],
  1544  					Amount:        11e5 / 1e8,
  1545  					Vout:          0,
  1546  				},
  1547  				{
  1548  					Confirmations: 1,
  1549  					Spendable:     true,
  1550  					TxID:          txIDs[1],
  1551  					Account:       tAcctName,
  1552  					ScriptPubKey:  scriptPubKeys[1],
  1553  					Address:       addresses[1],
  1554  					Amount:        25e5 / 1e8,
  1555  					Vout:          0,
  1556  				},
  1557  			},
  1558  			balance: 46e5,
  1559  			expectedCoins: []asset.Coins{
  1560  				{newOutput(&txHashes[0], 0, 11e5, wire.TxTreeRegular)},
  1561  			},
  1562  			expectedRedeemScripts: [][]dex.Bytes{
  1563  				{nil},
  1564  			},
  1565  			expectedLockedCoins: []*wire.OutPoint{
  1566  				wire.NewOutPoint(&txHashes[0], 0, wire.TxTreeRegular),
  1567  			},
  1568  		},
  1569  		{ // "split not allowed, need to fund in increasing order"
  1570  			name: "no split, need to fund in increasing order",
  1571  			multiOrder: &asset.MultiOrder{
  1572  				Values: []*asset.MultiOrderValue{
  1573  					{
  1574  						Value:        2e6,
  1575  						MaxSwapCount: 2,
  1576  					},
  1577  					{
  1578  						Value:        11e5,
  1579  						MaxSwapCount: 1,
  1580  					},
  1581  					{
  1582  						Value:        9e5,
  1583  						MaxSwapCount: 1,
  1584  					},
  1585  				},
  1586  				MaxFeeRate:    maxFeeRate,
  1587  				FeeSuggestion: feeSuggestion,
  1588  				Options: map[string]string{
  1589  					multiSplitKey: "false",
  1590  				},
  1591  			},
  1592  			maxLock: 50e5,
  1593  			utxos: []walletjson.ListUnspentResult{
  1594  				{
  1595  					Confirmations: 1,
  1596  					Spendable:     true,
  1597  					TxID:          txIDs[0],
  1598  					Account:       tAcctName,
  1599  					ScriptPubKey:  scriptPubKeys[0],
  1600  					Address:       addresses[0],
  1601  					Amount:        11e5 / 1e8,
  1602  					Vout:          0,
  1603  				},
  1604  				{
  1605  					Confirmations: 1,
  1606  					Spendable:     true,
  1607  					TxID:          txIDs[1],
  1608  					Account:       tAcctName,
  1609  					ScriptPubKey:  scriptPubKeys[1],
  1610  					Address:       addresses[1],
  1611  					Amount:        13e5 / 1e8,
  1612  					Vout:          0,
  1613  				},
  1614  				{
  1615  					Confirmations: 1,
  1616  					Spendable:     true,
  1617  					TxID:          txIDs[2],
  1618  					Account:       tAcctName,
  1619  					ScriptPubKey:  scriptPubKeys[2],
  1620  					Address:       addresses[2],
  1621  					Amount:        26e5 / 1e8,
  1622  					Vout:          0,
  1623  				},
  1624  			},
  1625  			balance: 50e5,
  1626  			expectedCoins: []asset.Coins{
  1627  				{newOutput(&txHashes[2], 0, 26e5, wire.TxTreeRegular)},
  1628  				{newOutput(&txHashes[1], 0, 13e5, wire.TxTreeRegular)},
  1629  				{newOutput(&txHashes[0], 0, 11e5, wire.TxTreeRegular)},
  1630  			},
  1631  			expectedRedeemScripts: [][]dex.Bytes{
  1632  				{nil},
  1633  				{nil},
  1634  				{nil},
  1635  			},
  1636  			expectedLockedCoins: []*wire.OutPoint{
  1637  				wire.NewOutPoint(&txHashes[0], 0, wire.TxTreeRegular),
  1638  				wire.NewOutPoint(&txHashes[1], 0, wire.TxTreeRegular),
  1639  				wire.NewOutPoint(&txHashes[2], 0, wire.TxTreeRegular),
  1640  			},
  1641  		},
  1642  		{ // "split allowed, no split required"
  1643  			name: "split allowed, no split required",
  1644  			multiOrder: &asset.MultiOrder{
  1645  				Values: []*asset.MultiOrderValue{
  1646  					{
  1647  						Value:        1e6,
  1648  						MaxSwapCount: 1,
  1649  					},
  1650  					{
  1651  						Value:        2e6,
  1652  						MaxSwapCount: 2,
  1653  					},
  1654  				},
  1655  				MaxFeeRate:    maxFeeRate,
  1656  				FeeSuggestion: feeSuggestion,
  1657  				Options: map[string]string{
  1658  					multiSplitKey: "true",
  1659  				},
  1660  			},
  1661  			allOrNothing: false,
  1662  			maxLock:      43e5,
  1663  			utxos: []walletjson.ListUnspentResult{
  1664  				{
  1665  					Confirmations: 1,
  1666  					Spendable:     true,
  1667  					TxID:          txIDs[2],
  1668  					Account:       tAcctName,
  1669  					ScriptPubKey:  scriptPubKeys[2],
  1670  					Address:       addresses[2],
  1671  					Amount:        1e6 / 1e8,
  1672  					Vout:          0,
  1673  				},
  1674  				{
  1675  					Confirmations: 1,
  1676  					Spendable:     true,
  1677  					TxID:          txIDs[0],
  1678  					Account:       tAcctName,
  1679  					ScriptPubKey:  scriptPubKeys[0],
  1680  					Address:       addresses[0],
  1681  					Amount:        11e5 / 1e8,
  1682  					Vout:          0,
  1683  				},
  1684  				{
  1685  					Confirmations: 1,
  1686  					Spendable:     true,
  1687  					TxID:          txIDs[1],
  1688  					Account:       tAcctName,
  1689  					ScriptPubKey:  scriptPubKeys[1],
  1690  					Address:       addresses[1],
  1691  					Amount:        22e5 / 1e8,
  1692  					Vout:          0,
  1693  				},
  1694  			},
  1695  			balance: 43e5,
  1696  			expectedCoins: []asset.Coins{
  1697  				{newOutput(&txHashes[0], 0, 11e5, wire.TxTreeRegular)},
  1698  				{newOutput(&txHashes[1], 0, 22e5, wire.TxTreeRegular)},
  1699  			},
  1700  			expectedRedeemScripts: [][]dex.Bytes{
  1701  				{nil},
  1702  				{nil},
  1703  			},
  1704  			expectedLockedCoins: []*wire.OutPoint{
  1705  				wire.NewOutPoint(&txHashes[1], 0, wire.TxTreeRegular),
  1706  				wire.NewOutPoint(&txHashes[2], 0, wire.TxTreeRegular),
  1707  			},
  1708  		},
  1709  		{ // "split allowed, can fund both with split"
  1710  			name: "split allowed, can fund both with split",
  1711  			multiOrder: &asset.MultiOrder{
  1712  				Values: []*asset.MultiOrderValue{
  1713  					{
  1714  						Value:        15e5,
  1715  						MaxSwapCount: 2,
  1716  					},
  1717  					{
  1718  						Value:        15e5,
  1719  						MaxSwapCount: 2,
  1720  					},
  1721  				},
  1722  				MaxFeeRate:    maxFeeRate,
  1723  				FeeSuggestion: feeSuggestion,
  1724  				Options: map[string]string{
  1725  					multiSplitKey: "true",
  1726  				},
  1727  			},
  1728  			utxos: []walletjson.ListUnspentResult{
  1729  				{
  1730  					Confirmations: 1,
  1731  					Spendable:     true,
  1732  					TxID:          txIDs[0],
  1733  					Account:       tAcctName,
  1734  					ScriptPubKey:  scriptPubKeys[0],
  1735  					Address:       addresses[0],
  1736  					Amount:        1e6 / 1e8,
  1737  					Vout:          0,
  1738  				},
  1739  				{
  1740  					Confirmations: 1,
  1741  					Spendable:     true,
  1742  					TxID:          txIDs[1],
  1743  					Account:       tAcctName,
  1744  					ScriptPubKey:  scriptPubKeys[1],
  1745  					Address:       addresses[1],
  1746  					Amount:        (2*float64(requiredForOrder(15e5, 2)) + float64(expectedSplitFee(2, 2)) - 1e6) / 1e8,
  1747  					Vout:          0,
  1748  				},
  1749  			},
  1750  			maxLock:         2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(2, 2),
  1751  			balance:         2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(2, 2),
  1752  			expectSendRawTx: true,
  1753  			expectedInputs: []*wire.TxIn{
  1754  				{
  1755  					PreviousOutPoint: wire.OutPoint{
  1756  						Hash:  txHashes[1],
  1757  						Index: 0,
  1758  					},
  1759  				},
  1760  				{
  1761  					PreviousOutPoint: wire.OutPoint{
  1762  						Hash:  txHashes[0],
  1763  						Index: 0,
  1764  					},
  1765  				},
  1766  			},
  1767  			expectedOutputs: []*wire.TxOut{
  1768  				wire.NewTxOut(requiredForOrder(15e5, 2), []byte{}),
  1769  				wire.NewTxOut(requiredForOrder(15e5, 2), []byte{}),
  1770  			},
  1771  			expectedSplitFee: expectedSplitFee(2, 2),
  1772  			expectedRedeemScripts: [][]dex.Bytes{
  1773  				{nil},
  1774  				{nil},
  1775  			},
  1776  		},
  1777  		{ // "split allowed, cannot fund both with split"
  1778  			name: "split allowed, cannot fund both with split",
  1779  			multiOrder: &asset.MultiOrder{
  1780  				Values: []*asset.MultiOrderValue{
  1781  					{
  1782  						Value:        15e5,
  1783  						MaxSwapCount: 2,
  1784  					},
  1785  					{
  1786  						Value:        15e5,
  1787  						MaxSwapCount: 2,
  1788  					},
  1789  				},
  1790  				MaxFeeRate:    maxFeeRate,
  1791  				FeeSuggestion: feeSuggestion,
  1792  				Options: map[string]string{
  1793  					multiSplitKey: "true",
  1794  				},
  1795  			},
  1796  			utxos: []walletjson.ListUnspentResult{
  1797  				{
  1798  					Confirmations: 1,
  1799  					Spendable:     true,
  1800  					TxID:          txIDs[0],
  1801  					Account:       tAcctName,
  1802  					ScriptPubKey:  scriptPubKeys[0],
  1803  					Address:       addresses[0],
  1804  					Amount:        1e6 / 1e8,
  1805  					Vout:          0,
  1806  				},
  1807  				{
  1808  					Confirmations: 1,
  1809  					Spendable:     true,
  1810  					TxID:          txIDs[1],
  1811  					Account:       tAcctName,
  1812  					ScriptPubKey:  scriptPubKeys[1],
  1813  					Address:       addresses[1],
  1814  					Amount:        (2*float64(requiredForOrder(15e5, 2)) + float64(expectedSplitFee(2, 2)) - 1e6) / 1e8,
  1815  					Vout:          0,
  1816  				},
  1817  			},
  1818  			maxLock:   2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(2, 2) - 1,
  1819  			balance:   2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(2, 2) - 1,
  1820  			expectErr: true,
  1821  		},
  1822  		{ // "can fund both with split and respect maxLock"
  1823  			name: "can fund both with split and respect maxLock",
  1824  			multiOrder: &asset.MultiOrder{
  1825  				Values: []*asset.MultiOrderValue{
  1826  					{
  1827  						Value:        15e5,
  1828  						MaxSwapCount: 2,
  1829  					},
  1830  					{
  1831  						Value:        15e5,
  1832  						MaxSwapCount: 2,
  1833  					},
  1834  				},
  1835  				MaxFeeRate:    maxFeeRate,
  1836  				FeeSuggestion: feeSuggestion,
  1837  				Options: map[string]string{
  1838  					multiSplitKey: "true",
  1839  				},
  1840  			},
  1841  			utxos: []walletjson.ListUnspentResult{
  1842  				{
  1843  					Confirmations: 1,
  1844  					Spendable:     true,
  1845  					TxID:          txIDs[0],
  1846  					Account:       tAcctName,
  1847  					ScriptPubKey:  scriptPubKeys[0],
  1848  					Address:       addresses[0],
  1849  					Amount:        float64(50e5) / 1e8,
  1850  					Vout:          0,
  1851  				},
  1852  			},
  1853  			balance:         50e5,
  1854  			maxLock:         2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(1, 2),
  1855  			expectSendRawTx: true,
  1856  			expectedInputs: []*wire.TxIn{
  1857  				{
  1858  					PreviousOutPoint: wire.OutPoint{
  1859  						Hash:  txHashes[0],
  1860  						Index: 0,
  1861  					},
  1862  				},
  1863  			},
  1864  			expectedOutputs: []*wire.TxOut{
  1865  				wire.NewTxOut(requiredForOrder(15e5, 2), []byte{}),
  1866  				wire.NewTxOut(requiredForOrder(15e5, 2), []byte{}),
  1867  			},
  1868  			expectedChange:   50e5 - (2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(1, 3)),
  1869  			expectedSplitFee: expectedSplitFee(1, 3),
  1870  			expectedRedeemScripts: [][]dex.Bytes{
  1871  				{nil},
  1872  				{nil},
  1873  			},
  1874  		},
  1875  		{ // "cannot fund both with split and respect maxLock"
  1876  			name: "cannot fund both with split and respect maxLock",
  1877  			multiOrder: &asset.MultiOrder{
  1878  				Values: []*asset.MultiOrderValue{
  1879  					{
  1880  						Value:        15e5,
  1881  						MaxSwapCount: 2,
  1882  					},
  1883  					{
  1884  						Value:        15e5,
  1885  						MaxSwapCount: 2,
  1886  					},
  1887  				},
  1888  				MaxFeeRate:    maxFeeRate,
  1889  				FeeSuggestion: feeSuggestion,
  1890  				Options: map[string]string{
  1891  					multiSplitKey: "true",
  1892  				},
  1893  			},
  1894  			utxos: []walletjson.ListUnspentResult{
  1895  				{
  1896  					Confirmations: 1,
  1897  					Spendable:     true,
  1898  					TxID:          txIDs[0],
  1899  					Account:       tAcctName,
  1900  					ScriptPubKey:  scriptPubKeys[0],
  1901  					Address:       addresses[0],
  1902  					Amount:        float64(50e5) / 1e8,
  1903  					Vout:          0,
  1904  				},
  1905  			},
  1906  			balance:   50e5,
  1907  			maxLock:   2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(1, 2) - 1,
  1908  			expectErr: true,
  1909  		},
  1910  		{ // "split allowed, can fund both with split with bond reserves"
  1911  			name: "split allowed, can fund both with split with bond reserves",
  1912  			multiOrder: &asset.MultiOrder{
  1913  				Values: []*asset.MultiOrderValue{
  1914  					{
  1915  						Value:        15e5,
  1916  						MaxSwapCount: 2,
  1917  					},
  1918  					{
  1919  						Value:        15e5,
  1920  						MaxSwapCount: 2,
  1921  					},
  1922  				},
  1923  				MaxFeeRate:    maxFeeRate,
  1924  				FeeSuggestion: feeSuggestion,
  1925  				Options: map[string]string{
  1926  					multiSplitKey: "true",
  1927  				},
  1928  			},
  1929  			bondReserves: 2e6,
  1930  			utxos: []walletjson.ListUnspentResult{
  1931  				{
  1932  					Confirmations: 1,
  1933  					Spendable:     true,
  1934  					TxID:          txIDs[0],
  1935  					Account:       tAcctName,
  1936  					ScriptPubKey:  scriptPubKeys[0],
  1937  					Address:       addresses[0],
  1938  					Amount:        (2*float64(requiredForOrder(15e5, 2)) + 2e6 + float64(expectedSplitFee(1, 3))) / 1e8,
  1939  					Vout:          0,
  1940  				},
  1941  			},
  1942  			balance:         2e6 + 2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(1, 3),
  1943  			maxLock:         2e6 + 2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(1, 3),
  1944  			expectSendRawTx: true,
  1945  			expectedInputs: []*wire.TxIn{
  1946  				{
  1947  					PreviousOutPoint: wire.OutPoint{
  1948  						Hash:  txHashes[0],
  1949  						Index: 0,
  1950  					},
  1951  				},
  1952  			},
  1953  			expectedOutputs: []*wire.TxOut{
  1954  				wire.NewTxOut(requiredForOrder(15e5, 2), []byte{}),
  1955  				wire.NewTxOut(requiredForOrder(15e5, 2), []byte{}),
  1956  			},
  1957  			expectedChange:   2e6,
  1958  			expectedSplitFee: expectedSplitFee(1, 3),
  1959  			expectedRedeemScripts: [][]dex.Bytes{
  1960  				{nil},
  1961  				{nil},
  1962  			},
  1963  		},
  1964  		{ // "split allowed, cannot fund both with split and keep and bond reserves"
  1965  			name: "split allowed, cannot fund both with split and keep and bond reserves",
  1966  			multiOrder: &asset.MultiOrder{
  1967  				Values: []*asset.MultiOrderValue{
  1968  					{
  1969  						Value:        15e5,
  1970  						MaxSwapCount: 2,
  1971  					},
  1972  					{
  1973  						Value:        15e5,
  1974  						MaxSwapCount: 2,
  1975  					},
  1976  				},
  1977  				MaxFeeRate:    maxFeeRate,
  1978  				FeeSuggestion: feeSuggestion,
  1979  				Options: map[string]string{
  1980  					multiSplitKey: "true",
  1981  				},
  1982  			},
  1983  			bondReserves: 2e6,
  1984  			utxos: []walletjson.ListUnspentResult{
  1985  				{
  1986  					Confirmations: 1,
  1987  					Spendable:     true,
  1988  					TxID:          txIDs[0],
  1989  					Account:       tAcctName,
  1990  					ScriptPubKey:  scriptPubKeys[0],
  1991  					Address:       addresses[0],
  1992  					Amount:        ((2*float64(requiredForOrder(15e5, 2)) + 2e6 + float64(expectedSplitFee(1, 3))) / 1e8) - 1/1e8,
  1993  					Vout:          0,
  1994  				},
  1995  			},
  1996  			balance:   2e6 + 2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(1, 3) - 1,
  1997  			maxLock:   2e6 + 2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(1, 3) - 1,
  1998  			expectErr: true,
  1999  		},
  2000  		{ // "split with buffer"
  2001  			name: "split with buffer",
  2002  			multiOrder: &asset.MultiOrder{
  2003  				Values: []*asset.MultiOrderValue{
  2004  					{
  2005  						Value:        15e5,
  2006  						MaxSwapCount: 2,
  2007  					},
  2008  					{
  2009  						Value:        15e5,
  2010  						MaxSwapCount: 2,
  2011  					},
  2012  				},
  2013  				MaxFeeRate:    maxFeeRate,
  2014  				FeeSuggestion: feeSuggestion,
  2015  				Options: map[string]string{
  2016  					multiSplitKey:       "true",
  2017  					multiSplitBufferKey: "10",
  2018  				},
  2019  			},
  2020  			utxos: []walletjson.ListUnspentResult{
  2021  				{
  2022  					Confirmations: 1,
  2023  					Spendable:     true,
  2024  					TxID:          txIDs[0],
  2025  					Account:       tAcctName,
  2026  					ScriptPubKey:  scriptPubKeys[0],
  2027  					Address:       addresses[0],
  2028  					Amount:        (2*float64(requiredForOrder(15e5, 2)*110/100) + float64(expectedSplitFee(1, 2))) / 1e8,
  2029  					Vout:          0,
  2030  				},
  2031  			},
  2032  			balance:         2*uint64(requiredForOrder(15e5, 2)*110/100) + expectedSplitFee(1, 2),
  2033  			maxLock:         2*uint64(requiredForOrder(15e5, 2)*110/100) + expectedSplitFee(1, 2),
  2034  			expectSendRawTx: true,
  2035  			expectedInputs: []*wire.TxIn{
  2036  				{
  2037  					PreviousOutPoint: wire.OutPoint{
  2038  						Hash:  txHashes[0],
  2039  						Index: 0,
  2040  					},
  2041  				},
  2042  			},
  2043  			expectedOutputs: []*wire.TxOut{
  2044  				wire.NewTxOut(requiredForOrder(15e5, 2)*110/100, []byte{}),
  2045  				wire.NewTxOut(requiredForOrder(15e5, 2)*110/100, []byte{}),
  2046  			},
  2047  			expectedSplitFee: expectedSplitFee(1, 2),
  2048  			expectedRedeemScripts: [][]dex.Bytes{
  2049  				{nil},
  2050  				{nil},
  2051  			},
  2052  		},
  2053  		{ // "split, maxLock too low to fund buffer"
  2054  			name: "split, maxLock too low to fund buffer",
  2055  			multiOrder: &asset.MultiOrder{
  2056  				Values: []*asset.MultiOrderValue{
  2057  					{
  2058  						Value:        15e5,
  2059  						MaxSwapCount: 2,
  2060  					},
  2061  					{
  2062  						Value:        15e5,
  2063  						MaxSwapCount: 2,
  2064  					},
  2065  				},
  2066  				MaxFeeRate:    maxFeeRate,
  2067  				FeeSuggestion: feeSuggestion,
  2068  				Options: map[string]string{
  2069  					multiSplitKey:       "true",
  2070  					multiSplitBufferKey: "10",
  2071  				},
  2072  			},
  2073  			utxos: []walletjson.ListUnspentResult{
  2074  				{
  2075  					Confirmations: 1,
  2076  					Spendable:     true,
  2077  					TxID:          txIDs[0],
  2078  					Account:       tAcctName,
  2079  					ScriptPubKey:  scriptPubKeys[0],
  2080  					Address:       addresses[0],
  2081  					Amount:        (2*float64(requiredForOrder(15e5, 2)*110/100) + float64(expectedSplitFee(1, 2))) / 1e8,
  2082  					Vout:          0,
  2083  				},
  2084  			},
  2085  			balance:   2*uint64(requiredForOrder(15e5, 2)*110/100) + expectedSplitFee(1, 2),
  2086  			maxLock:   2*uint64(requiredForOrder(15e5, 2)*110/100) + expectedSplitFee(1, 2) - 1,
  2087  			expectErr: true,
  2088  		},
  2089  		{ // "only one order needs a split, rest can be funded without"
  2090  			name: "only one order needs a split, rest can be funded without",
  2091  			multiOrder: &asset.MultiOrder{
  2092  				Values: []*asset.MultiOrderValue{
  2093  					{
  2094  						Value:        1e6,
  2095  						MaxSwapCount: 2,
  2096  					},
  2097  					{
  2098  						Value:        1e6,
  2099  						MaxSwapCount: 2,
  2100  					},
  2101  					{
  2102  						Value:        1e6,
  2103  						MaxSwapCount: 2,
  2104  					},
  2105  				},
  2106  				MaxFeeRate:    maxFeeRate,
  2107  				FeeSuggestion: feeSuggestion,
  2108  				Options: map[string]string{
  2109  					multiSplitKey: "true",
  2110  				},
  2111  			},
  2112  			utxos: []walletjson.ListUnspentResult{
  2113  				{
  2114  					Confirmations: 1,
  2115  					Spendable:     true,
  2116  					TxID:          txIDs[0],
  2117  					Account:       tAcctName,
  2118  					ScriptPubKey:  scriptPubKeys[0],
  2119  					Address:       addresses[0],
  2120  					Amount:        12e5 / 1e8,
  2121  					Vout:          0,
  2122  				},
  2123  				{
  2124  					Confirmations: 1,
  2125  					Spendable:     true,
  2126  					TxID:          txIDs[1],
  2127  					Account:       tAcctName,
  2128  					ScriptPubKey:  scriptPubKeys[1],
  2129  					Address:       addresses[1],
  2130  					Amount:        12e5 / 1e8,
  2131  					Vout:          0,
  2132  				},
  2133  				{
  2134  					Confirmations: 1,
  2135  					Spendable:     true,
  2136  					TxID:          txIDs[2],
  2137  					Account:       tAcctName,
  2138  					ScriptPubKey:  scriptPubKeys[2],
  2139  					Address:       addresses[2],
  2140  					Amount:        120e5 / 1e8,
  2141  					Vout:          0,
  2142  				},
  2143  			},
  2144  			maxLock:         50e5,
  2145  			balance:         144e5,
  2146  			expectSendRawTx: true,
  2147  			expectedInputs: []*wire.TxIn{
  2148  				{
  2149  					PreviousOutPoint: wire.OutPoint{
  2150  						Hash:  txHashes[2],
  2151  						Index: 0,
  2152  					},
  2153  				},
  2154  			},
  2155  			expectedOutputs: []*wire.TxOut{
  2156  				wire.NewTxOut(requiredForOrder(1e6, 2), []byte{}),
  2157  				wire.NewTxOut(120e5-requiredForOrder(1e6, 2)-int64(expectedSplitFee(1, 2)), []byte{}),
  2158  			},
  2159  			expectedSplitFee: expectedSplitFee(1, 2),
  2160  			expectedRedeemScripts: [][]dex.Bytes{
  2161  				{nil},
  2162  				{nil},
  2163  				{nil},
  2164  			},
  2165  			expectedCoins: []asset.Coins{
  2166  				{newOutput(&txHashes[0], 0, 12e5, wire.TxTreeRegular)},
  2167  				{newOutput(&txHashes[1], 0, 12e5, wire.TxTreeRegular)},
  2168  				nil,
  2169  			},
  2170  		},
  2171  		{ // "only one order needs a split due to bond reserves, rest funded without"
  2172  			name: "only one order needs a split due to bond reserves, rest funded without",
  2173  			multiOrder: &asset.MultiOrder{
  2174  				Values: []*asset.MultiOrderValue{
  2175  					{
  2176  						Value:        1e6,
  2177  						MaxSwapCount: 2,
  2178  					},
  2179  					{
  2180  						Value:        1e6,
  2181  						MaxSwapCount: 2,
  2182  					},
  2183  					{
  2184  						Value:        1e6,
  2185  						MaxSwapCount: 2,
  2186  					},
  2187  				},
  2188  				MaxFeeRate:    maxFeeRate,
  2189  				FeeSuggestion: feeSuggestion,
  2190  				Options: map[string]string{
  2191  					multiSplitKey: "true",
  2192  				},
  2193  			},
  2194  			utxos: []walletjson.ListUnspentResult{
  2195  				{
  2196  					Confirmations: 1,
  2197  					Spendable:     true,
  2198  					TxID:          txIDs[0],
  2199  					Account:       tAcctName,
  2200  					ScriptPubKey:  scriptPubKeys[0],
  2201  					Address:       addresses[0],
  2202  					Amount:        12e5 / 1e8,
  2203  					Vout:          0,
  2204  				},
  2205  				{
  2206  					Confirmations: 1,
  2207  					Spendable:     true,
  2208  					TxID:          txIDs[1],
  2209  					Account:       tAcctName,
  2210  					ScriptPubKey:  scriptPubKeys[1],
  2211  					Address:       addresses[1],
  2212  					Amount:        12e5 / 1e8,
  2213  					Vout:          0,
  2214  				},
  2215  				{
  2216  					Confirmations: 1,
  2217  					Spendable:     true,
  2218  					TxID:          txIDs[2],
  2219  					Account:       tAcctName,
  2220  					ScriptPubKey:  scriptPubKeys[2],
  2221  					Address:       addresses[2],
  2222  					Amount:        120e5 / 1e8,
  2223  					Vout:          0,
  2224  				},
  2225  			},
  2226  			maxLock:         0,
  2227  			bondReserves:    1e6,
  2228  			balance:         144e5,
  2229  			expectSendRawTx: true,
  2230  			expectedInputs: []*wire.TxIn{
  2231  				{
  2232  					PreviousOutPoint: wire.OutPoint{
  2233  						Hash:  txHashes[2],
  2234  						Index: 0,
  2235  					},
  2236  				},
  2237  			},
  2238  			expectedOutputs: []*wire.TxOut{
  2239  				wire.NewTxOut(requiredForOrder(1e6, 2), []byte{}),
  2240  				wire.NewTxOut(120e5-requiredForOrder(1e6, 2)-int64(expectedSplitFee(1, 2)), []byte{}),
  2241  			},
  2242  			expectedSplitFee: expectedSplitFee(1, 2),
  2243  			expectedRedeemScripts: [][]dex.Bytes{
  2244  				{nil},
  2245  				{nil},
  2246  				{nil},
  2247  			},
  2248  			expectedCoins: []asset.Coins{
  2249  				{newOutput(&txHashes[0], 0, 12e5, wire.TxTreeRegular)},
  2250  				{newOutput(&txHashes[1], 0, 12e5, wire.TxTreeRegular)},
  2251  				nil,
  2252  			},
  2253  		},
  2254  		{ // "only one order needs a split due to maxLock, rest funded without"
  2255  			name: "only one order needs a split due to maxLock, rest funded without",
  2256  			multiOrder: &asset.MultiOrder{
  2257  				Values: []*asset.MultiOrderValue{
  2258  					{
  2259  						Value:        1e6,
  2260  						MaxSwapCount: 2,
  2261  					},
  2262  					{
  2263  						Value:        1e6,
  2264  						MaxSwapCount: 2,
  2265  					},
  2266  					{
  2267  						Value:        1e6,
  2268  						MaxSwapCount: 2,
  2269  					},
  2270  				},
  2271  				MaxFeeRate:    maxFeeRate,
  2272  				FeeSuggestion: feeSuggestion,
  2273  				Options: map[string]string{
  2274  					multiSplitKey: "true",
  2275  				},
  2276  			},
  2277  			utxos: []walletjson.ListUnspentResult{
  2278  				{
  2279  					Confirmations: 1,
  2280  					Spendable:     true,
  2281  					TxID:          txIDs[0],
  2282  					Account:       tAcctName,
  2283  					ScriptPubKey:  scriptPubKeys[0],
  2284  					Address:       addresses[0],
  2285  					Amount:        12e5 / 1e8,
  2286  					Vout:          0,
  2287  				},
  2288  				{
  2289  					Confirmations: 1,
  2290  					Spendable:     true,
  2291  					TxID:          txIDs[1],
  2292  					Account:       tAcctName,
  2293  					ScriptPubKey:  scriptPubKeys[1],
  2294  					Address:       addresses[1],
  2295  					Amount:        12e5 / 1e8,
  2296  					Vout:          0,
  2297  				},
  2298  				{
  2299  					Confirmations: 1,
  2300  					Spendable:     true,
  2301  					TxID:          txIDs[2],
  2302  					Account:       tAcctName,
  2303  					ScriptPubKey:  scriptPubKeys[2],
  2304  					Address:       addresses[2],
  2305  					Amount:        9e5 / 1e8,
  2306  					Vout:          0,
  2307  				},
  2308  				{
  2309  					Confirmations: 1,
  2310  					Spendable:     true,
  2311  					TxID:          txIDs[3],
  2312  					Account:       tAcctName,
  2313  					ScriptPubKey:  scriptPubKeys[3],
  2314  					Address:       addresses[3],
  2315  					Amount:        9e5 / 1e8,
  2316  					Vout:          0,
  2317  				},
  2318  			},
  2319  			maxLock:         35e5,
  2320  			bondReserves:    0,
  2321  			balance:         42e5,
  2322  			expectSendRawTx: true,
  2323  			expectedInputs: []*wire.TxIn{
  2324  				{
  2325  					PreviousOutPoint: wire.OutPoint{
  2326  						Hash:  txHashes[3],
  2327  						Index: 0,
  2328  					},
  2329  				},
  2330  				{
  2331  					PreviousOutPoint: wire.OutPoint{
  2332  						Hash:  txHashes[2],
  2333  						Index: 0,
  2334  					},
  2335  				},
  2336  			},
  2337  			expectedOutputs: []*wire.TxOut{
  2338  				wire.NewTxOut(requiredForOrder(1e6, 2), []byte{}),
  2339  				wire.NewTxOut(18e5-requiredForOrder(1e6, 2)-int64(expectedSplitFee(2, 2)), []byte{}),
  2340  			},
  2341  			expectedSplitFee: expectedSplitFee(2, 2),
  2342  			expectedRedeemScripts: [][]dex.Bytes{
  2343  				{nil},
  2344  				{nil},
  2345  				{nil},
  2346  			},
  2347  			expectedCoins: []asset.Coins{
  2348  				{newOutput(&txHashes[0], 0, 12e5, wire.TxTreeRegular)},
  2349  				{newOutput(&txHashes[1], 0, 12e5, wire.TxTreeRegular)},
  2350  				nil,
  2351  			},
  2352  		},
  2353  	}
  2354  
  2355  	for _, test := range tests {
  2356  		node.unspent = test.utxos
  2357  		node.newAddr = tPKHAddr
  2358  		node.changeAddr = tPKHAddr
  2359  		node.signFunc = func(msgTx *wire.MsgTx) (*wire.MsgTx, bool, error) {
  2360  			return signFunc(msgTx, dexdcr.P2PKHSigScriptSize)
  2361  		}
  2362  		node.sentRawTx = nil
  2363  		node.lockedCoins = nil
  2364  		node.balanceResult = &walletjson.GetBalanceResult{
  2365  			Balances: []walletjson.GetAccountBalanceResult{
  2366  				{
  2367  					AccountName: tAcctName,
  2368  					Spendable:   toDCR(test.balance),
  2369  				},
  2370  			},
  2371  		}
  2372  		wallet.fundingCoins = make(map[outPoint]*fundingCoin)
  2373  		wallet.bondReserves.Store(test.bondReserves)
  2374  
  2375  		allCoins, _, splitFee, err := wallet.FundMultiOrder(test.multiOrder, test.maxLock)
  2376  		if test.expectErr {
  2377  			if err == nil {
  2378  				t.Fatalf("%s: no error returned", test.name)
  2379  			}
  2380  			if strings.Contains(err.Error(), "insufficient funds") {
  2381  				t.Fatalf("%s: unexpected insufficient funds error", test.name)
  2382  			}
  2383  			continue
  2384  		}
  2385  		if err != nil {
  2386  			t.Fatalf("%s: unexpected error: %v", test.name, err)
  2387  		}
  2388  
  2389  		if !test.expectSendRawTx { // no split
  2390  			if node.sentRawTx != nil {
  2391  				t.Fatalf("%s: unexpected transaction sent", test.name)
  2392  			}
  2393  			if len(allCoins) != len(test.expectedCoins) {
  2394  				t.Fatalf("%s: expected %d coins, got %d", test.name, len(test.expectedCoins), len(allCoins))
  2395  			}
  2396  			for i := range allCoins {
  2397  				if len(allCoins[i]) != len(test.expectedCoins[i]) {
  2398  					t.Fatalf("%s: expected %d coins in set %d, got %d", test.name, len(test.expectedCoins[i]), i, len(allCoins[i]))
  2399  				}
  2400  				actual := allCoins[i]
  2401  				expected := test.expectedCoins[i]
  2402  				sort.Slice(actual, func(i, j int) bool {
  2403  					return bytes.Compare(actual[i].ID(), actual[j].ID()) < 0
  2404  				})
  2405  				sort.Slice(expected, func(i, j int) bool {
  2406  					return bytes.Compare(expected[i].ID(), expected[j].ID()) < 0
  2407  				})
  2408  				for j := range actual {
  2409  					if !bytes.Equal(actual[j].ID(), expected[j].ID()) {
  2410  						t.Fatalf("%s: unexpected coin in set %d. expected %s, got %s", test.name, i, expected[j].ID(), actual[j].ID())
  2411  					}
  2412  					if actual[j].Value() != expected[j].Value() {
  2413  						t.Fatalf("%s: unexpected coin value in set %d. expected %d, got %d", test.name, i, expected[j].Value(), actual[j].Value())
  2414  					}
  2415  				}
  2416  			}
  2417  		} else { // expectSplit
  2418  			if node.sentRawTx == nil {
  2419  				t.Fatalf("%s: SendRawTransaction not called", test.name)
  2420  			}
  2421  			if len(node.sentRawTx.TxIn) != len(test.expectedInputs) {
  2422  				t.Fatalf("%s: expected %d inputs, got %d", test.name, len(test.expectedInputs), len(node.sentRawTx.TxIn))
  2423  			}
  2424  			for i, actualIn := range node.sentRawTx.TxIn {
  2425  				expectedIn := test.expectedInputs[i]
  2426  				if !bytes.Equal(actualIn.PreviousOutPoint.Hash[:], expectedIn.PreviousOutPoint.Hash[:]) {
  2427  					t.Fatalf("%s: unexpected input %d hash. expected %s, got %s", test.name, i, expectedIn.PreviousOutPoint.Hash, actualIn.PreviousOutPoint.Hash)
  2428  				}
  2429  				if actualIn.PreviousOutPoint.Index != expectedIn.PreviousOutPoint.Index {
  2430  					t.Fatalf("%s: unexpected input %d index. expected %d, got %d", test.name, i, expectedIn.PreviousOutPoint.Index, actualIn.PreviousOutPoint.Index)
  2431  				}
  2432  			}
  2433  			expectedNumOutputs := len(test.expectedOutputs)
  2434  			if test.expectedChange > 0 {
  2435  				expectedNumOutputs++
  2436  			}
  2437  			if len(node.sentRawTx.TxOut) != expectedNumOutputs {
  2438  				t.Fatalf("%s: expected %d outputs, got %d", test.name, expectedNumOutputs, len(node.sentRawTx.TxOut))
  2439  			}
  2440  
  2441  			for i, expectedOut := range test.expectedOutputs {
  2442  				actualOut := node.sentRawTx.TxOut[i]
  2443  				if actualOut.Value != expectedOut.Value {
  2444  					t.Fatalf("%s: unexpected output %d value. expected %d, got %d", test.name, i, expectedOut.Value, actualOut.Value)
  2445  				}
  2446  			}
  2447  			if test.expectedChange > 0 {
  2448  				actualOut := node.sentRawTx.TxOut[len(node.sentRawTx.TxOut)-1]
  2449  				if uint64(actualOut.Value) != test.expectedChange {
  2450  					t.Fatalf("%s: unexpected change value. expected %d, got %d", test.name, test.expectedChange, actualOut.Value)
  2451  				}
  2452  			}
  2453  
  2454  			if len(test.multiOrder.Values) != len(allCoins) {
  2455  				t.Fatalf("%s: expected %d coins, got %d", test.name, len(test.multiOrder.Values), len(allCoins))
  2456  			}
  2457  			splitTxID := node.sentRawTx.TxHash()
  2458  
  2459  			// This means all coins are split outputs
  2460  			if test.expectedCoins == nil {
  2461  				for i, actualCoin := range allCoins {
  2462  					actualOut := actualCoin[0].(*output)
  2463  					expectedOut := node.sentRawTx.TxOut[i]
  2464  					if uint64(expectedOut.Value) != actualOut.value {
  2465  						t.Fatalf("%s: unexpected output %d value. expected %d, got %d", test.name, i, expectedOut.Value, actualOut.value)
  2466  					}
  2467  					if !bytes.Equal(actualOut.pt.txHash[:], splitTxID[:]) {
  2468  						t.Fatalf("%s: unexpected output %d txid. expected %s, got %s", test.name, i, splitTxID, actualOut.pt.txHash)
  2469  					}
  2470  				}
  2471  			} else {
  2472  				var splitTxOutputIndex int
  2473  				for i := range allCoins {
  2474  					actual := allCoins[i]
  2475  					expected := test.expectedCoins[i]
  2476  
  2477  					// This means the coins are the split outputs
  2478  					if expected == nil {
  2479  						actualOut := actual[0].(*output)
  2480  						expectedOut := node.sentRawTx.TxOut[splitTxOutputIndex]
  2481  						if uint64(expectedOut.Value) != actualOut.value {
  2482  							t.Fatalf("%s: unexpected output %d value. expected %d, got %d", test.name, i, expectedOut.Value, actualOut.value)
  2483  						}
  2484  						if !bytes.Equal(actualOut.pt.txHash[:], splitTxID[:]) {
  2485  							t.Fatalf("%s: unexpected output %d txid. expected %s, got %s", test.name, i, splitTxID, actualOut.pt.txHash)
  2486  						}
  2487  						splitTxOutputIndex++
  2488  						continue
  2489  					}
  2490  
  2491  					if len(actual) != len(expected) {
  2492  						t.Fatalf("%s: expected %d coins in set %d, got %d", test.name, len(test.expectedCoins[i]), i, len(allCoins[i]))
  2493  					}
  2494  					sort.Slice(actual, func(i, j int) bool {
  2495  						return bytes.Compare(actual[i].ID(), actual[j].ID()) < 0
  2496  					})
  2497  					sort.Slice(expected, func(i, j int) bool {
  2498  						return bytes.Compare(expected[i].ID(), expected[j].ID()) < 0
  2499  					})
  2500  					for j := range actual {
  2501  						if !bytes.Equal(actual[j].ID(), expected[j].ID()) {
  2502  							t.Fatalf("%s: unexpected coin in set %d. expected %s, got %s", test.name, i, expected[j].ID(), actual[j].ID())
  2503  						}
  2504  						if actual[j].Value() != expected[j].Value() {
  2505  							t.Fatalf("%s: unexpected coin value in set %d. expected %d, got %d", test.name, i, expected[j].Value(), actual[j].Value())
  2506  						}
  2507  					}
  2508  				}
  2509  			}
  2510  
  2511  			// Each split output should be locked
  2512  			if len(node.lockedCoins) != len(allCoins) {
  2513  				t.Fatalf("%s: expected %d locked coins, got %d", test.name, len(allCoins), len(node.lockedCoins))
  2514  			}
  2515  
  2516  		}
  2517  
  2518  		// Check that the right coins are locked and in the fundingCoins map
  2519  		var totalNumCoins int
  2520  		for _, coins := range allCoins {
  2521  			totalNumCoins += len(coins)
  2522  		}
  2523  		if totalNumCoins != len(wallet.fundingCoins) {
  2524  			t.Fatalf("%s: expected %d funding coins in wallet, got %d", test.name, totalNumCoins, len(wallet.fundingCoins))
  2525  		}
  2526  		//totalNumCoins += len(test.expectedInputs)
  2527  		if totalNumCoins != len(node.lockedCoins) {
  2528  			t.Fatalf("%s: expected %d locked coins, got %d", test.name, totalNumCoins, len(node.lockedCoins))
  2529  		}
  2530  		lockedCoins := make(map[wire.OutPoint]any)
  2531  		for _, coin := range node.lockedCoins {
  2532  			lockedCoins[*coin] = true
  2533  		}
  2534  		checkLockedCoin := func(txHash chainhash.Hash, vout uint32) {
  2535  			if _, ok := lockedCoins[wire.OutPoint{Hash: txHash, Index: vout, Tree: wire.TxTreeRegular}]; !ok {
  2536  				t.Fatalf("%s: expected locked coin %s:%d not found", test.name, txHash, vout)
  2537  			}
  2538  		}
  2539  		checkFundingCoin := func(txHash chainhash.Hash, vout uint32) {
  2540  			if _, ok := wallet.fundingCoins[outPoint{txHash: txHash, vout: vout}]; !ok {
  2541  				t.Fatalf("%s: expected locked coin %s:%d not found in wallet", test.name, txHash, vout)
  2542  			}
  2543  		}
  2544  		for _, coins := range allCoins {
  2545  			for _, coin := range coins {
  2546  				// decode coin to output
  2547  				out := coin.(*output)
  2548  				checkLockedCoin(out.pt.txHash, out.pt.vout)
  2549  				checkFundingCoin(out.pt.txHash, out.pt.vout)
  2550  			}
  2551  		}
  2552  		//for _, expectedIn := range test.expectedInputs {
  2553  		//	checkLockedCoin(expectedIn.PreviousOutPoint.Hash, expectedIn.PreviousOutPoint.Index)
  2554  		//}
  2555  
  2556  		if test.expectedSplitFee != splitFee {
  2557  			t.Fatalf("%s: unexpected split fee. expected %d, got %d", test.name, test.expectedSplitFee, splitFee)
  2558  		}
  2559  	}
  2560  }
  2561  
  2562  func TestFundEdges(t *testing.T) {
  2563  	wallet, node, shutdown := tNewWallet()
  2564  	defer shutdown()
  2565  
  2566  	swapVal := uint64(1e8)
  2567  	lots := swapVal / tLotSize
  2568  
  2569  	// Swap fees
  2570  	//
  2571  	// fee_rate: 24 atoms / byte (dex MaxFeeRate)
  2572  	// swap_size: 251 bytes
  2573  	// swap_size_base: 85 bytes (251 - 166 p2pkh input)
  2574  	// lot_size: 1e7
  2575  	// swap_value: 1e8
  2576  	//  lots = swap_size / lot_size = 10
  2577  	//  base_tx_bytes = (lots - 1) * swap_size + swap_size_base = 9 * 251 + 85 = 2344
  2578  	//  base_fees = 56256
  2579  	//  backing_bytes: 1x P2PKH inputs = dexdcr.P2PKHInputSize = 166 bytes
  2580  	//  backing_fees: 166 * fee_rate(24 atoms/byte) = 3984 atoms
  2581  	//  total_bytes  = base_tx_bytes + backing_bytes = 2344 + 166 = 2510
  2582  	// total_fees: base_fees + backing_fees = 56256 + 3984 = 60240 atoms
  2583  	//          OR total_bytes * fee_rate = 2510 * 24 = 60240
  2584  	// base_best_case_bytes = swap_size_base + (lots - 1) * swap_output_size (P2SHOutputSize) + backing_bytes
  2585  	//                      = 85 + 9*34 + 166 = 557
  2586  	const swapSize = 251
  2587  	const totalBytes = 2510
  2588  	const bestCaseBytes = swapSize
  2589  	const swapOutputSize = 34
  2590  	fees := uint64(totalBytes) * tDCR.MaxFeeRate
  2591  	p2pkhUnspent := walletjson.ListUnspentResult{
  2592  		TxID:          tTxID,
  2593  		Address:       tPKHAddr.String(),
  2594  		Account:       tAcctName,
  2595  		Amount:        float64(swapVal+fees-1) / 1e8, // one atom less than needed
  2596  		Confirmations: 5,
  2597  		ScriptPubKey:  hex.EncodeToString(tP2PKHScript),
  2598  		Spendable:     true,
  2599  	}
  2600  
  2601  	node.unspent = []walletjson.ListUnspentResult{p2pkhUnspent}
  2602  	ord := &asset.Order{
  2603  		Version:       version,
  2604  		Value:         swapVal,
  2605  		MaxSwapCount:  lots,
  2606  		MaxFeeRate:    tDCR.MaxFeeRate,
  2607  		FeeSuggestion: feeSuggestion,
  2608  	}
  2609  
  2610  	// MaxOrder will try a split tx, which will work with one lot less.
  2611  	var feeReduction uint64 = swapSize * tDCR.MaxFeeRate
  2612  	estFeeReduction := swapSize * feeSuggestion
  2613  	splitFees := splitTxBaggage * tDCR.MaxFeeRate
  2614  	checkMaxOrder(t, wallet, lots-1, swapVal-tLotSize,
  2615  		fees+splitFees-feeReduction,                               // max fees
  2616  		(totalBytes+splitTxBaggage)*feeSuggestion-estFeeReduction, // worst case
  2617  		(bestCaseBytes+splitTxBaggage)*feeSuggestion)              // best case
  2618  
  2619  	_, _, _, err := wallet.FundOrder(ord)
  2620  	if err == nil {
  2621  		t.Fatalf("no error when not enough funds in single p2pkh utxo")
  2622  	}
  2623  
  2624  	// Now add the needed atoms and try again. The fees will reflect that the
  2625  	// split was skipped because insufficient available for splitFees.
  2626  	p2pkhUnspent.Amount = float64(swapVal+fees) / 1e8
  2627  	node.unspent = []walletjson.ListUnspentResult{p2pkhUnspent}
  2628  
  2629  	checkMaxOrder(t, wallet, lots, swapVal, fees, totalBytes*feeSuggestion,
  2630  		bestCaseBytes*feeSuggestion)
  2631  
  2632  	_, _, _, err = wallet.FundOrder(ord)
  2633  	if err != nil {
  2634  		t.Fatalf("should be enough to fund with a single p2pkh utxo: %v", err)
  2635  	}
  2636  
  2637  	// For a split transaction, we would need to cover the splitTxBaggage as
  2638  	// well.
  2639  	wallet.config().useSplitTx = true
  2640  	node.newAddr = tPKHAddr
  2641  	node.changeAddr = tPKHAddr
  2642  	node.signFunc = func(msgTx *wire.MsgTx) (*wire.MsgTx, bool, error) {
  2643  		return signFunc(msgTx, dexdcr.P2PKHSigScriptSize)
  2644  	}
  2645  
  2646  	fees = uint64(totalBytes+splitTxBaggage) * tDCR.MaxFeeRate
  2647  	v := swapVal + fees - 1
  2648  	node.unspent[0].Amount = float64(v) / 1e8
  2649  	coins, _, _, err := wallet.FundOrder(ord)
  2650  	if err != nil {
  2651  		t.Fatalf("error when skipping split tx because not enough to cover baggage: %v", err)
  2652  	}
  2653  	if coins[0].Value() != v {
  2654  		t.Fatalf("split performed when baggage wasn't covered")
  2655  	}
  2656  	// Now get the split.
  2657  	v = swapVal + fees
  2658  	node.unspent[0].Amount = float64(v) / 1e8
  2659  
  2660  	checkMaxOrder(t, wallet, lots, swapVal, fees, (totalBytes+splitTxBaggage)*feeSuggestion,
  2661  		(bestCaseBytes+splitTxBaggage)*feeSuggestion) // fees include split (did not fall back to no split)
  2662  
  2663  	coins, _, _, err = wallet.FundOrder(ord)
  2664  	if err != nil {
  2665  		t.Fatalf("error funding split tx: %v", err)
  2666  	}
  2667  	if coins[0].Value() == v {
  2668  		t.Fatalf("split performed when baggage wasn't covered")
  2669  	}
  2670  
  2671  	// Split transactions require a fee suggestion.
  2672  	// TODO:
  2673  	// 1.0: Error when no suggestion.
  2674  	// ord.FeeSuggestion = 0
  2675  	// _, _, err = wallet.FundOrder(ord)
  2676  	// if err == nil {
  2677  	// 	t.Fatalf("no error for no fee suggestions on split tx")
  2678  	// }
  2679  	ord.FeeSuggestion = tDCR.MaxFeeRate + 1
  2680  	_, _, _, err = wallet.FundOrder(ord)
  2681  	if err == nil {
  2682  		t.Fatalf("no error for high fee suggestions on split tx")
  2683  	}
  2684  	// Check success again.
  2685  	ord.FeeSuggestion = tDCR.MaxFeeRate
  2686  	_, _, _, err = wallet.FundOrder(ord)
  2687  	if err != nil {
  2688  		t.Fatalf("error fixing split tx: %v", err)
  2689  	}
  2690  	wallet.config().useSplitTx = false
  2691  
  2692  	// TODO: test version mismatch
  2693  }
  2694  
  2695  func TestSwap(t *testing.T) {
  2696  	wallet, node, shutdown := tNewWallet()
  2697  	defer shutdown()
  2698  
  2699  	swapVal := toAtoms(5)
  2700  	coins := asset.Coins{
  2701  		newOutput(tTxHash, 0, toAtoms(3), wire.TxTreeRegular),
  2702  		newOutput(tTxHash, 0, toAtoms(3), wire.TxTreeRegular),
  2703  	}
  2704  
  2705  	privBytes, _ := hex.DecodeString("b07209eec1a8fb6cfe5cb6ace36567406971a75c330db7101fb21bc679bc5330")
  2706  
  2707  	node.changeAddr = tPKHAddr
  2708  	var err error
  2709  	node.privWIF, err = dcrutil.NewWIF(privBytes, tChainParams.PrivateKeyID, dcrec.STEcdsaSecp256k1)
  2710  	if err != nil {
  2711  		t.Fatalf("NewWIF error: %v", err)
  2712  	}
  2713  
  2714  	node.newAddr = tPKHAddr
  2715  	node.changeAddr = tPKHAddr
  2716  
  2717  	secretHash, _ := hex.DecodeString("5124208c80d33507befa517c08ed01aa8d33adbf37ecd70fb5f9352f7a51a88d")
  2718  	contract := &asset.Contract{
  2719  		Address:    tPKHAddr.String(),
  2720  		Value:      swapVal,
  2721  		SecretHash: secretHash,
  2722  		LockTime:   uint64(time.Now().Unix()),
  2723  	}
  2724  
  2725  	swaps := &asset.Swaps{
  2726  		Inputs:     coins,
  2727  		Contracts:  []*asset.Contract{contract},
  2728  		LockChange: true,
  2729  		FeeRate:    tDCR.MaxFeeRate,
  2730  	}
  2731  
  2732  	// Aim for 3 signature cycles.
  2733  	sigSizer := 0
  2734  	signFunc := func(msgTx *wire.MsgTx) (*wire.MsgTx, bool, error) {
  2735  		// Set the sigScripts to random bytes of the correct length for spending a
  2736  		// p2pkh output.
  2737  		scriptSize := dexdcr.P2PKHSigScriptSize
  2738  		// Oscillate the signature size to work the fee optimization loop.
  2739  		if sigSizer%2 == 0 {
  2740  			scriptSize -= 2
  2741  		}
  2742  		sigSizer++
  2743  		return signFunc(msgTx, scriptSize)
  2744  	}
  2745  
  2746  	node.signFunc = signFunc
  2747  	// reset the signFunc after this test so captured variables are free
  2748  	defer func() { node.signFunc = defaultSignFunc }()
  2749  
  2750  	// This time should succeed.
  2751  	_, changeCoin, feesPaid, err := wallet.Swap(swaps)
  2752  	if err != nil {
  2753  		t.Fatalf("swap error: %v", err)
  2754  	}
  2755  
  2756  	// Make sure the change coin is locked.
  2757  	if len(node.lockedCoins) != 1 {
  2758  		t.Fatalf("change coin not locked")
  2759  	}
  2760  	txHash, _, _ := decodeCoinID(changeCoin.ID())
  2761  	if txHash.String() != node.lockedCoins[0].Hash.String() {
  2762  		t.Fatalf("wrong coin locked during swap")
  2763  	}
  2764  
  2765  	// Fees should be returned.
  2766  	minFees := tDCR.MaxFeeRate * uint64(node.sentRawTx.SerializeSize())
  2767  	if feesPaid < minFees {
  2768  		t.Fatalf("sent fees, %d, less than required fees, %d", feesPaid, minFees)
  2769  	}
  2770  
  2771  	// Not enough funds
  2772  	swaps.Inputs = coins[:1]
  2773  	_, _, _, err = wallet.Swap(swaps)
  2774  	if err == nil {
  2775  		t.Fatalf("no error for listunspent not enough funds")
  2776  	}
  2777  	swaps.Inputs = coins
  2778  
  2779  	// AddressPKH error
  2780  	node.newAddrErr = tErr
  2781  	_, _, _, err = wallet.Swap(swaps)
  2782  	if err == nil {
  2783  		t.Fatalf("no error for getnewaddress rpc error")
  2784  	}
  2785  	node.newAddrErr = nil
  2786  
  2787  	// ChangeAddress error
  2788  	node.changeAddrErr = tErr
  2789  	_, _, _, err = wallet.Swap(swaps)
  2790  	if err == nil {
  2791  		t.Fatalf("no error for getrawchangeaddress rpc error")
  2792  	}
  2793  	node.changeAddrErr = nil
  2794  
  2795  	// SignTx error
  2796  	node.signFunc = func(msgTx *wire.MsgTx) (*wire.MsgTx, bool, error) {
  2797  		return nil, false, tErr
  2798  	}
  2799  	_, _, _, err = wallet.Swap(swaps)
  2800  	if err == nil {
  2801  		t.Fatalf("no error for signrawtransactionwithwallet rpc error")
  2802  	}
  2803  
  2804  	// incomplete signatures
  2805  	node.signFunc = func(msgTx *wire.MsgTx) (*wire.MsgTx, bool, error) {
  2806  		return msgTx, false, nil
  2807  	}
  2808  	_, _, _, err = wallet.Swap(swaps)
  2809  	if err == nil {
  2810  		t.Fatalf("no error for incomplete signature rpc error")
  2811  	}
  2812  	node.signFunc = signFunc
  2813  
  2814  	// Make sure we can succeed again.
  2815  	_, _, _, err = wallet.Swap(swaps)
  2816  	if err != nil {
  2817  		t.Fatalf("re-swap error: %v", err)
  2818  	}
  2819  }
  2820  
  2821  type TAuditInfo struct{}
  2822  
  2823  func (ai *TAuditInfo) Recipient() string     { return tPKHAddr.String() }
  2824  func (ai *TAuditInfo) Expiration() time.Time { return time.Time{} }
  2825  func (ai *TAuditInfo) Coin() asset.Coin      { return &tCoin{} }
  2826  func (ai *TAuditInfo) Contract() dex.Bytes   { return nil }
  2827  func (ai *TAuditInfo) SecretHash() dex.Bytes { return nil }
  2828  
  2829  func TestRedeem(t *testing.T) {
  2830  	wallet, node, shutdown := tNewWallet()
  2831  	defer shutdown()
  2832  
  2833  	swapVal := toAtoms(5)
  2834  	secret := randBytes(32)
  2835  	secretHash := sha256.Sum256(secret)
  2836  	lockTime := time.Now().Add(time.Hour * 12)
  2837  	addr := tPKHAddr.String()
  2838  
  2839  	contract, err := dexdcr.MakeContract(addr, addr, secretHash[:], lockTime.Unix(), tChainParams)
  2840  	if err != nil {
  2841  		t.Fatalf("error making swap contract: %v", err)
  2842  	}
  2843  
  2844  	coin := newOutput(tTxHash, 0, swapVal, wire.TxTreeRegular)
  2845  
  2846  	ci := &asset.AuditInfo{
  2847  		Coin:       coin,
  2848  		Contract:   contract,
  2849  		Recipient:  tPKHAddr.String(),
  2850  		Expiration: lockTime,
  2851  	}
  2852  
  2853  	redemption := &asset.Redemption{
  2854  		Spends: ci,
  2855  		Secret: secret,
  2856  	}
  2857  
  2858  	privBytes, _ := hex.DecodeString("b07209eec1a8fb6cfe5cb6ace36567406971a75c330db7101fb21bc679bc5330")
  2859  
  2860  	node.newAddr = tPKHAddr
  2861  	node.privWIF, err = dcrutil.NewWIF(privBytes, tChainParams.PrivateKeyID, dcrec.STEcdsaSecp256k1)
  2862  	if err != nil {
  2863  		t.Fatalf("NewWIF error: %v", err)
  2864  	}
  2865  
  2866  	redemptions := &asset.RedeemForm{
  2867  		Redemptions: []*asset.Redemption{redemption},
  2868  	}
  2869  
  2870  	_, _, feesPaid, err := wallet.Redeem(redemptions)
  2871  	if err != nil {
  2872  		t.Fatalf("redeem error: %v", err)
  2873  	}
  2874  
  2875  	// Check that fees are returned.
  2876  	minFees := optimalFeeRate * uint64(node.sentRawTx.SerializeSize())
  2877  	if feesPaid < minFees {
  2878  		t.Fatalf("sent fees, %d, less than expected minimum fees, %d", feesPaid, minFees)
  2879  	}
  2880  
  2881  	// No audit info
  2882  	redemption.Spends = nil
  2883  	_, _, _, err = wallet.Redeem(redemptions)
  2884  	if err == nil {
  2885  		t.Fatalf("no error for nil AuditInfo")
  2886  	}
  2887  	redemption.Spends = ci
  2888  
  2889  	// Spoofing AuditInfo is not allowed.
  2890  	redemption.Spends = &asset.AuditInfo{}
  2891  	_, _, _, err = wallet.Redeem(redemptions)
  2892  	if err == nil {
  2893  		t.Fatalf("no error for spoofed AuditInfo")
  2894  	}
  2895  	redemption.Spends = ci
  2896  
  2897  	// Wrong secret hash
  2898  	redemption.Secret = randBytes(32)
  2899  	_, _, _, err = wallet.Redeem(redemptions)
  2900  	if err == nil {
  2901  		t.Fatalf("no error for wrong secret")
  2902  	}
  2903  	redemption.Secret = secret
  2904  
  2905  	// too low of value
  2906  	coin.value = 200
  2907  	_, _, _, err = wallet.Redeem(redemptions)
  2908  	if err == nil {
  2909  		t.Fatalf("no error for redemption not worth the fees")
  2910  	}
  2911  	coin.value = swapVal
  2912  
  2913  	// New address error
  2914  	node.newAddrErr = tErr
  2915  	_, _, _, err = wallet.Redeem(redemptions)
  2916  	if err == nil {
  2917  		t.Fatalf("no error for new address error")
  2918  	}
  2919  
  2920  	// Change address error
  2921  	node.changeAddrErr = tErr
  2922  	_, _, _, err = wallet.Redeem(redemptions)
  2923  	if err == nil {
  2924  		t.Fatalf("no error for change address error")
  2925  	}
  2926  	node.changeAddrErr = nil
  2927  
  2928  	// Missing priv key error
  2929  	node.privWIFErr = tErr
  2930  	_, _, _, err = wallet.Redeem(redemptions)
  2931  	if err == nil {
  2932  		t.Fatalf("no error for missing private key")
  2933  	}
  2934  	node.privWIFErr = nil
  2935  
  2936  	// Send error
  2937  	node.sendRawErr = tErr
  2938  	_, _, _, err = wallet.Redeem(redemptions)
  2939  	if err == nil {
  2940  		t.Fatalf("no error for send error")
  2941  	}
  2942  	node.sendRawErr = nil
  2943  
  2944  	// Wrong hash
  2945  	var h chainhash.Hash
  2946  	h[0] = 0x01
  2947  	node.sendRawHash = &h
  2948  	_, _, _, err = wallet.Redeem(redemptions)
  2949  	if err == nil {
  2950  		t.Fatalf("no error for wrong return hash")
  2951  	}
  2952  	node.sendRawHash = nil
  2953  }
  2954  
  2955  const (
  2956  	txCatReceive = "recv"
  2957  	txCatSend    = "send"
  2958  	//txCatGenerate = "generate"
  2959  )
  2960  
  2961  func TestSignMessage(t *testing.T) {
  2962  	wallet, node, shutdown := tNewWallet()
  2963  	defer shutdown()
  2964  
  2965  	vout := uint32(5)
  2966  	privBytes, _ := hex.DecodeString("b07209eec1a8fb6cfe5cb6ace36567406971a75c330db7101fb21bc679bc5330")
  2967  	privKey := secp256k1.PrivKeyFromBytes(privBytes)
  2968  	pubKey := privKey.PubKey()
  2969  
  2970  	msg := randBytes(36)
  2971  	pk := pubKey.SerializeCompressed()
  2972  	msgHash := chainhash.HashB(msg)
  2973  	signature := ecdsa.Sign(privKey, msgHash)
  2974  	sig := signature.Serialize()
  2975  
  2976  	var err error
  2977  	node.privWIF, err = dcrutil.NewWIF(privBytes, tChainParams.PrivateKeyID, dcrec.STEcdsaSecp256k1)
  2978  	if err != nil {
  2979  		t.Fatalf("NewWIF error: %v", err)
  2980  	}
  2981  
  2982  	op := newOutput(tTxHash, vout, 5e7, wire.TxTreeRegular)
  2983  
  2984  	wallet.fundingCoins[op.pt] = &fundingCoin{
  2985  		addr: tPKHAddr.String(),
  2986  	}
  2987  
  2988  	check := func() {
  2989  		pubkeys, sigs, err := wallet.SignMessage(op, msg)
  2990  		if err != nil {
  2991  			t.Fatalf("SignMessage error: %v", err)
  2992  		}
  2993  		if len(pubkeys) != 1 {
  2994  			t.Fatalf("expected 1 pubkey, received %d", len(pubkeys))
  2995  		}
  2996  		if len(sigs) != 1 {
  2997  			t.Fatalf("expected 1 sig, received %d", len(sigs))
  2998  		}
  2999  		if !bytes.Equal(pk, pubkeys[0]) {
  3000  			t.Fatalf("wrong pubkey. expected %x, got %x", pubkeys[0], pk)
  3001  		}
  3002  		if !bytes.Equal(sig, sigs[0]) {
  3003  			t.Fatalf("wrong signature. exptected %x, got %x", sigs[0], sig)
  3004  		}
  3005  	}
  3006  
  3007  	check()
  3008  	delete(wallet.fundingCoins, op.pt)
  3009  	txOut := makeGetTxOutRes(0, 5, nil)
  3010  	txOut.ScriptPubKey.Addresses = []string{tPKHAddr.String()}
  3011  	node.txOutRes[newOutPoint(tTxHash, vout)] = txOut
  3012  	check()
  3013  
  3014  	// gettxout error
  3015  	node.txOutErr = tErr
  3016  	_, _, err = wallet.SignMessage(op, msg)
  3017  	if err == nil {
  3018  		t.Fatalf("no error for gettxout rpc error")
  3019  	}
  3020  	node.txOutErr = nil
  3021  
  3022  	// dumpprivkey error
  3023  	node.privWIFErr = tErr
  3024  	_, _, err = wallet.SignMessage(op, msg)
  3025  	if err == nil {
  3026  		t.Fatalf("no error for dumpprivkey rpc error")
  3027  	}
  3028  	node.privWIFErr = nil
  3029  
  3030  	// bad coin
  3031  	badCoin := &tCoin{id: make([]byte, 15)}
  3032  	_, _, err = wallet.SignMessage(badCoin, msg)
  3033  	if err == nil {
  3034  		t.Fatalf("no error for bad coin")
  3035  	}
  3036  }
  3037  
  3038  func TestAuditContract(t *testing.T) {
  3039  	wallet, _, shutdown := tNewWallet()
  3040  	defer shutdown()
  3041  
  3042  	secretHash, _ := hex.DecodeString("5124208c80d33507befa517c08ed01aa8d33adbf37ecd70fb5f9352f7a51a88d")
  3043  	lockTime := time.Now().Add(time.Hour * 12)
  3044  	addrStr := tPKHAddr.String()
  3045  	contract, err := dexdcr.MakeContract(addrStr, addrStr, secretHash, lockTime.Unix(), tChainParams)
  3046  	if err != nil {
  3047  		t.Fatalf("error making swap contract: %v", err)
  3048  	}
  3049  	addr, _ := stdaddr.NewAddressScriptHashV0(contract, tChainParams)
  3050  	_, pkScript := addr.PaymentScript()
  3051  
  3052  	// Prepare the contract tx data.
  3053  	contractTx := wire.NewMsgTx()
  3054  	contractTx.AddTxIn(&wire.TxIn{})
  3055  	contractTx.AddTxOut(&wire.TxOut{
  3056  		Value:    5 * int64(tLotSize),
  3057  		PkScript: pkScript,
  3058  	})
  3059  	contractTxData, err := contractTx.Bytes()
  3060  	if err != nil {
  3061  		t.Fatalf("error preparing contract txdata: %v", err)
  3062  	}
  3063  
  3064  	contractHash := contractTx.TxHash()
  3065  	contractVout := uint32(0)
  3066  	contractCoinID := toCoinID(&contractHash, contractVout)
  3067  
  3068  	audit, err := wallet.AuditContract(contractCoinID, contract, contractTxData, true)
  3069  	if err != nil {
  3070  		t.Fatalf("audit error: %v", err)
  3071  	}
  3072  	if audit.Recipient != addrStr {
  3073  		t.Fatalf("wrong recipient. wanted '%s', got '%s'", addrStr, audit.Recipient)
  3074  	}
  3075  	if !bytes.Equal(audit.Contract, contract) {
  3076  		t.Fatalf("contract not set to coin redeem script")
  3077  	}
  3078  	if audit.Expiration.Equal(lockTime) {
  3079  		t.Fatalf("wrong lock time. wanted %d, got %d", lockTime.Unix(), audit.Expiration.Unix())
  3080  	}
  3081  
  3082  	// Invalid txid
  3083  	_, err = wallet.AuditContract(make([]byte, 15), contract, contractTxData, false)
  3084  	if err == nil {
  3085  		t.Fatalf("no error for bad txid")
  3086  	}
  3087  
  3088  	// Wrong contract
  3089  	pkh, _ := hex.DecodeString("c6a704f11af6cbee8738ff19fc28cdc70aba0b82")
  3090  	wrongAddr, _ := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(pkh, tChainParams)
  3091  	wrongAddrStr := wrongAddr.String()
  3092  	wrongContract, err := dexdcr.MakeContract(wrongAddrStr, wrongAddrStr, secretHash, lockTime.Unix(), tChainParams)
  3093  	if err != nil {
  3094  		t.Fatalf("error making wrong swap contract: %v", err)
  3095  	}
  3096  	_, err = wallet.AuditContract(contractCoinID, wrongContract, contractTxData, false)
  3097  	if err == nil {
  3098  		t.Fatalf("no error for wrong contract")
  3099  	}
  3100  
  3101  	// Invalid contract
  3102  	_, wrongPkScript := wrongAddr.PaymentScript()
  3103  	_, err = wallet.AuditContract(contractCoinID, wrongPkScript, contractTxData, false) // addrPkScript not a valid contract
  3104  	if err == nil {
  3105  		t.Fatalf("no error for invalid contract")
  3106  	}
  3107  
  3108  	// No txdata
  3109  	_, err = wallet.AuditContract(contractCoinID, contract, nil, false)
  3110  	if err == nil {
  3111  		t.Fatalf("no error for no txdata")
  3112  	}
  3113  
  3114  	// Invalid txdata, zero inputs
  3115  	contractTx.TxIn = nil
  3116  	invalidContractTxData, err := contractTx.Bytes()
  3117  	if err != nil {
  3118  		t.Fatalf("error preparing invalid contract txdata: %v", err)
  3119  	}
  3120  	_, err = wallet.AuditContract(contractCoinID, contract, invalidContractTxData, false)
  3121  	if err == nil {
  3122  		t.Fatalf("no error for unknown txout")
  3123  	}
  3124  
  3125  	// Wrong txdata, wrong output script
  3126  	wrongContractTx := wire.NewMsgTx()
  3127  	wrongContractTx.AddTxIn(&wire.TxIn{})
  3128  	wrongContractTx.AddTxOut(&wire.TxOut{
  3129  		Value:    5 * int64(tLotSize),
  3130  		PkScript: wrongPkScript,
  3131  	})
  3132  	wrongContractTxData, err := wrongContractTx.Bytes()
  3133  	if err != nil {
  3134  		t.Fatalf("error preparing wrong contract txdata: %v", err)
  3135  	}
  3136  	_, err = wallet.AuditContract(contractCoinID, contract, wrongContractTxData, false)
  3137  	if err == nil {
  3138  		t.Fatalf("no error for unknown txout")
  3139  	}
  3140  }
  3141  
  3142  type tReceipt struct {
  3143  	coin       *tCoin
  3144  	contract   []byte
  3145  	expiration uint64
  3146  }
  3147  
  3148  func (r *tReceipt) Expiration() time.Time { return time.Unix(int64(r.expiration), 0).UTC() }
  3149  func (r *tReceipt) Coin() asset.Coin      { return r.coin }
  3150  func (r *tReceipt) Contract() dex.Bytes   { return r.contract }
  3151  
  3152  func TestFindRedemption(t *testing.T) {
  3153  	wallet, node, shutdown := tNewWallet()
  3154  	defer shutdown()
  3155  
  3156  	_, bestBlockHeight, err := node.GetBestBlock(context.Background())
  3157  	if err != nil {
  3158  		t.Fatalf("unexpected GetBestBlock error: %v", err)
  3159  	}
  3160  
  3161  	contractHeight := bestBlockHeight + 1
  3162  	contractVout := uint32(1)
  3163  
  3164  	secret := randBytes(32)
  3165  	secretHash := sha256.Sum256(secret)
  3166  	lockTime := time.Now().Add(time.Hour * 12)
  3167  	addrStr := tPKHAddr.String()
  3168  	contract, err := dexdcr.MakeContract(addrStr, addrStr, secretHash[:], lockTime.Unix(), tChainParams)
  3169  	if err != nil {
  3170  		t.Fatalf("error making swap contract: %v", err)
  3171  	}
  3172  	contractAddr, _ := stdaddr.NewAddressScriptHashV0(contract, tChainParams)
  3173  	_, contractP2SHScript := contractAddr.PaymentScript()
  3174  
  3175  	tPKHAddrV3, _ := stdaddr.DecodeAddress(tPKHAddr.String(), tChainParams)
  3176  	_, otherScript := tPKHAddrV3.PaymentScript()
  3177  
  3178  	redemptionScript, _ := dexdcr.RedeemP2SHContract(contract, randBytes(73), randBytes(33), secret)
  3179  	otherSpendScript, _ := txscript.NewScriptBuilder().
  3180  		AddData(randBytes(73)).
  3181  		AddData(randBytes(33)).
  3182  		Script()
  3183  
  3184  	// Prepare and add the contract transaction to the blockchain. Put the pay-to-contract script at index 1.
  3185  	inputs := []*wire.TxIn{makeRPCVin(&chainhash.Hash{}, 0, otherSpendScript)}
  3186  	outputScripts := []dex.Bytes{otherScript, contractP2SHScript}
  3187  	contractTx := makeRawTx(inputs, outputScripts)
  3188  	contractTxHash := contractTx.TxHash()
  3189  	coinID := toCoinID(&contractTxHash, contractVout)
  3190  	blockHash, _ := node.blockchain.addRawTx(contractHeight, contractTx)
  3191  	txHex, err := makeTxHex(inputs, outputScripts)
  3192  	if err != nil {
  3193  		t.Fatalf("error generating hex for contract tx: %v", err)
  3194  	}
  3195  	walletTx := &walletjson.GetTransactionResult{
  3196  		BlockHash:     blockHash.String(),
  3197  		Confirmations: 1,
  3198  		Details: []walletjson.GetTransactionDetailsResult{
  3199  			{
  3200  				Address:  contractAddr.String(),
  3201  				Category: txCatSend,
  3202  				Vout:     contractVout,
  3203  			},
  3204  		},
  3205  		Hex: txHex,
  3206  	}
  3207  
  3208  	// Add an intermediate block for good measure.
  3209  	node.blockchain.addRawTx(contractHeight+1, dummyTx())
  3210  
  3211  	// Prepare the redemption tx inputs including an input that spends the contract output.
  3212  	inputs = append(inputs, makeRPCVin(&contractTxHash, contractVout, redemptionScript))
  3213  
  3214  	// Add the redemption to mempool and check if wallet.FindRedemption finds it.
  3215  	redeemTx := makeRawTx(inputs, []dex.Bytes{otherScript})
  3216  	node.blockchain.addRawTx(-1, redeemTx)
  3217  	_, checkSecret, err := wallet.FindRedemption(tCtx, coinID, nil)
  3218  	if err != nil {
  3219  		t.Fatalf("error finding redemption: %v", err)
  3220  	}
  3221  	if !bytes.Equal(checkSecret, secret) {
  3222  		t.Fatalf("wrong secret. expected %x, got %x", secret, checkSecret)
  3223  	}
  3224  
  3225  	node.walletTxFn = func() (*walletjson.GetTransactionResult, error) {
  3226  		return walletTx, nil
  3227  	}
  3228  
  3229  	// Move the redemption to a new block and check if wallet.FindRedemption finds it.
  3230  	_, redeemBlock := node.blockchain.addRawTx(contractHeight+2, makeRawTx(inputs, []dex.Bytes{otherScript}))
  3231  	_, checkSecret, err = wallet.FindRedemption(tCtx, coinID, nil)
  3232  	if err != nil {
  3233  		t.Fatalf("error finding redemption: %v", err)
  3234  	}
  3235  	if !bytes.Equal(checkSecret, secret) {
  3236  		t.Fatalf("wrong secret. expected %x, got %x", secret, checkSecret)
  3237  	}
  3238  
  3239  	// gettransaction error
  3240  	node.walletTxFn = func() (*walletjson.GetTransactionResult, error) {
  3241  		return walletTx, tErr
  3242  	}
  3243  	_, _, err = wallet.FindRedemption(tCtx, coinID, nil)
  3244  	if err == nil {
  3245  		t.Fatalf("no error for gettransaction rpc error")
  3246  	}
  3247  	node.walletTxFn = func() (*walletjson.GetTransactionResult, error) {
  3248  		return walletTx, nil
  3249  	}
  3250  
  3251  	// getcfilterv2 error
  3252  	node.rawErr[methodGetCFilterV2] = tErr
  3253  	_, _, err = wallet.FindRedemption(tCtx, coinID, nil)
  3254  	if err == nil {
  3255  		t.Fatalf("no error for getcfilterv2 rpc error")
  3256  	}
  3257  	delete(node.rawErr, methodGetCFilterV2)
  3258  
  3259  	// missing redemption
  3260  	redeemBlock.Transactions[0].TxIn[1].PreviousOutPoint.Hash = chainhash.Hash{}
  3261  	ctx, cancel := context.WithTimeout(tCtx, 2*time.Second)
  3262  	defer cancel() // ctx should auto-cancel after 2 seconds, but this is apparently good practice to prevent leak
  3263  	_, k, err := wallet.FindRedemption(ctx, coinID, nil)
  3264  	if ctx.Err() == nil || k != nil {
  3265  		// Expected ctx to cancel after timeout and no secret should be found.
  3266  		t.Fatalf("unexpected result for missing redemption: secret: %v, err: %v", k, err)
  3267  	}
  3268  	redeemBlock.Transactions[0].TxIn[1].PreviousOutPoint.Hash = contractTxHash
  3269  
  3270  	// Canceled context
  3271  	deadCtx, cancelCtx := context.WithCancel(tCtx)
  3272  	cancelCtx()
  3273  	_, _, err = wallet.FindRedemption(deadCtx, coinID, nil)
  3274  	if err == nil {
  3275  		t.Fatalf("no error for canceled context")
  3276  	}
  3277  
  3278  	// Expect FindRedemption to error because of bad input sig.
  3279  	redeemBlock.Transactions[0].TxIn[1].SignatureScript = randBytes(100)
  3280  	_, _, err = wallet.FindRedemption(tCtx, coinID, nil)
  3281  	if err == nil {
  3282  		t.Fatalf("no error for wrong redemption")
  3283  	}
  3284  	redeemBlock.Transactions[0].TxIn[1].SignatureScript = redemptionScript
  3285  
  3286  	// Wrong script type for output
  3287  	walletTx.Hex, _ = makeTxHex(inputs, []dex.Bytes{otherScript, otherScript})
  3288  	_, _, err = wallet.FindRedemption(tCtx, coinID, nil)
  3289  	if err == nil {
  3290  		t.Fatalf("no error for wrong script type")
  3291  	}
  3292  	walletTx.Hex = txHex
  3293  
  3294  	// Sanity check to make sure it passes again.
  3295  	_, _, err = wallet.FindRedemption(tCtx, coinID, nil)
  3296  	if err != nil {
  3297  		t.Fatalf("error after clearing errors: %v", err)
  3298  	}
  3299  }
  3300  
  3301  func TestRefund(t *testing.T) {
  3302  	wallet, node, shutdown := tNewWallet()
  3303  	defer shutdown()
  3304  
  3305  	secret := randBytes(32)
  3306  	secretHash := sha256.Sum256(secret)
  3307  	lockTime := time.Now().Add(time.Hour * 12)
  3308  	addrStr := tPKHAddr.String()
  3309  	contract, err := dexdcr.MakeContract(addrStr, addrStr, secretHash[:], lockTime.Unix(), tChainParams)
  3310  	if err != nil {
  3311  		t.Fatalf("error making swap contract: %v", err)
  3312  	}
  3313  	const feeSuggestion = 100
  3314  
  3315  	tipHash, tipHeight := node.getBestBlock()
  3316  	var confs int64 = 1
  3317  	if tipHeight > 1 {
  3318  		confs = 2
  3319  	}
  3320  
  3321  	bigTxOut := makeGetTxOutRes(confs, 5, nil)
  3322  	bigOutID := newOutPoint(tTxHash, 0)
  3323  	node.txOutRes[bigOutID] = bigTxOut
  3324  	node.txOutRes[bigOutID].BestBlock = tipHash.String() // required to calculate the block for the output
  3325  	node.changeAddr = tPKHAddr
  3326  	node.newAddr = tPKHAddr
  3327  
  3328  	privBytes, _ := hex.DecodeString("b07209eec1a8fb6cfe5cb6ace36567406971a75c330db7101fb21bc679bc5330")
  3329  	node.privWIF, err = dcrutil.NewWIF(privBytes, tChainParams.PrivateKeyID, dcrec.STEcdsaSecp256k1)
  3330  	if err != nil {
  3331  		t.Fatalf("NewWIF error: %v", err)
  3332  	}
  3333  
  3334  	contractOutput := newOutput(tTxHash, 0, 1e8, wire.TxTreeRegular)
  3335  	_, err = wallet.Refund(contractOutput.ID(), contract, feeSuggestion)
  3336  	if err != nil {
  3337  		t.Fatalf("refund error: %v", err)
  3338  	}
  3339  
  3340  	// Invalid coin
  3341  	badReceipt := &tReceipt{
  3342  		coin: &tCoin{id: make([]byte, 15)},
  3343  	}
  3344  	_, err = wallet.Refund(badReceipt.coin.id, badReceipt.contract, feeSuggestion)
  3345  	if err == nil {
  3346  		t.Fatalf("no error for bad receipt")
  3347  	}
  3348  
  3349  	// gettxout error
  3350  	node.txOutErr = tErr
  3351  	_, err = wallet.Refund(contractOutput.ID(), contract, feeSuggestion)
  3352  	if err == nil {
  3353  		t.Fatalf("no error for missing utxo")
  3354  	}
  3355  	node.txOutErr = nil
  3356  
  3357  	// bad contract
  3358  	badContractOutput := newOutput(tTxHash, 0, 1e8, wire.TxTreeRegular)
  3359  	_, err = wallet.Refund(badContractOutput.ID(), randBytes(50), feeSuggestion)
  3360  	if err == nil {
  3361  		t.Fatalf("no error for bad contract")
  3362  	}
  3363  
  3364  	// Too small.
  3365  	node.txOutRes[bigOutID] = newTxOutResult(nil, 100, 2)
  3366  	_, err = wallet.Refund(contractOutput.ID(), contract, feeSuggestion)
  3367  	if err == nil {
  3368  		t.Fatalf("no error for value < fees")
  3369  	}
  3370  	node.txOutRes[bigOutID] = bigTxOut
  3371  
  3372  	// signature error
  3373  	node.privWIFErr = tErr
  3374  	_, err = wallet.Refund(contractOutput.ID(), contract, feeSuggestion)
  3375  	if err == nil {
  3376  		t.Fatalf("no error for dumpprivkey rpc error")
  3377  	}
  3378  	node.privWIFErr = nil
  3379  
  3380  	// send error
  3381  	node.sendRawErr = tErr
  3382  	_, err = wallet.Refund(contractOutput.ID(), contract, feeSuggestion)
  3383  	if err == nil {
  3384  		t.Fatalf("no error for sendrawtransaction rpc error")
  3385  	}
  3386  	node.sendRawErr = nil
  3387  
  3388  	// bad checkhash
  3389  	var badHash chainhash.Hash
  3390  	badHash[0] = 0x05
  3391  	node.sendRawHash = &badHash
  3392  	_, err = wallet.Refund(contractOutput.ID(), contract, feeSuggestion)
  3393  	if err == nil {
  3394  		t.Fatalf("no error for tx hash")
  3395  	}
  3396  	node.sendRawHash = nil
  3397  
  3398  	// Sanity check that we can succeed again.
  3399  	_, err = wallet.Refund(contractOutput.ID(), contract, feeSuggestion)
  3400  	if err != nil {
  3401  		t.Fatalf("re-refund error: %v", err)
  3402  	}
  3403  }
  3404  
  3405  type tSenderType byte
  3406  
  3407  const (
  3408  	tSendSender tSenderType = iota
  3409  	tWithdrawSender
  3410  )
  3411  
  3412  func testSender(t *testing.T, senderType tSenderType) {
  3413  	wallet, node, shutdown := tNewWallet()
  3414  	defer shutdown()
  3415  
  3416  	var sendVal uint64 = 1e8
  3417  	var unspentVal uint64 = 100e8
  3418  	const feeSuggestion = 100
  3419  	funName := "Send"
  3420  	sender := func(addr string, val uint64) (asset.Coin, error) {
  3421  		return wallet.Send(addr, val, feeSuggestion)
  3422  	}
  3423  	if senderType == tWithdrawSender {
  3424  		funName = "Withdraw"
  3425  		// For withdraw, test with unspent total = withdraw value
  3426  		unspentVal = sendVal
  3427  		sender = func(addr string, val uint64) (asset.Coin, error) {
  3428  			return wallet.Withdraw(addr, val, feeSuggestion)
  3429  		}
  3430  	}
  3431  
  3432  	addr := tPKHAddr.String()
  3433  	node.changeAddr = tPKHAddr
  3434  
  3435  	node.unspent = []walletjson.ListUnspentResult{{
  3436  		TxID:          tTxID,
  3437  		Address:       tPKHAddr.String(),
  3438  		Account:       tAcctName,
  3439  		Amount:        float64(unspentVal) / 1e8,
  3440  		Confirmations: 5,
  3441  		ScriptPubKey:  hex.EncodeToString(tP2PKHScript),
  3442  		Spendable:     true,
  3443  	}}
  3444  	//node.unspent = append(node.unspent, node.unspent[0])
  3445  
  3446  	_, err := sender(addr, sendVal)
  3447  	if err != nil {
  3448  		t.Fatalf(funName+" error: %v", err)
  3449  	}
  3450  
  3451  	// invalid address
  3452  	_, err = sender("badaddr", sendVal)
  3453  	if err == nil {
  3454  		t.Fatalf("no error for bad address: %v", err)
  3455  	}
  3456  
  3457  	// GetRawChangeAddress error
  3458  	if senderType == tSendSender { // withdraw test does not get a change address
  3459  		node.changeAddrErr = tErr
  3460  		_, err = sender(addr, sendVal)
  3461  		if err == nil {
  3462  			t.Fatalf("no error for rawchangeaddress: %v", err)
  3463  		}
  3464  		node.changeAddrErr = nil
  3465  	}
  3466  
  3467  	// good again
  3468  	_, err = sender(addr, sendVal)
  3469  	if err != nil {
  3470  		t.Fatalf(funName+" error afterwards: %v", err)
  3471  	}
  3472  }
  3473  
  3474  func TestWithdraw(t *testing.T) {
  3475  	testSender(t, tWithdrawSender)
  3476  }
  3477  
  3478  func TestSend(t *testing.T) {
  3479  	testSender(t, tSendSender)
  3480  }
  3481  
  3482  func Test_withdraw(t *testing.T) {
  3483  	wallet, node, shutdown := tNewWallet()
  3484  	defer shutdown()
  3485  
  3486  	address := tPKHAddr.String()
  3487  	node.changeAddr = tPKHAddr
  3488  
  3489  	var unspentVal uint64 = 100e8
  3490  	node.unspent = []walletjson.ListUnspentResult{{
  3491  		TxID:          tTxID,
  3492  		Address:       tPKHAddr.String(),
  3493  		Account:       tAcctName,
  3494  		Amount:        float64(unspentVal) / 1e8,
  3495  		Confirmations: 5,
  3496  		ScriptPubKey:  hex.EncodeToString(tP2PKHScript),
  3497  		Spendable:     true,
  3498  	}}
  3499  
  3500  	addr, err := stdaddr.DecodeAddress(address, tChainParams)
  3501  	if err != nil {
  3502  		t.Fatal(err)
  3503  	}
  3504  
  3505  	// This should make a msgTx with one input and one output.
  3506  	msgTx, val, err := wallet.withdraw(addr, unspentVal, optimalFeeRate)
  3507  	if err != nil {
  3508  		t.Fatal(err)
  3509  	}
  3510  	if len(msgTx.TxOut) != 1 {
  3511  		t.Fatalf("expected 1 output, got %d", len(msgTx.TxOut))
  3512  	}
  3513  	if val != uint64(msgTx.TxOut[0].Value) {
  3514  		t.Errorf("expected non-change output to be %d, got %d", val, msgTx.TxOut[0].Value)
  3515  	}
  3516  	if val >= unspentVal {
  3517  		t.Errorf("expected output to be have fees deducted")
  3518  	}
  3519  
  3520  	// Then with unspentVal just slightly larger than send. This should still
  3521  	// make a msgTx with one output, but larger than before. The sent value is
  3522  	// SMALLER than requested because it was required for fees.
  3523  	avail := unspentVal + 77
  3524  	node.unspent[0].Amount = float64(avail) / 1e8
  3525  	msgTx, val, err = wallet.withdraw(addr, unspentVal, optimalFeeRate)
  3526  	if err != nil {
  3527  		t.Fatal(err)
  3528  	}
  3529  	if len(msgTx.TxOut) != 1 {
  3530  		t.Fatalf("expected 1 output, got %d", len(msgTx.TxOut))
  3531  	}
  3532  	if val != uint64(msgTx.TxOut[0].Value) {
  3533  		t.Errorf("expected non-change output to be %d, got %d", val, msgTx.TxOut[0].Value)
  3534  	}
  3535  	if val >= unspentVal {
  3536  		t.Errorf("expected output to be have fees deducted")
  3537  	}
  3538  
  3539  	// Still no change, but this time the sent value is LARGER than requested
  3540  	// because change would be dust, and we don't over pay fees.
  3541  	avail = unspentVal + 3000
  3542  	node.unspent[0].Amount = float64(avail) / 1e8
  3543  	msgTx, val, err = wallet.withdraw(addr, unspentVal, optimalFeeRate)
  3544  	if err != nil {
  3545  		t.Fatal(err)
  3546  	}
  3547  	if len(msgTx.TxOut) != 1 {
  3548  		t.Fatalf("expected 1 output, got %d", len(msgTx.TxOut))
  3549  	}
  3550  	if val <= unspentVal {
  3551  		t.Errorf("expected output to be more thrifty")
  3552  	}
  3553  
  3554  	// Then with unspentVal considerably larger than (double) send. This should
  3555  	// make a msgTx with two outputs as the change is no longer dust. The change
  3556  	// should be exactly unspentVal and the sent amount should be
  3557  	// unspentVal-fees.
  3558  	node.unspent[0].Amount = float64(unspentVal*2) / 1e8
  3559  	msgTx, val, err = wallet.withdraw(addr, unspentVal, optimalFeeRate)
  3560  	if err != nil {
  3561  		t.Fatal(err)
  3562  	}
  3563  	if len(msgTx.TxOut) != 2 {
  3564  		t.Fatalf("expected 2 outputs, got %d", len(msgTx.TxOut))
  3565  	}
  3566  	if val != uint64(msgTx.TxOut[0].Value) {
  3567  		t.Errorf("expected non-change output to be %d, got %d", val, msgTx.TxOut[0].Value)
  3568  	}
  3569  	if unspentVal != uint64(msgTx.TxOut[1].Value) {
  3570  		t.Errorf("expected change output to be %d, got %d", unspentVal, msgTx.TxOut[1].Value)
  3571  	}
  3572  }
  3573  
  3574  func Test_sendToAddress(t *testing.T) {
  3575  	wallet, node, shutdown := tNewWallet()
  3576  	defer shutdown()
  3577  
  3578  	address := tPKHAddr.String()
  3579  	node.changeAddr = tPKHAddr
  3580  
  3581  	var unspentVal uint64 = 100e8
  3582  	node.unspent = []walletjson.ListUnspentResult{{
  3583  		TxID:          tTxID,
  3584  		Address:       tPKHAddr.String(),
  3585  		Account:       tAcctName,
  3586  		Amount:        float64(unspentVal) / 1e8,
  3587  		Confirmations: 5,
  3588  		ScriptPubKey:  hex.EncodeToString(tP2PKHScript),
  3589  		Spendable:     true,
  3590  	}}
  3591  
  3592  	addr, err := stdaddr.DecodeAddress(address, tChainParams)
  3593  	if err != nil {
  3594  		t.Fatal(err)
  3595  	}
  3596  
  3597  	// This should return an error, not enough funds to send.
  3598  	_, _, _, err = wallet.sendToAddress(addr, unspentVal, optimalFeeRate)
  3599  	if err == nil {
  3600  		t.Fatal("Expected error, not enough funds to send.")
  3601  	}
  3602  
  3603  	// With a lower send value, send should be successful.
  3604  	var sendVal uint64 = 10e8
  3605  	node.unspent[0].Amount = float64(unspentVal)
  3606  	msgTx, val, _, err := wallet.sendToAddress(addr, sendVal, optimalFeeRate)
  3607  	if err != nil {
  3608  		t.Fatal(err)
  3609  	}
  3610  	if val != uint64(msgTx.TxOut[0].Value) {
  3611  		t.Errorf("expected non-change output to be %d, got %d", val, msgTx.TxOut[0].Value)
  3612  	}
  3613  	if val != sendVal {
  3614  		t.Errorf("expected non-change output to be %d, got %d", sendVal, val)
  3615  	}
  3616  }
  3617  
  3618  func TestLookupTxOutput(t *testing.T) {
  3619  	wallet, node, shutdown := tNewWallet()
  3620  	defer shutdown()
  3621  
  3622  	coinID := make([]byte, 36)
  3623  	copy(coinID[:32], tTxHash[:])
  3624  	op := newOutPoint(tTxHash, 0)
  3625  
  3626  	// Bad output coin
  3627  	op.vout = 10
  3628  	_, _, spent, err := wallet.lookupTxOutput(context.Background(), &op.txHash, op.vout)
  3629  	if err == nil {
  3630  		t.Fatalf("no error for bad output coin")
  3631  	}
  3632  	if spent != 0 {
  3633  		t.Fatalf("spent is not 0 for bad output coin")
  3634  	}
  3635  	op.vout = 0
  3636  
  3637  	// Add the txOutRes with 2 confs and BestBlock correctly set.
  3638  	node.txOutRes[op] = makeGetTxOutRes(2, 1, tP2PKHScript)
  3639  	_, confs, spent, err := wallet.lookupTxOutput(context.Background(), &op.txHash, op.vout)
  3640  	if err != nil {
  3641  		t.Fatalf("unexpected error for gettxout path: %v", err)
  3642  	}
  3643  	if confs != 2 {
  3644  		t.Fatalf("confs not retrieved from gettxout path. expected 2, got %d", confs)
  3645  	}
  3646  	if spent != 0 {
  3647  		t.Fatalf("expected spent = 0 for gettxout path, got true")
  3648  	}
  3649  
  3650  	// gettransaction error
  3651  	delete(node.txOutRes, op)
  3652  	walletTx := &walletjson.GetTransactionResult{}
  3653  	node.walletTxFn = func() (*walletjson.GetTransactionResult, error) {
  3654  		return walletTx, tErr
  3655  	}
  3656  	_, _, spent, err = wallet.lookupTxOutput(context.Background(), &op.txHash, op.vout)
  3657  	if err == nil {
  3658  		t.Fatalf("no error for gettransaction error")
  3659  	}
  3660  	if spent != 0 {
  3661  		t.Fatalf("spent is not 0 with gettransaction error")
  3662  	}
  3663  	node.walletTxFn = func() (*walletjson.GetTransactionResult, error) {
  3664  		return walletTx, nil
  3665  	}
  3666  
  3667  	// wallet.lookupTxOutput will check if the tx is confirmed, its hex
  3668  	// is valid and contains an output at index 0, for the output to be
  3669  	// considered spent.
  3670  	tx := wire.NewMsgTx()
  3671  	tx.AddTxIn(&wire.TxIn{})
  3672  	tx.AddTxOut(&wire.TxOut{
  3673  		PkScript: tP2PKHScript,
  3674  	})
  3675  	txHex, err := msgTxToHex(tx)
  3676  	if err != nil {
  3677  		t.Fatalf("error preparing tx hex with 1 output: %v", err)
  3678  	}
  3679  	walletTx.Hex = txHex // unconfirmed = unspent
  3680  
  3681  	_, _, spent, err = wallet.lookupTxOutput(context.Background(), &op.txHash, op.vout)
  3682  	if err != nil {
  3683  		t.Fatalf("unexpected error for gettransaction path (unconfirmed): %v", err)
  3684  	}
  3685  	if spent != 0 {
  3686  		t.Fatalf("expected spent = 0 for gettransaction path (unconfirmed), got true")
  3687  	}
  3688  
  3689  	// Confirmed wallet tx without gettxout response is spent.
  3690  	walletTx.Confirmations = 2
  3691  	_, _, spent, err = wallet.lookupTxOutput(context.Background(), &op.txHash, op.vout)
  3692  	if err != nil {
  3693  		t.Fatalf("unexpected error for gettransaction path (confirmed): %v", err)
  3694  	}
  3695  	if spent != 1 {
  3696  		t.Fatalf("expected spent = 1 for gettransaction path (confirmed), got false")
  3697  	}
  3698  
  3699  	// In spv mode, spent status is unknown without a block filters scan.
  3700  	wallet.wallet.(*rpcWallet).spvMode = true
  3701  	_, _, spent, err = wallet.lookupTxOutput(context.Background(), &op.txHash, op.vout)
  3702  	if err != nil {
  3703  		t.Fatalf("unexpected error for spv gettransaction path (non-wallet output): %v", err)
  3704  	}
  3705  	if spent != -1 {
  3706  		t.Fatalf("expected spent = -1 for spv gettransaction path (non-wallet output), got true")
  3707  	}
  3708  
  3709  	// In spv mode, output is spent if it pays to the wallet (but no txOutRes).
  3710  	/* what is the use case for this since a contract never pays to wallet?
  3711  	node.walletTx.Details = []walletjson.GetTransactionDetailsResult{{
  3712  		Vout:     0,
  3713  		Category: "receive", // output at index 0 pays to the wallet
  3714  	}}
  3715  	_, _, spent, err = wallet.lookupTxOutput(context.Background(), &op.txHash, op.vout)
  3716  	if err != nil {
  3717  		t.Fatalf("unexpected error for spv gettransaction path (wallet output): %v", err)
  3718  	}
  3719  	if spent != 1 {
  3720  		t.Fatalf("expected spent = 1 for spv gettransaction path (wallet output), got false")
  3721  	}
  3722  	*/
  3723  }
  3724  
  3725  func TestSendEdges(t *testing.T) {
  3726  	wallet, node, shutdown := tNewWallet()
  3727  	defer shutdown()
  3728  
  3729  	const feeRate uint64 = 3
  3730  
  3731  	const swapVal = 2e8 // leaving untyped. NewTxOut wants int64
  3732  
  3733  	contractAddr, _ := stdaddr.NewAddressScriptHashV0(randBytes(20), tChainParams)
  3734  	// See dexdcr.IsDust for the source of this dustCoverage voodoo.
  3735  	dustCoverage := (dexdcr.P2PKHOutputSize + 165) * feeRate * 3
  3736  	dexReqFees := dexdcr.InitTxSize * feeRate
  3737  
  3738  	_, pkScript := contractAddr.PaymentScript()
  3739  
  3740  	newBaseTx := func(funding uint64) *wire.MsgTx {
  3741  		baseTx := wire.NewMsgTx()
  3742  		baseTx.AddTxIn(wire.NewTxIn(new(wire.OutPoint), int64(funding), nil))
  3743  		baseTx.AddTxOut(wire.NewTxOut(swapVal, pkScript))
  3744  		return baseTx
  3745  	}
  3746  
  3747  	node.signFunc = func(tx *wire.MsgTx) (*wire.MsgTx, bool, error) {
  3748  		return signFunc(tx, dexdcr.P2PKHSigScriptSize)
  3749  	}
  3750  
  3751  	tests := []struct {
  3752  		name      string
  3753  		funding   uint64
  3754  		expChange bool
  3755  	}{
  3756  		{
  3757  			name:    "not enough for change output",
  3758  			funding: swapVal + dexReqFees - 1,
  3759  		},
  3760  		{
  3761  			// Still dust here, but a different path.
  3762  			name:    "exactly enough for change output",
  3763  			funding: swapVal + dexReqFees,
  3764  		},
  3765  		{
  3766  			name:    "more than enough for change output but still dust",
  3767  			funding: swapVal + dexReqFees + 1,
  3768  		},
  3769  		{
  3770  			name:    "1 atom short to not be dust",
  3771  			funding: swapVal + dexReqFees + dustCoverage - 1,
  3772  		},
  3773  		{
  3774  			name:      "exactly enough to not be dust",
  3775  			funding:   swapVal + dexReqFees + dustCoverage,
  3776  			expChange: true,
  3777  		},
  3778  	}
  3779  
  3780  	// tPKHAddrV3, _ := stdaddr.DecodeAddress(tPKHAddr.String(), tChainParams)
  3781  	node.changeAddr = tPKHAddr
  3782  
  3783  	for _, tt := range tests {
  3784  		tx, err := wallet.sendWithReturn(newBaseTx(tt.funding), feeRate, -1)
  3785  		if err != nil {
  3786  			t.Fatalf("sendWithReturn error: %v", err)
  3787  		}
  3788  
  3789  		if len(tx.TxOut) == 1 && tt.expChange {
  3790  			t.Fatalf("%s: no change added", tt.name)
  3791  		} else if len(tx.TxOut) == 2 && !tt.expChange {
  3792  			t.Fatalf("%s: change output added for dust. Output value = %d", tt.name, tx.TxOut[1].Value)
  3793  		}
  3794  	}
  3795  }
  3796  
  3797  func TestSyncStatus(t *testing.T) {
  3798  	wallet, node, shutdown := tNewWallet()
  3799  	defer shutdown()
  3800  
  3801  	node.rawRes[methodSyncStatus], node.rawErr[methodSyncStatus] = json.Marshal(&walletjson.SyncStatusResult{
  3802  		Synced:               true,
  3803  		InitialBlockDownload: false,
  3804  		HeadersFetchProgress: 1,
  3805  	})
  3806  	ss, err := wallet.SyncStatus()
  3807  	if err != nil {
  3808  		t.Fatalf("SyncStatus error (synced expected): %v", err)
  3809  	}
  3810  	if !ss.Synced {
  3811  		t.Fatalf("synced = false for progress=1")
  3812  	}
  3813  	if ss.BlockProgress() < 1 {
  3814  		t.Fatalf("progress not complete with sync true")
  3815  	}
  3816  
  3817  	node.rawErr[methodSyncStatus] = tErr
  3818  	_, err = wallet.SyncStatus()
  3819  	if err == nil {
  3820  		t.Fatalf("SyncStatus error not propagated")
  3821  	}
  3822  	node.rawErr[methodSyncStatus] = nil
  3823  
  3824  	nodeSyncStatusResult := &walletjson.SyncStatusResult{
  3825  		Synced:               false,
  3826  		InitialBlockDownload: false,
  3827  		HeadersFetchProgress: 0.5, // Headers: 200, WalletTip: 100
  3828  	}
  3829  	node.rawRes[methodSyncStatus], node.rawErr[methodSyncStatus] = json.Marshal(nodeSyncStatusResult)
  3830  	node.rawRes[methodGetPeerInfo], node.rawErr[methodGetPeerInfo] = json.Marshal([]*walletjson.GetPeerInfoResult{{StartingHeight: 1000}})
  3831  
  3832  	ss, err = wallet.SyncStatus()
  3833  	if err != nil {
  3834  		t.Fatalf("SyncStatus error (half-synced): %v", err)
  3835  	}
  3836  	if ss.Synced {
  3837  		t.Fatalf("synced = true for progress=0.5")
  3838  	}
  3839  	if ss.BlockProgress() != nodeSyncStatusResult.HeadersFetchProgress {
  3840  		t.Fatalf("progress out of range. Expected %.2f, got %.2f", nodeSyncStatusResult.HeadersFetchProgress, ss.BlockProgress())
  3841  	}
  3842  	if ss.Blocks != 500 {
  3843  		t.Fatalf("wrong header sync height. expected 500, got %d", ss.Blocks)
  3844  	}
  3845  }
  3846  
  3847  func TestPreSwap(t *testing.T) {
  3848  	wallet, node, shutdown := tNewWallet()
  3849  	defer shutdown()
  3850  
  3851  	// See math from TestFundEdges. 10 lots with max fee rate of 34 sats/vbyte.
  3852  
  3853  	swapVal := uint64(1e8)
  3854  	lots := swapVal / tLotSize // 10 lots
  3855  
  3856  	const totalBytes = 2510
  3857  	// base_best_case_bytes = swap_size_base + backing_bytes
  3858  	//                      = 85 + 166 = 251
  3859  	const bestCaseBytes = 251 // i.e. swapSize
  3860  
  3861  	backingFees := uint64(totalBytes) * tDCR.MaxFeeRate // total_bytes * fee_rate
  3862  
  3863  	minReq := swapVal + backingFees
  3864  
  3865  	fees := uint64(totalBytes) * tDCR.MaxFeeRate
  3866  	p2pkhUnspent := walletjson.ListUnspentResult{
  3867  		TxID:          tTxID,
  3868  		Address:       tPKHAddr.String(),
  3869  		Account:       tAcctName,
  3870  		Amount:        float64(swapVal+fees-1) / 1e8, // one atom less than needed
  3871  		Confirmations: 5,
  3872  		ScriptPubKey:  hex.EncodeToString(tP2PKHScript),
  3873  		Spendable:     true,
  3874  	}
  3875  
  3876  	node.unspent = []walletjson.ListUnspentResult{p2pkhUnspent}
  3877  
  3878  	form := &asset.PreSwapForm{
  3879  		Version:       version,
  3880  		LotSize:       tLotSize,
  3881  		Lots:          lots,
  3882  		MaxFeeRate:    tDCR.MaxFeeRate,
  3883  		Immediate:     false,
  3884  		FeeSuggestion: feeSuggestion,
  3885  		// Redeem fields unneeded
  3886  	}
  3887  
  3888  	node.unspent[0].Amount = float64(minReq) / 1e8
  3889  
  3890  	// Initial success.
  3891  	preSwap, err := wallet.PreSwap(form)
  3892  	if err != nil {
  3893  		t.Fatalf("PreSwap error: %v", err)
  3894  	}
  3895  
  3896  	maxFees := totalBytes * tDCR.MaxFeeRate
  3897  	estHighFees := totalBytes * feeSuggestion
  3898  	estLowFees := bestCaseBytes * feeSuggestion
  3899  	checkSwapEstimate(t, preSwap.Estimate, lots, swapVal, maxFees, estHighFees, estLowFees)
  3900  
  3901  	// Too little funding is an error.
  3902  	node.unspent[0].Amount = float64(minReq-1) / 1e8
  3903  	_, err = wallet.PreSwap(form)
  3904  	if err == nil {
  3905  		t.Fatalf("no PreSwap error for not enough funds")
  3906  	}
  3907  	node.unspent[0].Amount = float64(minReq) / 1e8
  3908  
  3909  	// Success again.
  3910  	_, err = wallet.PreSwap(form)
  3911  	if err != nil {
  3912  		t.Fatalf("PreSwap error: %v", err)
  3913  	}
  3914  }
  3915  
  3916  func TestPreRedeem(t *testing.T) {
  3917  	wallet, _, shutdown := tNewWallet()
  3918  	defer shutdown()
  3919  
  3920  	preRedeem, err := wallet.PreRedeem(&asset.PreRedeemForm{
  3921  		Version: version,
  3922  		Lots:    5,
  3923  	})
  3924  	// Shouldn't actually be any path to error.
  3925  	if err != nil {
  3926  		t.Fatalf("PreRedeem non-segwit error: %v", err)
  3927  	}
  3928  
  3929  	// Just a sanity check.
  3930  	if preRedeem.Estimate.RealisticBestCase >= preRedeem.Estimate.RealisticWorstCase {
  3931  		t.Fatalf("best case > worst case")
  3932  	}
  3933  }
  3934  
  3935  func Test_dcrPerKBToAtomsPerByte(t *testing.T) {
  3936  	tests := []struct {
  3937  		name             string
  3938  		estimatedFeeRate float64
  3939  		want             uint64
  3940  		wantErr          bool
  3941  	}{
  3942  		{
  3943  			"catch negative", // but caller should check
  3944  			-0.0002,
  3945  			0,
  3946  			true,
  3947  		},
  3948  		{
  3949  			"ok 0", // but caller should check
  3950  			0.0,
  3951  			0,
  3952  			false,
  3953  		},
  3954  		{
  3955  			"ok 10",
  3956  			0.0001,
  3957  			10,
  3958  			false,
  3959  		},
  3960  		{
  3961  			"ok 11",
  3962  			0.00011,
  3963  			11,
  3964  			false,
  3965  		},
  3966  		{
  3967  			"ok 1",
  3968  			0.00001,
  3969  			1,
  3970  			false,
  3971  		},
  3972  		{
  3973  			"ok 1 rounded up",
  3974  			0.000002,
  3975  			1,
  3976  			false,
  3977  		},
  3978  		{
  3979  			"catch NaN err",
  3980  			math.NaN(),
  3981  			0,
  3982  			true,
  3983  		},
  3984  	}
  3985  	for _, tt := range tests {
  3986  		t.Run(tt.name, func(t *testing.T) {
  3987  			got, err := dcrPerKBToAtomsPerByte(tt.estimatedFeeRate)
  3988  			if (err != nil) != tt.wantErr {
  3989  				t.Errorf("dcrPerKBToAtomsPerByte() error = %v, wantErr %v", err, tt.wantErr)
  3990  				return
  3991  			}
  3992  			if got != tt.want {
  3993  				t.Errorf("dcrPerKBToAtomsPerByte() = %v, want %v", got, tt.want)
  3994  			}
  3995  		})
  3996  	}
  3997  }
  3998  
  3999  type tReconfigurer struct {
  4000  	*rpcWallet
  4001  	restart bool
  4002  	err     error
  4003  }
  4004  
  4005  func (r *tReconfigurer) Reconfigure(ctx context.Context, cfg *asset.WalletConfig, net dex.Network, currentAddress string) (restartRequired bool, err error) {
  4006  	return r.restart, r.err
  4007  }
  4008  
  4009  func TestReconfigure(t *testing.T) {
  4010  	wallet, _, shutdown := tNewWalletMonitorBlocks(false)
  4011  	defer shutdown()
  4012  
  4013  	reconfigurer := tReconfigurer{
  4014  		rpcWallet: wallet.wallet.(*rpcWallet),
  4015  	}
  4016  
  4017  	wallet.wallet = &reconfigurer
  4018  
  4019  	ctx, cancel := context.WithCancel(context.Background())
  4020  	defer cancel()
  4021  
  4022  	cfg1 := &walletConfig{
  4023  		UseSplitTx:       true,
  4024  		FallbackFeeRate:  55,
  4025  		FeeRateLimit:     98,
  4026  		RedeemConfTarget: 7,
  4027  		ApiFeeFallback:   true,
  4028  	}
  4029  
  4030  	cfg2 := &walletConfig{
  4031  		UseSplitTx:       false,
  4032  		FallbackFeeRate:  66,
  4033  		FeeRateLimit:     97,
  4034  		RedeemConfTarget: 5,
  4035  		ApiFeeFallback:   false,
  4036  	}
  4037  
  4038  	// TODO: Test account names reconfiguration for rpcwallets.
  4039  	checkConfig := func(cfg *walletConfig) {
  4040  		if cfg.UseSplitTx != wallet.config().useSplitTx ||
  4041  			toAtoms(cfg.FallbackFeeRate/1000) != wallet.config().fallbackFeeRate ||
  4042  			toAtoms(cfg.FeeRateLimit/1000) != wallet.config().feeRateLimit ||
  4043  			cfg.RedeemConfTarget != wallet.config().redeemConfTarget ||
  4044  			cfg.ApiFeeFallback != wallet.config().apiFeeFallback {
  4045  			t.Fatalf("wallet not configured with the correct values")
  4046  		}
  4047  	}
  4048  
  4049  	settings1, err := config.Mapify(cfg1)
  4050  	if err != nil {
  4051  		t.Fatalf("failed to mapify: %v", err)
  4052  	}
  4053  
  4054  	settings2, err := config.Mapify(cfg2)
  4055  	if err != nil {
  4056  		t.Fatalf("failed to mapify: %v", err)
  4057  	}
  4058  
  4059  	walletCfg := &asset.WalletConfig{
  4060  		Type:     walletTypeDcrwRPC,
  4061  		Settings: settings1,
  4062  		DataDir:  "abcd",
  4063  	}
  4064  
  4065  	// restart = false
  4066  	restart, err := wallet.Reconfigure(ctx, walletCfg, "123456")
  4067  	if err != nil {
  4068  		t.Fatalf("did not expect an error")
  4069  	}
  4070  	if restart {
  4071  		t.Fatalf("expected false restart but got true")
  4072  	}
  4073  	checkConfig(cfg1)
  4074  
  4075  	// restart = 2
  4076  	reconfigurer.restart = true
  4077  	restart, err = wallet.Reconfigure(ctx, walletCfg, "123456")
  4078  	if err != nil {
  4079  		t.Fatalf("did not expect an error")
  4080  	}
  4081  	if !restart {
  4082  		t.Fatalf("expected true restart but got false")
  4083  	}
  4084  	checkConfig(cfg1)
  4085  
  4086  	// try to set new configs, but get error. config should not change.
  4087  	reconfigurer.err = errors.New("reconfigure error")
  4088  	walletCfg.Settings = settings2
  4089  	_, err = wallet.Reconfigure(ctx, walletCfg, "123456")
  4090  	if err == nil {
  4091  		t.Fatalf("expected an error")
  4092  	}
  4093  	checkConfig(cfg1)
  4094  }
  4095  
  4096  func TestEstimateSendTxFee(t *testing.T) {
  4097  	wallet, node, shutdown := tNewWallet()
  4098  	defer shutdown()
  4099  
  4100  	addr := tPKHAddr.String()
  4101  	node.changeAddr = tPKHAddr
  4102  	var unspentVal uint64 = 100e8
  4103  	unspents := make([]walletjson.ListUnspentResult, 0)
  4104  	balanceResult := &walletjson.GetBalanceResult{
  4105  		Balances: []walletjson.GetAccountBalanceResult{
  4106  			{
  4107  				AccountName: tAcctName,
  4108  			},
  4109  		},
  4110  	}
  4111  	node.balanceResult = balanceResult
  4112  
  4113  	var vout uint32
  4114  	addUtxo := func(atomAmt uint64, confs int64, updateUnspent bool) {
  4115  		if updateUnspent {
  4116  			node.unspent[0].Amount += float64(atomAmt) / 1e8
  4117  			return
  4118  		}
  4119  		utxo := walletjson.ListUnspentResult{
  4120  			TxID:          tTxID,
  4121  			Vout:          vout,
  4122  			Address:       tPKHAddr.String(),
  4123  			Account:       tAcctName,
  4124  			Amount:        float64(atomAmt) / 1e8,
  4125  			Confirmations: confs,
  4126  			ScriptPubKey:  hex.EncodeToString(tP2PKHScript),
  4127  			Spendable:     true,
  4128  		}
  4129  		unspents = append(unspents, utxo)
  4130  		node.unspent = unspents
  4131  		// update balance
  4132  		balanceResult.Balances[0].Spendable += utxo.Amount
  4133  		vout++
  4134  	}
  4135  
  4136  	tx := wire.NewMsgTx()
  4137  	payScriptVer, payScript := tPKHAddr.PaymentScript()
  4138  	tx.AddTxOut(newTxOut(int64(unspentVal), payScriptVer, payScript))
  4139  
  4140  	// bSize is the base size for a single tx input.
  4141  	bSize := dexdcr.TxInOverhead + uint32(wire.VarIntSerializeSize(uint64(dexdcr.P2PKHSigScriptSize))) + dexdcr.P2PKHSigScriptSize
  4142  
  4143  	txSize := uint32(tx.SerializeSize()) + bSize
  4144  	estFee := uint64(txSize) * optimalFeeRate
  4145  	changeFee := dexdcr.P2PKHOutputSize * optimalFeeRate
  4146  	estFeeWithChange := changeFee + estFee
  4147  
  4148  	// This should return fee estimate for one output.
  4149  	addUtxo(unspentVal, 1, false)
  4150  	estimate, _, err := wallet.EstimateSendTxFee(addr, unspentVal, optimalFeeRate, true, false)
  4151  	if err != nil {
  4152  		t.Fatal(err)
  4153  	}
  4154  	if estimate != estFee {
  4155  		t.Fatalf("expected estimate to be %v, got %v)", estFee, estimate)
  4156  	}
  4157  
  4158  	// This should return fee estimate for two output.
  4159  	estimate, _, err = wallet.EstimateSendTxFee(addr, unspentVal/2, optimalFeeRate, true, false)
  4160  	if err != nil {
  4161  		t.Fatal(err)
  4162  	}
  4163  	if estimate != estFeeWithChange {
  4164  		t.Fatalf("expected estimate to be %v, got %v)", estFeeWithChange, estimate)
  4165  	}
  4166  
  4167  	// This should return an error, not enough funds to cover fees.
  4168  	_, _, err = wallet.EstimateSendTxFee(addr, unspentVal, optimalFeeRate, false, false)
  4169  	if err == nil {
  4170  		t.Fatal("Expected error not enough to cover funds required")
  4171  	}
  4172  
  4173  	dust := uint64(100)
  4174  	addUtxo(dust, 0, true)
  4175  	// This should return fee estimate for one output with dust added to fee.
  4176  	estFeeWithDust := estFee + 100
  4177  	estimate, _, err = wallet.EstimateSendTxFee(addr, unspentVal, optimalFeeRate, true, false)
  4178  	if err != nil {
  4179  		t.Fatal(err)
  4180  	}
  4181  	if estimate != estFeeWithDust {
  4182  		t.Fatalf("expected estimate to be %v, got %v)", estFeeWithDust, estimate)
  4183  	}
  4184  
  4185  	// Invalid address
  4186  	_, valid, _ := wallet.EstimateSendTxFee("invalidsendaddress", unspentVal, optimalFeeRate, true, false)
  4187  	if valid {
  4188  		t.Fatal("Expected false for an invalid address")
  4189  	}
  4190  
  4191  	// Successful estimate for empty address
  4192  	_, _, err = wallet.EstimateSendTxFee("", unspentVal, optimalFeeRate, true, false)
  4193  	if err != nil {
  4194  		t.Fatalf("Error for empty address: %v", err)
  4195  	}
  4196  
  4197  	// Zero send amount
  4198  	_, _, err = wallet.EstimateSendTxFee(addr, 0, optimalFeeRate, true, false)
  4199  	if err == nil {
  4200  		t.Fatal("Expected error, send amount is zero")
  4201  	}
  4202  }
  4203  
  4204  func TestConfirmRedemption(t *testing.T) {
  4205  	wallet, node, shutdown := tNewWallet()
  4206  	defer shutdown()
  4207  
  4208  	swapVal := toAtoms(5)
  4209  	secret := randBytes(32)
  4210  	secretHash := sha256.Sum256(secret)
  4211  	lockTime := time.Now().Add(time.Hour * 12)
  4212  	addr := tPKHAddr.String()
  4213  
  4214  	contract, err := dexdcr.MakeContract(addr, addr, secretHash[:], lockTime.Unix(), tChainParams)
  4215  	if err != nil {
  4216  		t.Fatalf("error making swap contract: %v", err)
  4217  	}
  4218  
  4219  	privBytes, _ := hex.DecodeString("b07209eec1a8fb6cfe5cb6ace36567406971a75c330db7101fb21bc679bc5330")
  4220  
  4221  	node.changeAddr = tPKHAddr
  4222  	node.privWIF, err = dcrutil.NewWIF(privBytes, tChainParams.PrivateKeyID, dcrec.STEcdsaSecp256k1)
  4223  	if err != nil {
  4224  		t.Fatalf("NewWIF error: %v", err)
  4225  	}
  4226  
  4227  	contractAddr, _ := stdaddr.NewAddressScriptHashV0(contract, tChainParams)
  4228  	_, contractP2SHScript := contractAddr.PaymentScript()
  4229  
  4230  	redemptionScript, _ := dexdcr.RedeemP2SHContract(contract, randBytes(73), randBytes(33), secret)
  4231  
  4232  	spentTx := makeRawTx(nil, []dex.Bytes{contractP2SHScript})
  4233  	txHash := spentTx.TxHash()
  4234  	node.blockchain.addRawTx(1, spentTx)
  4235  	inputs := []*wire.TxIn{makeRPCVin(&txHash, 0, redemptionScript)}
  4236  	spenderTx := makeRawTx(inputs, nil)
  4237  	node.blockchain.addRawTx(2, spenderTx)
  4238  
  4239  	tip, _ := wallet.getBestBlock(wallet.ctx)
  4240  	wallet.currentTip.Store(tip)
  4241  
  4242  	txFn := func(doErr []bool) func() (*walletjson.GetTransactionResult, error) {
  4243  		var i int
  4244  		return func() (*walletjson.GetTransactionResult, error) {
  4245  			defer func() { i++ }()
  4246  			if doErr[i] {
  4247  				return nil, asset.CoinNotFoundError
  4248  			}
  4249  			b, err := spenderTx.Bytes() // spender is redeem, searched first
  4250  			if err != nil {
  4251  				t.Fatal(err)
  4252  			}
  4253  			if i > 0 {
  4254  				b, err = spentTx.Bytes() // spent is swap, searched if the fist call was a forced error
  4255  				if err != nil {
  4256  					t.Fatal(err)
  4257  				}
  4258  			}
  4259  			h := hex.EncodeToString(b)
  4260  			return &walletjson.GetTransactionResult{
  4261  				BlockHash:     hex.EncodeToString(randBytes(32)),
  4262  				Hex:           h,
  4263  				Confirmations: int64(i), // 0 for redeem and 1 for swap
  4264  			}, nil
  4265  		}
  4266  	}
  4267  
  4268  	coin := newOutput(&txHash, 0, swapVal, wire.TxTreeRegular)
  4269  
  4270  	ci := &asset.AuditInfo{
  4271  		Coin:       coin,
  4272  		Contract:   contract,
  4273  		Recipient:  tPKHAddr.String(),
  4274  		Expiration: lockTime,
  4275  		SecretHash: secretHash[:],
  4276  	}
  4277  
  4278  	redemption := &asset.Redemption{
  4279  		Spends: ci,
  4280  		Secret: secret,
  4281  	}
  4282  
  4283  	coinID := coin.ID()
  4284  	// Inverting the first byte.
  4285  	badCoinID := append(append(make([]byte, 0, len(coinID)), ^coinID[0]), coinID[1:]...)
  4286  
  4287  	tests := []struct {
  4288  		name             string
  4289  		redemption       *asset.Redemption
  4290  		coinID           []byte
  4291  		wantErr          bool
  4292  		bestBlockErr     error
  4293  		txRes            func() (*walletjson.GetTransactionResult, error)
  4294  		wantConfs        uint64
  4295  		mempoolRedeems   map[[32]byte]*mempoolRedeem
  4296  		txOutRes         map[outPoint]*chainjson.GetTxOutResult
  4297  		unspentOutputErr error
  4298  	}{{
  4299  		name:       "ok tx never seen before now",
  4300  		coinID:     coinID,
  4301  		redemption: redemption,
  4302  		txRes:      txFn([]bool{false}),
  4303  	}, {
  4304  		name:           "ok tx in map",
  4305  		coinID:         coinID,
  4306  		redemption:     redemption,
  4307  		txRes:          txFn([]bool{false}),
  4308  		mempoolRedeems: map[[32]byte]*mempoolRedeem{secretHash: {txHash: txHash, firstSeen: time.Now()}},
  4309  	}, {
  4310  		name:           "tx in map has different hash than coin id",
  4311  		coinID:         badCoinID,
  4312  		redemption:     redemption,
  4313  		txRes:          txFn([]bool{false}),
  4314  		mempoolRedeems: map[[32]byte]*mempoolRedeem{secretHash: {txHash: txHash, firstSeen: time.Now()}},
  4315  		wantErr:        true,
  4316  	}, {
  4317  		name:       "ok tx not found spent new tx",
  4318  		coinID:     coinID,
  4319  		redemption: redemption,
  4320  		txRes:      txFn([]bool{false}),
  4321  		txOutRes:   map[outPoint]*chainjson.GetTxOutResult{newOutPoint(&txHash, 0): makeGetTxOutRes(0, 5, nil)},
  4322  	}, {
  4323  		name:           "ok old tx should maybe be abandoned",
  4324  		coinID:         coinID,
  4325  		redemption:     redemption,
  4326  		txRes:          txFn([]bool{false}),
  4327  		mempoolRedeems: map[[32]byte]*mempoolRedeem{secretHash: {txHash: txHash, firstSeen: time.Now().Add(-maxRedeemMempoolAge - time.Second)}},
  4328  	}, {
  4329  		name:       "ok and spent",
  4330  		coinID:     coinID,
  4331  		txRes:      txFn([]bool{true, false}),
  4332  		redemption: redemption,
  4333  		wantConfs:  1, // one confirm because this tx is in the best block
  4334  	}, {
  4335  		name:   "ok and spent but we dont know who spent it",
  4336  		coinID: coinID,
  4337  		txRes:  txFn([]bool{true, false}),
  4338  		redemption: func() *asset.Redemption {
  4339  			ci := &asset.AuditInfo{
  4340  				Coin:       coin,
  4341  				Contract:   contract,
  4342  				Recipient:  tPKHAddr.String(),
  4343  				Expiration: lockTime,
  4344  				SecretHash: make([]byte, 32), // fake secret hash
  4345  			}
  4346  			return &asset.Redemption{
  4347  				Spends: ci,
  4348  				Secret: secret,
  4349  			}
  4350  		}(),
  4351  		wantConfs: requiredRedeemConfirms,
  4352  	}, {
  4353  		name:       "get transaction error",
  4354  		coinID:     coinID,
  4355  		redemption: redemption,
  4356  		txRes:      txFn([]bool{true, true}),
  4357  		wantErr:    true,
  4358  	}, {
  4359  		name:       "decode coin error",
  4360  		coinID:     nil,
  4361  		redemption: redemption,
  4362  		txRes:      txFn([]bool{true, false}),
  4363  		wantErr:    true,
  4364  	}, {
  4365  		name:     "redeem error",
  4366  		coinID:   coinID,
  4367  		txOutRes: map[outPoint]*chainjson.GetTxOutResult{newOutPoint(&txHash, 0): makeGetTxOutRes(0, 5, nil)},
  4368  		txRes:    txFn([]bool{true, false}),
  4369  		redemption: func() *asset.Redemption {
  4370  			ci := &asset.AuditInfo{
  4371  				Coin: coin,
  4372  				// Contract:   contract,
  4373  				Recipient:  tPKHAddr.String(),
  4374  				Expiration: lockTime,
  4375  				SecretHash: secretHash[:],
  4376  			}
  4377  			return &asset.Redemption{
  4378  				Spends: ci,
  4379  				Secret: secret,
  4380  			}
  4381  		}(),
  4382  		wantErr: true,
  4383  	}}
  4384  	for _, test := range tests {
  4385  		node.walletTxFn = test.txRes
  4386  		node.bestBlockErr = test.bestBlockErr
  4387  		wallet.mempoolRedeems = test.mempoolRedeems
  4388  		if wallet.mempoolRedeems == nil {
  4389  			wallet.mempoolRedeems = make(map[[32]byte]*mempoolRedeem)
  4390  		}
  4391  		node.txOutRes = test.txOutRes
  4392  		if node.txOutRes == nil {
  4393  			node.txOutRes = make(map[outPoint]*chainjson.GetTxOutResult)
  4394  		}
  4395  		status, err := wallet.ConfirmRedemption(test.coinID, test.redemption, 0)
  4396  		if test.wantErr {
  4397  			if err == nil {
  4398  				t.Fatalf("%q: expected error", test.name)
  4399  			}
  4400  			continue
  4401  		}
  4402  		if err != nil {
  4403  			t.Fatalf("%q: unexpected error: %v", test.name, err)
  4404  		}
  4405  		if status.Confs != test.wantConfs {
  4406  			t.Fatalf("%q: wanted %d confs but got %d", test.name, test.wantConfs, status.Confs)
  4407  		}
  4408  	}
  4409  }
  4410  
  4411  func TestPurchaseTickets(t *testing.T) {
  4412  	const feeSuggestion = 100
  4413  	const sdiff = 1
  4414  
  4415  	wallet, cl, shutdown := tNewWalletMonitorBlocks(false)
  4416  	defer shutdown()
  4417  	wallet.connected.Store(true)
  4418  	cl.stakeInfo.Difficulty = dcrutil.Amount(sdiff).ToCoin()
  4419  	cl.balanceResult = &walletjson.GetBalanceResult{Balances: []walletjson.GetAccountBalanceResult{{AccountName: tAcctName}}}
  4420  	setBalance := func(n, reserves uint64) {
  4421  		ticketCost := n * (sdiff + feeSuggestion*minVSPTicketPurchaseSize)
  4422  		cl.balanceResult.Balances[0].Spendable = dcrutil.Amount(ticketCost).ToCoin()
  4423  		wallet.bondReserves.Store(reserves)
  4424  	}
  4425  
  4426  	var blocksToConfirm atomic.Int64
  4427  	cl.walletTxFn = func() (*walletjson.GetTransactionResult, error) {
  4428  		txHex, _ := makeTxHex(nil, []dex.Bytes{randBytes(25)})
  4429  		var confs int64 = 1
  4430  		if blocksToConfirm.Load() > 0 {
  4431  			confs = 0
  4432  		}
  4433  		return &walletjson.GetTransactionResult{Hex: txHex, Confirmations: confs}, nil
  4434  	}
  4435  
  4436  	var remains []uint32
  4437  	checkRemains := func(exp ...uint32) {
  4438  		t.Helper()
  4439  		if len(remains) != len(exp) {
  4440  			t.Fatalf("wrong number of remains, wanted %d, got %+v", len(exp), remains)
  4441  		}
  4442  		for i := 0; i < len(remains); i++ {
  4443  			if remains[i] != exp[i] {
  4444  				t.Fatalf("wrong remains updates: wanted %+v, got %+v", exp, remains)
  4445  			}
  4446  		}
  4447  	}
  4448  
  4449  	waitForTicketLoopToExit := func() {
  4450  		// Ensure the loop closes
  4451  		timeout := time.After(time.Second)
  4452  		for {
  4453  			if !wallet.ticketBuyer.running.Load() {
  4454  				break
  4455  			}
  4456  			select {
  4457  			case <-time.After(time.Millisecond):
  4458  				return
  4459  			case <-timeout:
  4460  				t.Fatalf("ticket loop didn't exit")
  4461  			}
  4462  		}
  4463  	}
  4464  
  4465  	buyTickets := func(n int, wantErr bool) {
  4466  		defer waitForTicketLoopToExit()
  4467  		remains = make([]uint32, 0)
  4468  		if err := wallet.PurchaseTickets(n, feeSuggestion); err != nil {
  4469  			t.Fatalf("initial PurchaseTickets error: %v", err)
  4470  		}
  4471  
  4472  		var emitted int
  4473  		timeout := time.After(time.Second)
  4474  	out:
  4475  		for {
  4476  			var ni asset.WalletNotification
  4477  			select {
  4478  			case ni = <-cl.emitC:
  4479  			case <-timeout:
  4480  				t.Fatalf("timed out looking for ticket updates")
  4481  			default:
  4482  				blocksToConfirm.Add(-1)
  4483  				wallet.runTicketBuyer()
  4484  				continue
  4485  			}
  4486  			switch nt := ni.(type) {
  4487  			case *asset.CustomWalletNote:
  4488  				switch n := nt.Payload.(type) {
  4489  				case *TicketPurchaseUpdate:
  4490  					remains = append(remains, n.Remaining)
  4491  					if n.Err != "" {
  4492  						if wantErr {
  4493  							return
  4494  						}
  4495  						t.Fatalf("Error received in TicketPurchaseUpdate: %s", n.Err)
  4496  					}
  4497  					if n.Remaining == 0 {
  4498  						break out
  4499  					}
  4500  					emitted++
  4501  				}
  4502  
  4503  			}
  4504  		}
  4505  	}
  4506  
  4507  	tixHashes := func(n int) []*chainhash.Hash {
  4508  		hs := make([]*chainhash.Hash, n)
  4509  		for i := 0; i < n; i++ {
  4510  			var ticketHash chainhash.Hash
  4511  			copy(ticketHash[:], randBytes(32))
  4512  			hs[i] = &ticketHash
  4513  		}
  4514  		return hs
  4515  	}
  4516  
  4517  	// Single ticket purchased right away.
  4518  	cl.purchasedTickets = [][]*chainhash.Hash{tixHashes(1)}
  4519  	setBalance(1, 0)
  4520  	buyTickets(1, false)
  4521  	checkRemains(1, 0)
  4522  
  4523  	// Multiple tickets purchased right away.
  4524  	cl.purchasedTickets = [][]*chainhash.Hash{tixHashes(2)}
  4525  	setBalance(2, 0)
  4526  	buyTickets(2, false)
  4527  	checkRemains(2, 0)
  4528  
  4529  	// Two tickets, purchased in two tries, skipping some tries for unconfirmed
  4530  	// tickets.
  4531  	blocksToConfirm.Store(3)
  4532  	cl.purchasedTickets = [][]*chainhash.Hash{tixHashes(1), tixHashes(1)}
  4533  	buyTickets(2, false)
  4534  	checkRemains(2, 1, 0)
  4535  
  4536  	// (Wallet).PurchaseTickets error
  4537  	cl.purchasedTickets = [][]*chainhash.Hash{tixHashes(4)}
  4538  	cl.purchaseTicketsErr = errors.New("test error")
  4539  	setBalance(4, 0)
  4540  	buyTickets(4, true)
  4541  	checkRemains(4, 0)
  4542  
  4543  	// Low-balance error
  4544  	cl.purchasedTickets = [][]*chainhash.Hash{tixHashes(1)}
  4545  	setBalance(1, 1) // reserves make our available balance 0
  4546  	buyTickets(1, true)
  4547  	checkRemains(1, 0)
  4548  }
  4549  
  4550  func TestFindBond(t *testing.T) {
  4551  	wallet, node, shutdown := tNewWallet()
  4552  	defer shutdown()
  4553  
  4554  	privBytes, _ := hex.DecodeString("b07209eec1a8fb6cfe5cb6ace36567406971a75c330db7101fb21bc679bc5330")
  4555  	bondKey := secp256k1.PrivKeyFromBytes(privBytes)
  4556  
  4557  	amt := uint64(50_000)
  4558  	acctID := [32]byte{}
  4559  	lockTime := time.Now().Add(time.Hour * 12)
  4560  	utxo := walletjson.ListUnspentResult{
  4561  		TxID:          tTxID,
  4562  		Address:       tPKHAddr.String(),
  4563  		Account:       tAcctName,
  4564  		Amount:        1.0,
  4565  		Confirmations: 1,
  4566  		ScriptPubKey:  hex.EncodeToString(tP2PKHScript),
  4567  		Spendable:     true,
  4568  	}
  4569  	node.unspent = []walletjson.ListUnspentResult{utxo}
  4570  	node.newAddr = tPKHAddr
  4571  	node.changeAddr = tPKHAddr
  4572  
  4573  	bond, _, err := wallet.MakeBondTx(0, amt, 200, lockTime, bondKey, acctID[:])
  4574  	if err != nil {
  4575  		t.Fatal(err)
  4576  	}
  4577  
  4578  	txFn := func(err error, tx []byte) func() (*walletjson.GetTransactionResult, error) {
  4579  		return func() (*walletjson.GetTransactionResult, error) {
  4580  			if err != nil {
  4581  				return nil, err
  4582  			}
  4583  			h := hex.EncodeToString(tx)
  4584  			return &walletjson.GetTransactionResult{
  4585  				BlockHash: hex.EncodeToString(randBytes(32)),
  4586  				Hex:       h,
  4587  			}, nil
  4588  		}
  4589  	}
  4590  
  4591  	newBondTx := func() *wire.MsgTx {
  4592  		msgTx := wire.NewMsgTx()
  4593  		if err := msgTx.FromBytes(bond.SignedTx); err != nil {
  4594  			t.Fatal(err)
  4595  		}
  4596  		return msgTx
  4597  	}
  4598  	tooFewOutputs := newBondTx()
  4599  	tooFewOutputs.TxOut = tooFewOutputs.TxOut[2:]
  4600  	tooFewOutputsBytes, err := tooFewOutputs.Bytes()
  4601  	if err != nil {
  4602  		t.Fatal(err)
  4603  	}
  4604  
  4605  	badBondScript := newBondTx()
  4606  	badBondScript.TxOut[1].PkScript = badBondScript.TxOut[1].PkScript[1:]
  4607  	badBondScriptBytes, err := badBondScript.Bytes()
  4608  	if err != nil {
  4609  		t.Fatal(err)
  4610  	}
  4611  
  4612  	noBondMatch := newBondTx()
  4613  	noBondMatch.TxOut[0].PkScript = noBondMatch.TxOut[0].PkScript[1:]
  4614  	noBondMatchBytes, err := noBondMatch.Bytes()
  4615  	if err != nil {
  4616  		t.Fatal(err)
  4617  	}
  4618  
  4619  	node.blockchain.addRawTx(1, newBondTx())
  4620  	verboseBlocks := node.blockchain.verboseBlocks
  4621  
  4622  	tests := []struct {
  4623  		name          string
  4624  		coinID        []byte
  4625  		txRes         func() (*walletjson.GetTransactionResult, error)
  4626  		bestBlockErr  error
  4627  		verboseBlocks map[chainhash.Hash]*wire.MsgBlock
  4628  		searchUntil   time.Time
  4629  		wantErr       bool
  4630  	}{{
  4631  		name:   "ok",
  4632  		coinID: bond.CoinID,
  4633  		txRes:  txFn(nil, bond.SignedTx),
  4634  	}, {
  4635  		name:   "ok with find blocks",
  4636  		coinID: bond.CoinID,
  4637  		txRes:  txFn(asset.CoinNotFoundError, nil),
  4638  	}, {
  4639  		name:    "bad coin id",
  4640  		coinID:  make([]byte, 0),
  4641  		txRes:   txFn(nil, bond.SignedTx),
  4642  		wantErr: true,
  4643  	}, {
  4644  		name:    "missing an output",
  4645  		coinID:  bond.CoinID,
  4646  		txRes:   txFn(nil, tooFewOutputsBytes),
  4647  		wantErr: true,
  4648  	}, {
  4649  		name:    "bad bond commitment script",
  4650  		coinID:  bond.CoinID,
  4651  		txRes:   txFn(nil, badBondScriptBytes),
  4652  		wantErr: true,
  4653  	}, {
  4654  		name:    "bond script does not match commitment",
  4655  		coinID:  bond.CoinID,
  4656  		txRes:   txFn(nil, noBondMatchBytes),
  4657  		wantErr: true,
  4658  	}, {
  4659  		name:    "bad msgtx",
  4660  		coinID:  bond.CoinID,
  4661  		txRes:   txFn(nil, bond.SignedTx[100:]),
  4662  		wantErr: true,
  4663  	}, {
  4664  		name:         "get best block error",
  4665  		coinID:       bond.CoinID,
  4666  		txRes:        txFn(asset.CoinNotFoundError, nil),
  4667  		bestBlockErr: errors.New("some error"),
  4668  		wantErr:      true,
  4669  	}, {
  4670  		name:          "block not found",
  4671  		coinID:        bond.CoinID,
  4672  		txRes:         txFn(asset.CoinNotFoundError, nil),
  4673  		verboseBlocks: map[chainhash.Hash]*wire.MsgBlock{},
  4674  		wantErr:       true,
  4675  	}}
  4676  
  4677  	for _, test := range tests {
  4678  		t.Run(test.name, func(t *testing.T) {
  4679  			node.walletTxFn = test.txRes
  4680  			node.bestBlockErr = test.bestBlockErr
  4681  			node.blockchain.verboseBlocks = verboseBlocks
  4682  			if test.verboseBlocks != nil {
  4683  				node.blockchain.verboseBlocks = test.verboseBlocks
  4684  			}
  4685  			bd, err := wallet.FindBond(tCtx, test.coinID, test.searchUntil)
  4686  			if test.wantErr {
  4687  				if err == nil {
  4688  					t.Fatal("expected error")
  4689  				}
  4690  				return
  4691  			}
  4692  			if err != nil {
  4693  				t.Fatalf("unexpected error: %v", err)
  4694  			}
  4695  			if !bd.CheckPrivKey(bondKey) {
  4696  				t.Fatal("pkh not equal")
  4697  			}
  4698  		})
  4699  	}
  4700  }
  4701  
  4702  func makeSwapContract(lockTimeOffset time.Duration) (pkScriptVer uint16, pkScript []byte) {
  4703  	secret := randBytes(32)
  4704  	secretHash := sha256.Sum256(secret)
  4705  
  4706  	lockTime := time.Now().Add(lockTimeOffset)
  4707  	var err error
  4708  	contract, err := dexdcr.MakeContract(tPKHAddr.String(), tPKHAddr.String(), secretHash[:], lockTime.Unix(), chaincfg.MainNetParams())
  4709  	if err != nil {
  4710  		panic("error making swap contract:" + err.Error())
  4711  	}
  4712  
  4713  	scriptAddr, err := stdaddr.NewAddressScriptHashV0(contract, chaincfg.MainNetParams())
  4714  	if err != nil {
  4715  		panic("error making script address:" + err.Error())
  4716  	}
  4717  
  4718  	return scriptAddr.PaymentScript()
  4719  }
  4720  
  4721  func TestIDUnknownTx(t *testing.T) {
  4722  	// Swap Tx - any tx with p2sh outputs that is not a bond.
  4723  	_, swapPKScript := makeSwapContract(time.Hour * 12)
  4724  	swapTx := &wire.MsgTx{
  4725  		TxIn:  []*wire.TxIn{wire.NewTxIn(&wire.OutPoint{}, 0, nil)},
  4726  		TxOut: []*wire.TxOut{wire.NewTxOut(int64(toAtoms(1)), swapPKScript)},
  4727  	}
  4728  
  4729  	// Redeem Tx
  4730  	swapContract, _ := dexdcr.MakeContract(tPKHAddr.String(), tPKHAddr.String(), randBytes(32), time.Now().Unix(), chaincfg.MainNetParams())
  4731  	txIn := wire.NewTxIn(&wire.OutPoint{}, 0, nil)
  4732  	txIn.SignatureScript, _ = dexdcr.RedeemP2SHContract(swapContract, randBytes(73), randBytes(33), randBytes(32))
  4733  	redeemFee := 0.0000143
  4734  	_, tP2PKH := tPKHAddr.PaymentScript()
  4735  	redemptionTx := &wire.MsgTx{
  4736  		TxIn:  []*wire.TxIn{txIn},
  4737  		TxOut: []*wire.TxOut{wire.NewTxOut(int64(toAtoms(5-redeemFee)), tP2PKH)},
  4738  	}
  4739  
  4740  	h2b := func(h string) []byte {
  4741  		b, _ := hex.DecodeString(h)
  4742  		return b
  4743  	}
  4744  
  4745  	// Create Bond Tx
  4746  	bondLockTime := 1711637410
  4747  	bondID := h2b("0e39bbb09592fd00b7d770cc832ddf4d625ae3a0")
  4748  	accountID := h2b("a0836b39b5ceb84f422b8a8cd5940117087a8522457c6d81d200557652fbe6ea")
  4749  	bondContract, _ := dexdcr.MakeBondScript(0, uint32(bondLockTime), bondID)
  4750  	contractAddr, err := stdaddr.NewAddressScriptHashV0(bondContract, chaincfg.MainNetParams())
  4751  	if err != nil {
  4752  		t.Fatal("error making script address:" + err.Error())
  4753  	}
  4754  	_, bondPkScript := contractAddr.PaymentScript()
  4755  
  4756  	bondOutput := wire.NewTxOut(int64(toAtoms(2)), bondPkScript)
  4757  	bondCommitPkScript, _ := bondPushDataScript(0, accountID, int64(bondLockTime), bondID)
  4758  	bondCommitmentOutput := wire.NewTxOut(0, bondCommitPkScript)
  4759  	createBondTx := &wire.MsgTx{
  4760  		TxIn:  []*wire.TxIn{wire.NewTxIn(&wire.OutPoint{}, 0, nil)},
  4761  		TxOut: []*wire.TxOut{bondOutput, bondCommitmentOutput},
  4762  	}
  4763  
  4764  	// Redeem Bond Tx
  4765  	txIn = wire.NewTxIn(&wire.OutPoint{}, 0, nil)
  4766  	txIn.SignatureScript, _ = dexdcr.RefundBondScript(bondContract, randBytes(73), randBytes(33))
  4767  	redeemBondTx := &wire.MsgTx{
  4768  		TxIn:  []*wire.TxIn{txIn},
  4769  		TxOut: []*wire.TxOut{wire.NewTxOut(int64(toAtoms(5)), tP2PKH)},
  4770  	}
  4771  
  4772  	// Split Tx
  4773  	splitTx := &wire.MsgTx{
  4774  		TxIn:  []*wire.TxIn{wire.NewTxIn(&wire.OutPoint{}, 0, nil)},
  4775  		TxOut: []*wire.TxOut{wire.NewTxOut(0, tP2PKH), wire.NewTxOut(0, tP2PKH)},
  4776  	}
  4777  
  4778  	// Send Tx
  4779  	cpAddr, _ := stdaddr.DecodeAddress("Dsedb5o6Tw225Loq5J56BZ9jS4ehnEnmQ16", tChainParams)
  4780  	_, cpPkScript := cpAddr.PaymentScript()
  4781  	sendTx := &wire.MsgTx{
  4782  		TxIn:  []*wire.TxIn{wire.NewTxIn(&wire.OutPoint{}, 0, nil)},
  4783  		TxOut: []*wire.TxOut{wire.NewTxOut(int64(toAtoms(0.001)), tP2PKH), wire.NewTxOut(int64(toAtoms(4)), cpPkScript)},
  4784  	}
  4785  
  4786  	// Receive Tx
  4787  	receiveTx := &wire.MsgTx{
  4788  		TxIn:  []*wire.TxIn{wire.NewTxIn(&wire.OutPoint{}, 0, nil)},
  4789  		TxOut: []*wire.TxOut{wire.NewTxOut(int64(toAtoms(0.001)), cpPkScript), wire.NewTxOut(int64(toAtoms(4)), tP2PKH)},
  4790  	}
  4791  	type test struct {
  4792  		name            string
  4793  		ltr             *ListTransactionsResult
  4794  		tx              *wire.MsgTx
  4795  		validateAddress map[string]*walletjson.ValidateAddressResult
  4796  		exp             *asset.WalletTransaction
  4797  	}
  4798  
  4799  	// Ticket Tx
  4800  	ticketTx := &wire.MsgTx{
  4801  		TxIn:  []*wire.TxIn{wire.NewTxIn(&wire.OutPoint{}, 0, nil)},
  4802  		TxOut: []*wire.TxOut{wire.NewTxOut(int64(toAtoms(1)), cpPkScript)},
  4803  	}
  4804  
  4805  	float64Ptr := func(f float64) *float64 {
  4806  		return &f
  4807  	}
  4808  
  4809  	stringPtr := func(s string) *string {
  4810  		return &s
  4811  	}
  4812  
  4813  	regularTx := walletjson.LTTTRegular
  4814  	ticketPurchaseTx := walletjson.LTTTTicket
  4815  	ticketRevocationTx := walletjson.LTTTRevocation
  4816  	ticketVote := walletjson.LTTTVote
  4817  
  4818  	tests := []*test{
  4819  		{
  4820  			name: "swap",
  4821  			ltr: &ListTransactionsResult{
  4822  				TxType: &regularTx,
  4823  				Fee:    float64Ptr(0.0000321),
  4824  				TxID:   swapTx.TxHash().String(),
  4825  			},
  4826  			tx: swapTx,
  4827  			exp: &asset.WalletTransaction{
  4828  				Type:   asset.SwapOrSend,
  4829  				ID:     swapTx.TxHash().String(),
  4830  				Amount: toAtoms(1),
  4831  				Fees:   toAtoms(0.0000321),
  4832  			},
  4833  		},
  4834  		{
  4835  			name: "redeem",
  4836  			ltr: &ListTransactionsResult{
  4837  				TxType: &regularTx,
  4838  				TxID:   redemptionTx.TxHash().String(),
  4839  			},
  4840  			tx: redemptionTx,
  4841  			exp: &asset.WalletTransaction{
  4842  				Type:   asset.Redeem,
  4843  				ID:     redemptionTx.TxHash().String(),
  4844  				Amount: toAtoms(5 - redeemFee),
  4845  				Fees:   0,
  4846  			},
  4847  		},
  4848  		{
  4849  			name: "create bond",
  4850  			ltr: &ListTransactionsResult{
  4851  				TxType: &regularTx,
  4852  				Fee:    float64Ptr(0.0000222),
  4853  				TxID:   createBondTx.TxHash().String(),
  4854  			},
  4855  			tx: createBondTx,
  4856  			exp: &asset.WalletTransaction{
  4857  				Type:   asset.CreateBond,
  4858  				ID:     createBondTx.TxHash().String(),
  4859  				Amount: toAtoms(2),
  4860  				Fees:   toAtoms(0.0000222),
  4861  				BondInfo: &asset.BondTxInfo{
  4862  					AccountID: accountID,
  4863  					BondID:    bondID,
  4864  					LockTime:  uint64(bondLockTime),
  4865  				},
  4866  			},
  4867  		},
  4868  		{
  4869  			name: "redeem bond",
  4870  			ltr: &ListTransactionsResult{
  4871  				TxType: &regularTx,
  4872  				TxID:   redeemBondTx.TxHash().String(),
  4873  			},
  4874  			tx: redeemBondTx,
  4875  			exp: &asset.WalletTransaction{
  4876  				Type:   asset.RedeemBond,
  4877  				ID:     redeemBondTx.TxHash().String(),
  4878  				Amount: toAtoms(5),
  4879  				BondInfo: &asset.BondTxInfo{
  4880  					AccountID: []byte{},
  4881  					BondID:    bondID,
  4882  					LockTime:  uint64(bondLockTime),
  4883  				},
  4884  			},
  4885  		},
  4886  		{
  4887  			name: "split",
  4888  			ltr: &ListTransactionsResult{
  4889  				TxType: &regularTx,
  4890  				Fee:    float64Ptr(-0.0000251),
  4891  				Send:   true,
  4892  				TxID:   splitTx.TxHash().String(),
  4893  			},
  4894  			tx: splitTx,
  4895  			exp: &asset.WalletTransaction{
  4896  				Type: asset.Split,
  4897  				ID:   splitTx.TxHash().String(),
  4898  				Fees: toAtoms(0.0000251),
  4899  			},
  4900  		},
  4901  		{
  4902  			name: "send",
  4903  			ltr: &ListTransactionsResult{
  4904  				TxType: &regularTx,
  4905  				Send:   true,
  4906  				Fee:    float64Ptr(0.0000504),
  4907  				TxID:   sendTx.TxHash().String(),
  4908  			},
  4909  			tx: sendTx,
  4910  			exp: &asset.WalletTransaction{
  4911  				Type:      asset.Send,
  4912  				ID:        sendTx.TxHash().String(),
  4913  				Amount:    toAtoms(4),
  4914  				Recipient: stringPtr(cpAddr.String()),
  4915  				Fees:      toAtoms(0.0000504),
  4916  			},
  4917  			validateAddress: map[string]*walletjson.ValidateAddressResult{
  4918  				tPKHAddr.String(): {
  4919  					IsMine:  true,
  4920  					Account: tAcctName,
  4921  				},
  4922  			},
  4923  		},
  4924  		{
  4925  			name: "receive",
  4926  			ltr: &ListTransactionsResult{
  4927  				TxType: &regularTx,
  4928  				TxID:   receiveTx.TxHash().String(),
  4929  			},
  4930  			tx: receiveTx,
  4931  			exp: &asset.WalletTransaction{
  4932  				Type:      asset.Receive,
  4933  				ID:        receiveTx.TxHash().String(),
  4934  				Amount:    toAtoms(4),
  4935  				Recipient: stringPtr(tPKHAddr.String()),
  4936  			},
  4937  			validateAddress: map[string]*walletjson.ValidateAddressResult{
  4938  				tPKHAddr.String(): {
  4939  					IsMine:  true,
  4940  					Account: tAcctName,
  4941  				},
  4942  			},
  4943  		},
  4944  		{
  4945  			name: "ticket purchase",
  4946  			ltr: &ListTransactionsResult{
  4947  				TxType: &ticketPurchaseTx,
  4948  				TxID:   ticketTx.TxHash().String(),
  4949  			},
  4950  			tx: ticketTx,
  4951  			exp: &asset.WalletTransaction{
  4952  				Type:   asset.TicketPurchase,
  4953  				ID:     ticketTx.TxHash().String(),
  4954  				Amount: toAtoms(1),
  4955  			},
  4956  		},
  4957  		{
  4958  			name: "ticket vote",
  4959  			ltr: &ListTransactionsResult{
  4960  				TxType: &ticketVote,
  4961  				TxID:   ticketTx.TxHash().String(),
  4962  			},
  4963  			tx: ticketTx,
  4964  			exp: &asset.WalletTransaction{
  4965  				Type:   asset.TicketVote,
  4966  				ID:     ticketTx.TxHash().String(),
  4967  				Amount: toAtoms(1),
  4968  			},
  4969  		},
  4970  		{
  4971  			name: "ticket revocation",
  4972  			ltr: &ListTransactionsResult{
  4973  				TxType: &ticketRevocationTx,
  4974  				TxID:   ticketTx.TxHash().String(),
  4975  			},
  4976  			tx: ticketTx,
  4977  			exp: &asset.WalletTransaction{
  4978  				Type:   asset.TicketRevocation,
  4979  				ID:     ticketTx.TxHash().String(),
  4980  				Amount: toAtoms(1),
  4981  			},
  4982  		},
  4983  	}
  4984  
  4985  	runTest := func(tt *test) {
  4986  		t.Run(tt.name, func(t *testing.T) {
  4987  			wallet, node, shutdown := tNewWallet()
  4988  			defer shutdown()
  4989  			node.validateAddress = tt.validateAddress
  4990  			node.blockchain.rawTxs[tt.tx.TxHash()] = &wireTxWithHeight{
  4991  				tx: tt.tx,
  4992  			}
  4993  			wt, err := wallet.idUnknownTx(context.Background(), tt.ltr)
  4994  			if err != nil {
  4995  				t.Fatalf("%s: unexpected error: %v", tt.name, err)
  4996  			}
  4997  			if !reflect.DeepEqual(wt, tt.exp) {
  4998  				t.Fatalf("%s: expected %+v, got %+v", tt.name, tt.exp, wt)
  4999  			}
  5000  		})
  5001  	}
  5002  
  5003  	for _, tt := range tests {
  5004  		runTest(tt)
  5005  	}
  5006  }
  5007  
  5008  func TestRescanSync(t *testing.T) {
  5009  	wallet, node, shutdown := tNewWalletMonitorBlocks(false)
  5010  	defer shutdown()
  5011  
  5012  	const tip = 1000
  5013  	wallet.currentTip.Store(&block{height: tip})
  5014  
  5015  	node.rawRes[methodSyncStatus], node.rawErr[methodSyncStatus] = json.Marshal(&walletjson.SyncStatusResult{
  5016  		Synced:               true,
  5017  		InitialBlockDownload: false,
  5018  		HeadersFetchProgress: 1,
  5019  	})
  5020  
  5021  	node.blockchain.mainchain[tip] = &chainhash.Hash{}
  5022  
  5023  	checkProgress := func(expSynced bool, expProgress float32) {
  5024  		t.Helper()
  5025  		ss, err := wallet.SyncStatus()
  5026  		if err != nil {
  5027  			t.Fatalf("Unexpected error: %v", err)
  5028  		}
  5029  		if ss.Synced != expSynced {
  5030  			t.Fatalf("expected synced = %t, bot %t", expSynced, ss.Synced)
  5031  		}
  5032  		if !ss.Synced {
  5033  			txProgress := float32(*ss.Transactions) / float32(ss.TargetHeight)
  5034  			if math.Abs(float64(expProgress/txProgress)-1) > 0.001 {
  5035  				t.Fatalf("expected progress %f, got %f", expProgress, txProgress)
  5036  			}
  5037  		}
  5038  	}
  5039  
  5040  	// No rescan in progress.
  5041  	checkProgress(true, 1)
  5042  
  5043  	// Rescan running. No progress.
  5044  	wallet.rescan.progress = &rescanProgress{}
  5045  	checkProgress(false, 0)
  5046  
  5047  	// Halfway done.
  5048  	wallet.rescan.progress = &rescanProgress{scannedThrough: tip / 2}
  5049  	checkProgress(false, 0.5)
  5050  
  5051  	// Not synced until progress is nil.
  5052  	wallet.rescan.progress = &rescanProgress{scannedThrough: tip}
  5053  	checkProgress(false, 1)
  5054  
  5055  	// Scanned > tip OK
  5056  	wallet.rescan.progress = &rescanProgress{scannedThrough: tip * 2}
  5057  	checkProgress(false, 1)
  5058  
  5059  	// Rescan complete.
  5060  	wallet.rescan.progress = nil
  5061  	checkProgress(true, 1)
  5062  
  5063  }