decred.org/dcrdex@v1.0.3/client/asset/btc/btc_test.go (about)

     1  //go:build !spvlive && !harness
     2  
     3  package btc
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"crypto/sha256"
     9  	"encoding/base64"
    10  	"encoding/hex"
    11  	"encoding/json"
    12  	"errors"
    13  	"fmt"
    14  	"math/rand"
    15  	"os"
    16  	"reflect"
    17  	"sort"
    18  	"strings"
    19  	"sync"
    20  	"testing"
    21  	"time"
    22  
    23  	"decred.org/dcrdex/client/asset"
    24  	"decred.org/dcrdex/dex"
    25  	"decred.org/dcrdex/dex/calc"
    26  	"decred.org/dcrdex/dex/encode"
    27  	dexbtc "decred.org/dcrdex/dex/networks/btc"
    28  	"github.com/btcsuite/btcd/btcec/v2"
    29  	"github.com/btcsuite/btcd/btcec/v2/ecdsa"
    30  	"github.com/btcsuite/btcd/btcjson"
    31  	"github.com/btcsuite/btcd/btcutil"
    32  	"github.com/btcsuite/btcd/chaincfg"
    33  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    34  	"github.com/btcsuite/btcd/txscript"
    35  	"github.com/btcsuite/btcd/wire"
    36  )
    37  
    38  var (
    39  	tLogger  dex.Logger
    40  	tCtx     context.Context
    41  	tLotSize uint64 = 1e6 // 0.01 BTC
    42  	tBTC            = &dex.Asset{
    43  		ID:         0,
    44  		Symbol:     "btc",
    45  		Version:    version,
    46  		MaxFeeRate: 34,
    47  		SwapConf:   1,
    48  	}
    49  	tSwapSizeBase  uint64 = dexbtc.InitTxSizeBaseSegwit
    50  	tSwapSize      uint64 = dexbtc.InitTxSizeSegwit
    51  	optimalFeeRate uint64 = 24
    52  	tErr                  = fmt.Errorf("test error")
    53  	tTxID                 = "308e9a3675fc3ea3862b7863eeead08c621dcc37ff59de597dd3cdab41450ad9"
    54  	tTxHash        *chainhash.Hash
    55  	tP2PKHAddr     = "1Bggq7Vu5oaoLFV1NNp5KhAzcku83qQhgi"
    56  	tP2PKH         []byte
    57  	tP2WPKH        []byte
    58  	tP2WPKHAddr           = "bc1qq49ypf420s0kh52l9pk7ha8n8nhsugdpculjas"
    59  	feeSuggestion  uint64 = 10
    60  )
    61  
    62  func boolPtr(v bool) *bool {
    63  	return &v
    64  }
    65  
    66  func btcAddr(segwit bool) btcutil.Address {
    67  	var addr btcutil.Address
    68  	if segwit {
    69  		addr, _ = btcutil.DecodeAddress(tP2WPKHAddr, &chaincfg.MainNetParams)
    70  	} else {
    71  		addr, _ = btcutil.DecodeAddress(tP2PKHAddr, &chaincfg.MainNetParams)
    72  	}
    73  	return addr
    74  }
    75  
    76  func randBytes(l int) []byte {
    77  	b := make([]byte, l)
    78  	rand.Read(b)
    79  	return b
    80  }
    81  
    82  func signFuncRaw(t *testing.T, params []json.RawMessage, sizeTweak int, sigComplete, segwit bool) (json.RawMessage, error) {
    83  	signTxRes := SignTxResult{
    84  		Complete: sigComplete,
    85  	}
    86  	var msgHex string
    87  	err := json.Unmarshal(params[0], &msgHex)
    88  	if err != nil {
    89  		t.Fatalf("error unmarshaling transaction hex: %v", err)
    90  	}
    91  	msgBytes, _ := hex.DecodeString(msgHex)
    92  	txReader := bytes.NewReader(msgBytes)
    93  	msgTx := wire.NewMsgTx(wire.TxVersion)
    94  	err = msgTx.Deserialize(txReader)
    95  	if err != nil {
    96  		t.Fatalf("error deserializing contract: %v", err)
    97  	}
    98  
    99  	signFunc(msgTx, sizeTweak, segwit)
   100  
   101  	buf := new(bytes.Buffer)
   102  	err = msgTx.Serialize(buf)
   103  	if err != nil {
   104  		t.Fatalf("error serializing contract: %v", err)
   105  	}
   106  	signTxRes.Hex = buf.Bytes()
   107  	return mustMarshal(signTxRes), nil
   108  }
   109  
   110  func signFunc(tx *wire.MsgTx, sizeTweak int, segwit bool) {
   111  	// Set the sigScripts to random bytes of the correct length for spending a
   112  	// p2pkh output.
   113  	if segwit {
   114  		sigSize := 73 + sizeTweak
   115  		for i := range tx.TxIn {
   116  			tx.TxIn[i].Witness = wire.TxWitness{
   117  				randBytes(sigSize),
   118  				randBytes(33),
   119  			}
   120  		}
   121  	} else {
   122  		scriptSize := dexbtc.RedeemP2PKHSigScriptSize + sizeTweak
   123  		for i := range tx.TxIn {
   124  			tx.TxIn[i].SignatureScript = randBytes(scriptSize)
   125  		}
   126  	}
   127  }
   128  
   129  type msgBlockWithHeight struct {
   130  	msgBlock *wire.MsgBlock
   131  	height   int64
   132  }
   133  
   134  type testData struct {
   135  	walletCfg     *baseWalletConfig
   136  	badSendHash   *chainhash.Hash
   137  	sendErr       error
   138  	sentRawTx     *wire.MsgTx
   139  	txOutRes      *btcjson.GetTxOutResult
   140  	txOutErr      error
   141  	sigIncomplete bool
   142  	signFunc      func(*wire.MsgTx)
   143  	signMsgFunc   func([]json.RawMessage) (json.RawMessage, error)
   144  
   145  	blockchainMtx       sync.RWMutex
   146  	verboseBlocks       map[chainhash.Hash]*msgBlockWithHeight
   147  	dbBlockForTx        map[chainhash.Hash]*hashEntry
   148  	mainchain           map[int64]*chainhash.Hash
   149  	getBlockchainInfo   *GetBlockchainInfoResult
   150  	getBestBlockHashErr error
   151  
   152  	mempoolTxs        map[chainhash.Hash]*wire.MsgTx
   153  	rawVerboseErr     error
   154  	lockedCoins       []*RPCOutpoint
   155  	estFeeErr         error
   156  	listLockUnspent   []*RPCOutpoint
   157  	getBalances       *GetBalancesResult
   158  	getBalancesErr    error
   159  	lockUnspentErr    error
   160  	changeAddr        string
   161  	changeAddrErr     error
   162  	newAddress        string
   163  	newAddressErr     error
   164  	privKeyForAddr    *btcutil.WIF
   165  	privKeyForAddrErr error
   166  	birthdayTime      time.Time
   167  
   168  	// If there is an "any" key in the getTransactionMap, that value will be
   169  	// returned for all requests. Otherwise the tx id is looked up.
   170  	getTransactionMap map[string]*GetTransactionResult
   171  	getTransactionErr error
   172  
   173  	getBlockchainInfoErr error
   174  	unlockErr            error
   175  	lockErr              error
   176  	sendToAddress        string
   177  	sendToAddressErr     error
   178  	setTxFee             bool
   179  	signTxErr            error
   180  	listUnspent          []*ListUnspentResult
   181  	listUnspentErr       error
   182  	tipChanged           chan asset.WalletNotification
   183  
   184  	// spv
   185  	fetchInputInfoTx  *wire.MsgTx
   186  	getCFilterScripts map[chainhash.Hash][][]byte
   187  	checkpoints       map[OutPoint]*scanCheckpoint
   188  	confs             uint32
   189  	confsSpent        bool
   190  	confsErr          error
   191  	walletTxSpent     bool
   192  	txFee             uint64
   193  	ownedAddresses    map[string]bool
   194  	ownsAddress       bool
   195  	locked            bool
   196  }
   197  
   198  func newTestData() *testData {
   199  	// setup genesis block, required by bestblock polling goroutine
   200  	genesisHash := chaincfg.MainNetParams.GenesisHash
   201  	return &testData{
   202  		txOutRes: newTxOutResult([]byte{}, 1, 0),
   203  		verboseBlocks: map[chainhash.Hash]*msgBlockWithHeight{
   204  			*genesisHash: {msgBlock: &wire.MsgBlock{}},
   205  		},
   206  		dbBlockForTx: make(map[chainhash.Hash]*hashEntry),
   207  		mainchain: map[int64]*chainhash.Hash{
   208  			0: genesisHash,
   209  		},
   210  		mempoolTxs:        make(map[chainhash.Hash]*wire.MsgTx),
   211  		fetchInputInfoTx:  dummyTx(),
   212  		getCFilterScripts: make(map[chainhash.Hash][][]byte),
   213  		confsErr:          WalletTransactionNotFound,
   214  		checkpoints:       make(map[OutPoint]*scanCheckpoint),
   215  		tipChanged:        make(chan asset.WalletNotification, 1),
   216  		getTransactionMap: make(map[string]*GetTransactionResult),
   217  	}
   218  }
   219  
   220  func (c *testData) getBlock(blockHash chainhash.Hash) *msgBlockWithHeight {
   221  	c.blockchainMtx.Lock()
   222  	defer c.blockchainMtx.Unlock()
   223  	return c.verboseBlocks[blockHash]
   224  }
   225  
   226  func (c *testData) GetBestBlockHeight() int64 {
   227  	c.blockchainMtx.RLock()
   228  	defer c.blockchainMtx.RUnlock()
   229  	var bestBlkHeight int64
   230  	for height := range c.mainchain {
   231  		if height >= bestBlkHeight {
   232  			bestBlkHeight = height
   233  		}
   234  	}
   235  	return bestBlkHeight
   236  }
   237  
   238  func (c *testData) bestBlock() (*chainhash.Hash, int64) {
   239  	c.blockchainMtx.RLock()
   240  	defer c.blockchainMtx.RUnlock()
   241  	var bestHash *chainhash.Hash
   242  	var bestBlkHeight int64
   243  	for height, hash := range c.mainchain {
   244  		if height >= bestBlkHeight {
   245  			bestBlkHeight = height
   246  			bestHash = hash
   247  		}
   248  	}
   249  	return bestHash, bestBlkHeight
   250  }
   251  
   252  func encodeOrError(thing any, err error) (json.RawMessage, error) {
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  	return json.Marshal(thing)
   257  }
   258  
   259  type tRawRequester struct {
   260  	*testData
   261  }
   262  
   263  func (c *tRawRequester) RawRequest(_ context.Context, method string, params []json.RawMessage) (json.RawMessage, error) {
   264  	switch method {
   265  	// TODO: handle methodGetBlockHash and add actual tests to cover it.
   266  	case methodGetNetworkInfo:
   267  		return json.Marshal(&btcjson.GetNetworkInfoResult{
   268  			Connections: 1,
   269  		})
   270  	case methodEstimateSmartFee:
   271  		if c.testData.estFeeErr != nil {
   272  			return nil, c.testData.estFeeErr
   273  		}
   274  		optimalRate := float64(optimalFeeRate) * 1e-5 // ~0.00024
   275  		return json.Marshal(&btcjson.EstimateSmartFeeResult{
   276  			Blocks:  2,
   277  			FeeRate: &optimalRate,
   278  		})
   279  	case methodSendRawTransaction:
   280  		var txHex string
   281  		err := json.Unmarshal(params[0], &txHex)
   282  		if err != nil {
   283  			return nil, err
   284  		}
   285  		tx, err := msgTxFromHex(txHex)
   286  		if err != nil {
   287  			return nil, err
   288  		}
   289  		c.sentRawTx = tx
   290  		if c.sendErr == nil && c.badSendHash == nil {
   291  			h := tx.TxHash().String()
   292  			return json.Marshal(&h)
   293  		}
   294  		if c.sendErr != nil {
   295  			return nil, c.sendErr
   296  		}
   297  		return json.Marshal(c.badSendHash.String())
   298  	case methodGetTxOut:
   299  		return encodeOrError(c.txOutRes, c.txOutErr)
   300  	case methodGetBestBlockHash:
   301  		c.blockchainMtx.RLock()
   302  		if c.getBestBlockHashErr != nil {
   303  			c.blockchainMtx.RUnlock()
   304  			return nil, c.getBestBlockHashErr
   305  		}
   306  		c.blockchainMtx.RUnlock()
   307  		bestHash, _ := c.bestBlock()
   308  		return json.Marshal(bestHash.String())
   309  	case methodGetBlockHash:
   310  		var blockHeight int64
   311  		if err := json.Unmarshal(params[0], &blockHeight); err != nil {
   312  			return nil, err
   313  		}
   314  		c.blockchainMtx.RLock()
   315  		defer c.blockchainMtx.RUnlock()
   316  		for height, blockHash := range c.mainchain {
   317  			if height == blockHeight {
   318  				return json.Marshal(blockHash.String())
   319  			}
   320  		}
   321  		return nil, fmt.Errorf("block not found")
   322  
   323  	case methodGetRawMempool:
   324  		hashes := make([]string, 0, len(c.mempoolTxs))
   325  		for txHash := range c.mempoolTxs {
   326  			hashes = append(hashes, txHash.String())
   327  		}
   328  		return json.Marshal(hashes)
   329  	case methodGetRawTransaction:
   330  		if c.testData.rawVerboseErr != nil {
   331  			return nil, c.testData.rawVerboseErr
   332  		}
   333  		var hashStr string
   334  		err := json.Unmarshal(params[0], &hashStr)
   335  		if err != nil {
   336  			return nil, err
   337  		}
   338  		txHash, err := chainhash.NewHashFromStr(hashStr)
   339  		if err != nil {
   340  			return nil, err
   341  		}
   342  		msgTx := c.mempoolTxs[*txHash]
   343  		if msgTx == nil {
   344  			return nil, fmt.Errorf("transaction not found")
   345  		}
   346  		txB, _ := serializeMsgTx(msgTx)
   347  		return json.Marshal(hex.EncodeToString(txB))
   348  	case methodSignTx:
   349  		if c.signTxErr != nil {
   350  			return nil, c.signTxErr
   351  		}
   352  		signTxRes := SignTxResult{
   353  			Complete: !c.sigIncomplete,
   354  		}
   355  		var msgHex string
   356  		err := json.Unmarshal(params[0], &msgHex)
   357  		if err != nil {
   358  			return nil, fmt.Errorf("json.Unmarshal error for tRawRequester -> RawRequest -> methodSignTx: %v", err)
   359  		}
   360  		msgBytes, _ := hex.DecodeString(msgHex)
   361  		txReader := bytes.NewReader(msgBytes)
   362  		msgTx := wire.NewMsgTx(wire.TxVersion)
   363  		err = msgTx.Deserialize(txReader)
   364  		if err != nil {
   365  			return nil, fmt.Errorf("MsgTx.Deserialize error for tRawRequester -> RawRequest -> methodSignTx: %v", err)
   366  		}
   367  
   368  		c.signFunc(msgTx)
   369  
   370  		buf := new(bytes.Buffer)
   371  		err = msgTx.Serialize(buf)
   372  		if err != nil {
   373  			return nil, fmt.Errorf("MsgTx.Serialize error for tRawRequester -> RawRequest -> methodSignTx: %v", err)
   374  		}
   375  		signTxRes.Hex = buf.Bytes()
   376  		return mustMarshal(signTxRes), nil
   377  	case methodGetBlock:
   378  		c.blockchainMtx.Lock()
   379  		defer c.blockchainMtx.Unlock()
   380  		var blockHashStr string
   381  		err := json.Unmarshal(params[0], &blockHashStr)
   382  		if err != nil {
   383  			return nil, err
   384  		}
   385  		blkHash, err := chainhash.NewHashFromStr(blockHashStr)
   386  		if err != nil {
   387  			return nil, err
   388  		}
   389  		blk, found := c.verboseBlocks[*blkHash]
   390  		if !found {
   391  			return nil, fmt.Errorf("block not found")
   392  		}
   393  		var buf bytes.Buffer
   394  		err = blk.msgBlock.Serialize(&buf)
   395  		if err != nil {
   396  			return nil, err
   397  		}
   398  		return json.Marshal(hex.EncodeToString(buf.Bytes()))
   399  
   400  	case methodGetBlockHeader:
   401  		var blkHashStr string
   402  		_ = json.Unmarshal(params[0], &blkHashStr)
   403  		blkHash, err := chainhash.NewHashFromStr(blkHashStr)
   404  		if err != nil {
   405  			return nil, err
   406  		}
   407  		block := c.getBlock(*blkHash)
   408  		if block == nil {
   409  			return nil, fmt.Errorf("no block verbose found")
   410  		}
   411  		// block may get modified concurrently, lock mtx before reading fields.
   412  		c.blockchainMtx.RLock()
   413  		defer c.blockchainMtx.RUnlock()
   414  		return json.Marshal(&BlockHeader{
   415  			Hash:   blkHash.String(),
   416  			Height: block.height,
   417  			// Confirmations: block.Confirmations,
   418  			// Time:          block.Time,
   419  		})
   420  	case methodLockUnspent:
   421  		if c.lockUnspentErr != nil {
   422  			return json.Marshal(false)
   423  		}
   424  		coins := make([]*RPCOutpoint, 0)
   425  		_ = json.Unmarshal(params[1], &coins)
   426  		if string(params[0]) == "false" {
   427  			if c.lockedCoins != nil {
   428  				c.lockedCoins = append(c.lockedCoins, coins...)
   429  			} else {
   430  				c.lockedCoins = coins
   431  			}
   432  		}
   433  		return json.Marshal(true)
   434  	case methodListLockUnspent:
   435  		return mustMarshal(c.listLockUnspent), nil
   436  	case methodGetBalances:
   437  		return encodeOrError(c.getBalances, c.getBalancesErr)
   438  	case methodChangeAddress:
   439  		return encodeOrError(c.changeAddr, c.changeAddrErr)
   440  	case methodNewAddress:
   441  		return encodeOrError(c.newAddress, c.newAddressErr)
   442  	case methodPrivKeyForAddress:
   443  		if c.privKeyForAddrErr != nil {
   444  			return nil, c.privKeyForAddrErr
   445  		}
   446  		return json.Marshal(c.privKeyForAddr.String())
   447  	case methodGetTransaction:
   448  		if c.getTransactionErr != nil {
   449  			return nil, c.getTransactionErr
   450  		}
   451  
   452  		c.blockchainMtx.Lock()
   453  		defer c.blockchainMtx.Unlock()
   454  		var txID string
   455  		err := json.Unmarshal(params[0], &txID)
   456  		if err != nil {
   457  			return nil, err
   458  		}
   459  
   460  		var txData *GetTransactionResult
   461  		if c.getTransactionMap != nil {
   462  			if txData = c.getTransactionMap["any"]; txData == nil {
   463  				txData = c.getTransactionMap[txID]
   464  			}
   465  		}
   466  		if txData == nil {
   467  			return nil, WalletTransactionNotFound
   468  		}
   469  		return json.Marshal(txData)
   470  	case methodGetBlockchainInfo:
   471  		c.blockchainMtx.RLock()
   472  		defer c.blockchainMtx.RUnlock()
   473  		return encodeOrError(c.getBlockchainInfo, c.getBlockchainInfoErr)
   474  	case methodLock:
   475  		return nil, c.lockErr
   476  	case methodUnlock:
   477  		return nil, c.unlockErr
   478  	case methodSendToAddress:
   479  		return encodeOrError(c.sendToAddress, c.sendToAddressErr)
   480  	case methodSetTxFee:
   481  		return json.Marshal(c.setTxFee)
   482  	case methodListUnspent:
   483  		return encodeOrError(c.listUnspent, c.listUnspentErr)
   484  	case methodFundRawTransaction:
   485  		b, _ := serializeMsgTx(wire.NewMsgTx(wire.TxVersion))
   486  		resp := &struct {
   487  			Transaction string  `json:"hex"`
   488  			Fee         float64 `json:"fee"`
   489  		}{
   490  			Transaction: hex.EncodeToString(b),
   491  			Fee:         toBTC(c.txFee),
   492  		}
   493  		return json.Marshal(resp)
   494  	case methodGetWalletInfo:
   495  		return json.Marshal(&GetWalletInfoResult{UnlockedUntil: nil /* unencrypted -> unlocked */})
   496  	case methodGetAddressInfo:
   497  		var addr string
   498  		err := json.Unmarshal(params[0], &addr)
   499  		if err != nil {
   500  			panic(err)
   501  		}
   502  		owns := c.ownedAddresses != nil && c.ownedAddresses[addr]
   503  		if !owns {
   504  			owns = c.ownsAddress
   505  		}
   506  		return json.Marshal(&btcjson.GetAddressInfoResult{
   507  			IsMine: owns,
   508  		})
   509  	}
   510  	panic("method not registered: " + method)
   511  }
   512  
   513  const testBlocksPerBlockTimeOffset = 4
   514  
   515  func generateTestBlockTime(blockHeight int64) time.Time {
   516  	return time.Unix(1e6, 0).Add(time.Duration(blockHeight) * maxFutureBlockTime / testBlocksPerBlockTimeOffset)
   517  }
   518  
   519  func (c *testData) addRawTx(blockHeight int64, tx *wire.MsgTx) (*chainhash.Hash, *wire.MsgBlock) {
   520  	c.blockchainMtx.Lock()
   521  	defer c.blockchainMtx.Unlock()
   522  	blockHash, found := c.mainchain[blockHeight]
   523  	if !found {
   524  		prevBlock := &chainhash.Hash{}
   525  		if blockHeight > 0 {
   526  			var exists bool
   527  			prevBlock, exists = c.mainchain[blockHeight-1]
   528  			if !exists {
   529  				prevBlock = &chainhash.Hash{}
   530  			}
   531  		}
   532  		nonce, bits := rand.Uint32(), rand.Uint32()
   533  		header := wire.NewBlockHeader(0, prevBlock, &chainhash.Hash{} /* lie, maybe fix this */, bits, nonce)
   534  		header.Timestamp = generateTestBlockTime(blockHeight)
   535  		msgBlock := wire.NewMsgBlock(header) // only now do we know the block hash
   536  		hash := msgBlock.BlockHash()
   537  		blockHash = &hash
   538  		c.verboseBlocks[*blockHash] = &msgBlockWithHeight{
   539  			msgBlock: msgBlock,
   540  			height:   blockHeight,
   541  		}
   542  		c.mainchain[blockHeight] = blockHash
   543  	}
   544  	block := c.verboseBlocks[*blockHash]
   545  	// NOTE: Adding a transaction changes the msgBlock.BlockHash() so the
   546  	// map key is technically always wrong.
   547  	block.msgBlock.AddTransaction(tx)
   548  	return blockHash, block.msgBlock
   549  }
   550  
   551  func (c *testData) addDBBlockForTx(txHash, blockHash *chainhash.Hash) {
   552  	c.blockchainMtx.Lock()
   553  	defer c.blockchainMtx.Unlock()
   554  	c.dbBlockForTx[*txHash] = &hashEntry{hash: *blockHash}
   555  }
   556  
   557  func (c *testData) getBlockAtHeight(blockHeight int64) (*chainhash.Hash, *msgBlockWithHeight) {
   558  	c.blockchainMtx.RLock()
   559  	defer c.blockchainMtx.RUnlock()
   560  	blockHash, found := c.mainchain[blockHeight]
   561  	if !found {
   562  		return nil, nil
   563  	}
   564  	blk := c.verboseBlocks[*blockHash]
   565  	return blockHash, blk
   566  }
   567  
   568  func (c *testData) truncateChains() {
   569  	c.blockchainMtx.RLock()
   570  	defer c.blockchainMtx.RUnlock()
   571  	c.mainchain = make(map[int64]*chainhash.Hash)
   572  	c.verboseBlocks = make(map[chainhash.Hash]*msgBlockWithHeight)
   573  	c.mempoolTxs = make(map[chainhash.Hash]*wire.MsgTx)
   574  }
   575  
   576  func makeRawTx(pkScripts []dex.Bytes, inputs []*wire.TxIn) *wire.MsgTx {
   577  	tx := &wire.MsgTx{
   578  		TxIn: inputs,
   579  	}
   580  	for _, pkScript := range pkScripts {
   581  		tx.TxOut = append(tx.TxOut, wire.NewTxOut(1, pkScript))
   582  	}
   583  	return tx
   584  }
   585  
   586  func makeTxHex(pkScripts []dex.Bytes, inputs []*wire.TxIn) ([]byte, error) {
   587  	msgTx := wire.NewMsgTx(wire.TxVersion)
   588  	for _, txIn := range inputs {
   589  		msgTx.AddTxIn(txIn)
   590  	}
   591  	for _, pkScript := range pkScripts {
   592  		txOut := wire.NewTxOut(100000000, pkScript)
   593  		msgTx.AddTxOut(txOut)
   594  	}
   595  	txBuf := bytes.NewBuffer(make([]byte, 0, dexbtc.MsgTxVBytes(msgTx)))
   596  	err := msgTx.Serialize(txBuf)
   597  	if err != nil {
   598  		return nil, err
   599  	}
   600  	return txBuf.Bytes(), nil
   601  }
   602  
   603  // msgTxFromHex creates a wire.MsgTx by deserializing the hex-encoded
   604  // transaction.
   605  func msgTxFromHex(txHex string) (*wire.MsgTx, error) {
   606  	return deserializeMsgTx(hex.NewDecoder(strings.NewReader(txHex)))
   607  }
   608  
   609  func makeRPCVin(txHash *chainhash.Hash, vout uint32, sigScript []byte, witness [][]byte) *wire.TxIn {
   610  	return wire.NewTxIn(wire.NewOutPoint(txHash, vout), sigScript, witness)
   611  }
   612  
   613  func dummyInput() *wire.TxIn {
   614  	return wire.NewTxIn(wire.NewOutPoint(&chainhash.Hash{0x01}, 0), nil, nil)
   615  }
   616  
   617  func dummyTx() *wire.MsgTx {
   618  	return makeRawTx([]dex.Bytes{randBytes(32)}, []*wire.TxIn{dummyInput()})
   619  }
   620  
   621  func newTxOutResult(script []byte, value uint64, confs int64) *btcjson.GetTxOutResult {
   622  	return &btcjson.GetTxOutResult{
   623  		Confirmations: confs,
   624  		Value:         float64(value) / 1e8,
   625  		ScriptPubKey: btcjson.ScriptPubKeyResult{
   626  			Hex: hex.EncodeToString(script),
   627  		},
   628  	}
   629  }
   630  
   631  func makeSwapContract(segwit bool, lockTimeOffset time.Duration) (secret []byte, secretHash [32]byte, pkScript, contract []byte, addr, contractAddr btcutil.Address, lockTime time.Time) {
   632  	secret = randBytes(32)
   633  	secretHash = sha256.Sum256(secret)
   634  
   635  	addr = btcAddr(segwit)
   636  
   637  	lockTime = time.Now().Add(lockTimeOffset)
   638  	contract, err := dexbtc.MakeContract(addr, addr, secretHash[:], lockTime.Unix(), segwit, &chaincfg.MainNetParams)
   639  	if err != nil {
   640  		panic("error making swap contract:" + err.Error())
   641  	}
   642  	contractAddr, _ = scriptHashAddress(segwit, contract, &chaincfg.MainNetParams)
   643  	pkScript, _ = txscript.PayToAddrScript(contractAddr)
   644  	return
   645  }
   646  
   647  func tNewWallet(segwit bool, walletType string) (*intermediaryWallet, *testData, func()) {
   648  	if segwit {
   649  		tSwapSize = dexbtc.InitTxSizeSegwit
   650  		tSwapSizeBase = dexbtc.InitTxSizeBaseSegwit
   651  	} else {
   652  		tSwapSize = dexbtc.InitTxSize
   653  		tSwapSizeBase = dexbtc.InitTxSizeBase
   654  	}
   655  
   656  	dataDir, err := os.MkdirTemp("", "")
   657  	if err != nil {
   658  		panic("couldn't create data dir:" + err.Error())
   659  	}
   660  
   661  	data := newTestData()
   662  	walletCfg := &asset.WalletConfig{
   663  		Emit: asset.NewWalletEmitter(data.tipChanged, BipID, tLogger),
   664  		PeersChange: func(num uint32, err error) {
   665  			fmt.Printf("peer count = %d, err = %v", num, err)
   666  		},
   667  		DataDir: dataDir,
   668  	}
   669  	walletCtx, shutdown := context.WithCancel(tCtx)
   670  	cfg := &BTCCloneCFG{
   671  		WalletCFG:           walletCfg,
   672  		Symbol:              "btc",
   673  		Logger:              tLogger,
   674  		ChainParams:         &chaincfg.MainNetParams,
   675  		WalletInfo:          WalletInfo,
   676  		DefaultFallbackFee:  defaultFee,
   677  		DefaultFeeRateLimit: defaultFeeRateLimit,
   678  		Segwit:              segwit,
   679  		FeeEstimator:        rpcFeeRate,
   680  		AddressDecoder:      btcutil.DecodeAddress,
   681  	}
   682  
   683  	var wallet *intermediaryWallet
   684  	switch walletType {
   685  	case walletTypeRPC:
   686  		wallet, err = newRPCWallet(&tRawRequester{data}, cfg, &RPCWalletConfig{})
   687  	case walletTypeSPV:
   688  		w, err := newUnconnectedWallet(cfg, &WalletConfig{})
   689  		if err == nil {
   690  			neutrinoClient := &tNeutrinoClient{data}
   691  			spvw := &spvWallet{
   692  				chainParams: &chaincfg.MainNetParams,
   693  				cfg:         &WalletConfig{},
   694  				wallet:      &tBtcWallet{data},
   695  				cl:          neutrinoClient,
   696  				tipChan:     make(chan *BlockVector, 1),
   697  				acctNum:     0,
   698  				log:         cfg.Logger.SubLogger("SPV"),
   699  				decodeAddr:  btcutil.DecodeAddress,
   700  			}
   701  			spvw.BlockFiltersScanner = NewBlockFiltersScanner(spvw, spvw.log)
   702  			spvw.txBlocks = data.dbBlockForTx
   703  			spvw.checkpoints = data.checkpoints
   704  			w.setNode(spvw)
   705  			wallet = &intermediaryWallet{
   706  				baseWallet:     w,
   707  				txFeeEstimator: spvw,
   708  				tipRedeemer:    spvw,
   709  			}
   710  			wallet.prepareRedemptionFinder()
   711  		}
   712  	}
   713  
   714  	data.walletCfg = wallet.cfgV.Load().(*baseWalletConfig)
   715  
   716  	if err != nil {
   717  		shutdown()
   718  		os.RemoveAll(dataDir)
   719  		panic(err.Error())
   720  	}
   721  	// Initialize the best block.
   722  	bestHash, err := wallet.node.getBestBlockHash()
   723  	if err != nil {
   724  		shutdown()
   725  		os.RemoveAll(dataDir)
   726  		panic(err.Error())
   727  	}
   728  	wallet.tipMtx.Lock()
   729  	wallet.currentTip = &BlockVector{
   730  		Height: data.GetBestBlockHeight(),
   731  		Hash:   *bestHash,
   732  	}
   733  	wallet.tipMtx.Unlock()
   734  	var wg sync.WaitGroup
   735  	wg.Add(1)
   736  	go func() {
   737  		defer wg.Done()
   738  		wallet.watchBlocks(walletCtx)
   739  	}()
   740  	shutdownAndWait := func() {
   741  		shutdown()
   742  		os.RemoveAll(dataDir)
   743  		wg.Wait()
   744  	}
   745  	return wallet, data, shutdownAndWait
   746  }
   747  
   748  func mustMarshal(thing any) []byte {
   749  	b, err := json.Marshal(thing)
   750  	if err != nil {
   751  		panic("mustMarshal error: " + err.Error())
   752  	}
   753  	return b
   754  }
   755  
   756  func TestMain(m *testing.M) {
   757  	tLogger = dex.StdOutLogger("TEST", dex.LevelCritical)
   758  	var shutdown func()
   759  	tCtx, shutdown = context.WithCancel(context.Background())
   760  	tTxHash, _ = chainhash.NewHashFromStr(tTxID)
   761  	tP2PKH, _ = hex.DecodeString("76a9148fc02268f208a61767504fe0b48d228641ba81e388ac")
   762  	tP2WPKH, _ = hex.DecodeString("0014148fc02268f208a61767504fe0b48d228641ba81")
   763  	// tP2SH, _ = hex.DecodeString("76a91412a9abf5c32392f38bd8a1f57d81b1aeecc5699588ac")
   764  	doIt := func() int {
   765  		// Not counted as coverage, must test Archiver constructor explicitly.
   766  		defer shutdown()
   767  		return m.Run()
   768  	}
   769  	os.Exit(doIt())
   770  }
   771  
   772  type testFunc func(t *testing.T, segwit bool, walletType string)
   773  
   774  func runRubric(t *testing.T, f testFunc) {
   775  	t.Run("rpc|segwit", func(t *testing.T) {
   776  		f(t, true, walletTypeRPC)
   777  	})
   778  	t.Run("rpc|non-segwit", func(t *testing.T) {
   779  		f(t, false, walletTypeRPC)
   780  	})
   781  	t.Run("spv|segwit", func(t *testing.T) {
   782  		f(t, true, walletTypeSPV)
   783  	})
   784  }
   785  
   786  func TestFundMultiOrder(t *testing.T) {
   787  	runRubric(t, testFundMultiOrder)
   788  }
   789  
   790  func decodeString(s string) []byte {
   791  	b, _ := hex.DecodeString(s)
   792  	return b
   793  }
   794  
   795  func testFundMultiOrder(t *testing.T, segwit bool, walletType string) {
   796  	wallet, node, shutdown := tNewWallet(segwit, walletType)
   797  	defer shutdown()
   798  
   799  	maxFeeRate := uint64(200)
   800  	feeSuggestion := uint64(100)
   801  
   802  	txIDs := make([]string, 0, 5)
   803  	txHashes := make([]*chainhash.Hash, 0, 5)
   804  
   805  	addresses_legacy := []string{
   806  		"n235HrCqx9EcS7teHcJEAthoBF5gvtrAoy",
   807  		"mfjtHyu163DW5ZJXRHkY6kMLtHyWHTH6Qx",
   808  		"mi5QmGr9KwVLM2WVNB9hwSS7KhUx8uNQqU",
   809  		"mhMda8yTy52Avowe34ufbq3D59VoG7jUsy",
   810  		"mhgZ41MUC6EBevkwnhvetkLbBmb61F7Lyr",
   811  	}
   812  	scriptPubKeys_legacy := []dex.Bytes{
   813  		decodeString("76a914e114d5bb20cdbd75f3726f27c10423eb1332576288ac"),
   814  		decodeString("76a91402721143370117f4b37146f6688862892f272a7b88ac"),
   815  		decodeString("76a9141c13a8666a373c29a8a8a270b56816bb43a395e888ac"),
   816  		decodeString("76a914142ce1063712235182613794d6759f62dea8205a88ac"),
   817  		decodeString("76a91417c10273e4236592fd91f3aec1571810bbb0db6888ac"),
   818  	}
   819  
   820  	addresses_segwit := []string{
   821  		"bcrt1qy7agjj62epx0ydnqskgwlcfwu52xjtpj36hr0d",
   822  		"bcrt1qhyflz52jwha67dvg92q92k887emxu6jj0ytrrd",
   823  		"bcrt1qus3827tpu7uhreq3f3x5wmfmpgmkd0y7zhjdrs",
   824  		"bcrt1q4fjzhnum2krkurhg55xtadzyf76f8waqj26d0e",
   825  		"bcrt1qqnl9fhnms6gpmlmrwnsq36h4rqxwd3m4plkcw4",
   826  	}
   827  	scriptPubKeys_segwit := []dex.Bytes{
   828  		decodeString("001427ba894b4ac84cf236608590efe12ee514692c32"),
   829  		decodeString("0014b913f1515275fbaf35882a805558e7f6766e6a52"),
   830  		decodeString("0014e422757961e7b971e4114c4d476d3b0a3766bc9e"),
   831  		decodeString("0014aa642bcf9b55876e0ee8a50cbeb4444fb493bba0"),
   832  		decodeString("001404fe54de7b86901dff6374e008eaf5180ce6c775"),
   833  	}
   834  	for i := 0; i < 5; i++ {
   835  		txIDs = append(txIDs, hex.EncodeToString(encode.RandomBytes(32)))
   836  		h, _ := chainhash.NewHashFromStr(txIDs[i])
   837  		txHashes = append(txHashes, h)
   838  	}
   839  
   840  	addresses := func(i int) string {
   841  		if segwit {
   842  			return addresses_segwit[i]
   843  		}
   844  		return addresses_legacy[i]
   845  	}
   846  
   847  	scriptPubKeys := func(i int) dex.Bytes {
   848  		if segwit {
   849  			return scriptPubKeys_segwit[i]
   850  		}
   851  		return scriptPubKeys_legacy[i]
   852  	}
   853  
   854  	addrStr := tP2PKHAddr
   855  	if segwit {
   856  		addrStr = tP2WPKHAddr
   857  	}
   858  	node.newAddress = addrStr
   859  	node.changeAddr = addrStr
   860  	node.signFunc = func(tx *wire.MsgTx) {
   861  		signFunc(tx, 0, wallet.segwit)
   862  	}
   863  
   864  	expectedSplitFee := func(numInputs, numOutputs uint64) uint64 {
   865  		var inputSize, outputSize uint64
   866  		if segwit {
   867  			inputSize = dexbtc.RedeemP2WPKHInputTotalSize
   868  			outputSize = dexbtc.P2WPKHOutputSize
   869  		} else {
   870  			inputSize = dexbtc.RedeemP2PKHInputSize
   871  			outputSize = dexbtc.P2PKHOutputSize
   872  		}
   873  
   874  		return (dexbtc.MinimumTxOverhead + numInputs*inputSize + numOutputs*outputSize) * feeSuggestion
   875  	}
   876  
   877  	requiredForOrder := func(value, maxSwapCount uint64) int64 {
   878  		var inputSize uint64
   879  		if segwit {
   880  			inputSize = dexbtc.RedeemP2WPKHInputTotalSize
   881  		} else {
   882  			inputSize = dexbtc.RedeemP2PKHInputSize
   883  		}
   884  		return int64(calc.RequiredOrderFunds(value, inputSize, maxSwapCount,
   885  			wallet.initTxSizeBase, wallet.initTxSize, maxFeeRate))
   886  	}
   887  
   888  	type test struct {
   889  		name         string
   890  		multiOrder   *asset.MultiOrder
   891  		allOrNothing bool
   892  		maxLock      uint64
   893  		utxos        []*ListUnspentResult
   894  		bondReserves uint64
   895  		balance      uint64
   896  
   897  		// if expectedCoins is nil, all the coins are from
   898  		// the split output. If any of the coins are nil,
   899  		// than that output is from the split output.
   900  		expectedCoins         []asset.Coins
   901  		expectedRedeemScripts [][]dex.Bytes
   902  		expectSendRawTx       bool
   903  		expectedSplitFee      uint64
   904  		expectedInputs        []*wire.TxIn
   905  		expectedOutputs       []*wire.TxOut
   906  		expectedChange        uint64
   907  		expectedLockedCoins   []*RPCOutpoint
   908  		expectErr             bool
   909  	}
   910  
   911  	tests := []*test{
   912  		{ // "split not allowed, utxos like split previously done"
   913  			name: "split not allowed, utxos like split previously done",
   914  			multiOrder: &asset.MultiOrder{
   915  				Values: []*asset.MultiOrderValue{
   916  					{
   917  						Value:        1e6,
   918  						MaxSwapCount: 1,
   919  					},
   920  					{
   921  						Value:        2e6,
   922  						MaxSwapCount: 2,
   923  					},
   924  				},
   925  				MaxFeeRate:    maxFeeRate,
   926  				FeeSuggestion: feeSuggestion,
   927  				Options: map[string]string{
   928  					"swapsplit": "false",
   929  				},
   930  			},
   931  			utxos: []*ListUnspentResult{
   932  				{
   933  					Confirmations: 1,
   934  					Spendable:     true,
   935  					TxID:          txIDs[0],
   936  					RedeemScript:  nil,
   937  					ScriptPubKey:  scriptPubKeys(0),
   938  					Address:       addresses(0),
   939  					Amount:        19e5 / 1e8,
   940  					Vout:          0,
   941  				},
   942  				{
   943  					Confirmations: 1,
   944  					Spendable:     true,
   945  					TxID:          txIDs[1],
   946  					RedeemScript:  nil,
   947  					ScriptPubKey:  scriptPubKeys(1),
   948  					Address:       addresses(1),
   949  					Amount:        35e5 / 1e8,
   950  					Vout:          0,
   951  				},
   952  			},
   953  			balance: 35e5,
   954  			expectedCoins: []asset.Coins{
   955  				{NewOutput(txHashes[0], 0, 19e5)},
   956  				{NewOutput(txHashes[1], 0, 35e5)},
   957  			},
   958  			expectedRedeemScripts: [][]dex.Bytes{
   959  				{nil},
   960  				{nil},
   961  			},
   962  		},
   963  		{ // "split not allowed, require multiple utxos per order"
   964  			name: "split not allowed, require multiple utxos per order",
   965  			multiOrder: &asset.MultiOrder{
   966  				Values: []*asset.MultiOrderValue{
   967  					{
   968  						Value:        1e6,
   969  						MaxSwapCount: 1,
   970  					},
   971  					{
   972  						Value:        2e6,
   973  						MaxSwapCount: 2,
   974  					},
   975  				},
   976  				MaxFeeRate:    maxFeeRate,
   977  				FeeSuggestion: feeSuggestion,
   978  				Options: map[string]string{
   979  					"swapsplit": "false",
   980  				},
   981  			},
   982  			allOrNothing: true,
   983  			utxos: []*ListUnspentResult{
   984  				{
   985  					Confirmations: 1,
   986  					Spendable:     true,
   987  					TxID:          txIDs[0],
   988  					RedeemScript:  nil,
   989  					ScriptPubKey:  scriptPubKeys(0),
   990  					Address:       addresses(0),
   991  					Amount:        6e5 / 1e8,
   992  					Vout:          0,
   993  				},
   994  				{
   995  					Confirmations: 1,
   996  					Spendable:     true,
   997  					TxID:          txIDs[1],
   998  					RedeemScript:  nil,
   999  					ScriptPubKey:  scriptPubKeys(1),
  1000  					Address:       addresses(1),
  1001  					Amount:        5e5 / 1e8,
  1002  					Vout:          0,
  1003  				},
  1004  				{
  1005  					Confirmations: 1,
  1006  					Spendable:     true,
  1007  					TxID:          txIDs[2],
  1008  					RedeemScript:  nil,
  1009  					ScriptPubKey:  scriptPubKeys(2),
  1010  					Address:       addresses(2),
  1011  					Amount:        22e5 / 1e8,
  1012  					Vout:          0,
  1013  				},
  1014  			},
  1015  			balance: 33e5,
  1016  			expectedCoins: []asset.Coins{
  1017  				{NewOutput(txHashes[0], 0, 6e5), NewOutput(txHashes[1], 0, 5e5)},
  1018  				{NewOutput(txHashes[2], 0, 22e5)},
  1019  			},
  1020  			expectedRedeemScripts: [][]dex.Bytes{
  1021  				{nil, nil},
  1022  				{nil},
  1023  			},
  1024  			expectedLockedCoins: []*RPCOutpoint{
  1025  				{txHashes[0].String(), 0},
  1026  				{txHashes[1].String(), 0},
  1027  				{txHashes[2].String(), 0},
  1028  			},
  1029  		},
  1030  		{ // "split not allowed, can only fund first order and respect maxLock"
  1031  			name: "split not allowed, can only fund first order and respect maxLock",
  1032  			multiOrder: &asset.MultiOrder{
  1033  				Values: []*asset.MultiOrderValue{
  1034  					{
  1035  						Value:        1e6,
  1036  						MaxSwapCount: 1,
  1037  					},
  1038  					{
  1039  						Value:        2e6,
  1040  						MaxSwapCount: 2,
  1041  					},
  1042  				},
  1043  				MaxFeeRate:    maxFeeRate,
  1044  				FeeSuggestion: feeSuggestion,
  1045  				Options: map[string]string{
  1046  					"swapsplit": "false",
  1047  				},
  1048  			},
  1049  			maxLock: 32e5,
  1050  			utxos: []*ListUnspentResult{
  1051  				{
  1052  					Confirmations: 1,
  1053  					Spendable:     true,
  1054  					TxID:          txIDs[2],
  1055  					RedeemScript:  nil,
  1056  					ScriptPubKey:  scriptPubKeys(2),
  1057  					Address:       addresses(2),
  1058  					Amount:        1e6 / 1e8,
  1059  					Vout:          0,
  1060  				},
  1061  				{
  1062  					Confirmations: 1,
  1063  					Spendable:     true,
  1064  					TxID:          txIDs[0],
  1065  					RedeemScript:  nil,
  1066  					ScriptPubKey:  scriptPubKeys(0),
  1067  					Address:       addresses(0),
  1068  					Amount:        11e5 / 1e8,
  1069  					Vout:          0,
  1070  				},
  1071  				{
  1072  					Confirmations: 1,
  1073  					Spendable:     true,
  1074  					TxID:          txIDs[1],
  1075  					RedeemScript:  nil,
  1076  					ScriptPubKey:  scriptPubKeys(1),
  1077  					Address:       addresses(1),
  1078  					Amount:        25e5 / 1e8,
  1079  					Vout:          0,
  1080  				},
  1081  			},
  1082  			balance: 46e5,
  1083  			expectedCoins: []asset.Coins{
  1084  				{NewOutput(txHashes[0], 0, 11e5)},
  1085  			},
  1086  			expectedRedeemScripts: [][]dex.Bytes{
  1087  				{nil},
  1088  			},
  1089  			expectedLockedCoins: []*RPCOutpoint{
  1090  				{txHashes[0].String(), 0},
  1091  			},
  1092  		},
  1093  		{ // "split not allowed, can only fund first order and respect bond reserves"
  1094  			name: "no split allowed, can only fund first order and respect bond reserves",
  1095  			multiOrder: &asset.MultiOrder{
  1096  				Values: []*asset.MultiOrderValue{
  1097  					{
  1098  						Value:        1e6,
  1099  						MaxSwapCount: 1,
  1100  					},
  1101  					{
  1102  						Value:        2e6,
  1103  						MaxSwapCount: 2,
  1104  					},
  1105  				},
  1106  				MaxFeeRate:    maxFeeRate,
  1107  				FeeSuggestion: feeSuggestion,
  1108  				Options: map[string]string{
  1109  					multiSplitKey: "false",
  1110  				},
  1111  			},
  1112  			maxLock:      46e5,
  1113  			bondReserves: 12e5,
  1114  			utxos: []*ListUnspentResult{
  1115  				{
  1116  					Confirmations: 1,
  1117  					Spendable:     true,
  1118  					TxID:          txIDs[2],
  1119  					RedeemScript:  nil,
  1120  					ScriptPubKey:  scriptPubKeys(2),
  1121  					Address:       addresses(2),
  1122  					Amount:        1e6 / 1e8,
  1123  					Vout:          0,
  1124  				},
  1125  				{
  1126  					Confirmations: 1,
  1127  					Spendable:     true,
  1128  					TxID:          txIDs[0],
  1129  					RedeemScript:  nil,
  1130  					ScriptPubKey:  scriptPubKeys(0),
  1131  					Address:       addresses(0),
  1132  					Amount:        11e5 / 1e8,
  1133  					Vout:          0,
  1134  				},
  1135  				{
  1136  					Confirmations: 1,
  1137  					Spendable:     true,
  1138  					TxID:          txIDs[1],
  1139  					RedeemScript:  nil,
  1140  					ScriptPubKey:  scriptPubKeys(1),
  1141  					Address:       addresses(1),
  1142  					Amount:        25e5 / 1e8,
  1143  					Vout:          0,
  1144  				},
  1145  			},
  1146  			balance: 46e5,
  1147  			expectedCoins: []asset.Coins{
  1148  				{NewOutput(txHashes[0], 0, 11e5)},
  1149  			},
  1150  			expectedRedeemScripts: [][]dex.Bytes{
  1151  				{nil},
  1152  			},
  1153  			expectedLockedCoins: []*RPCOutpoint{
  1154  				{txHashes[0].String(), 0},
  1155  			},
  1156  		},
  1157  		{ // "split not allowed, need to fund in increasing order"
  1158  			name: "no split, need to fund in increasing order",
  1159  			multiOrder: &asset.MultiOrder{
  1160  				Values: []*asset.MultiOrderValue{
  1161  					{
  1162  						Value:        2e6,
  1163  						MaxSwapCount: 2,
  1164  					},
  1165  					{
  1166  						Value:        11e5,
  1167  						MaxSwapCount: 1,
  1168  					},
  1169  					{
  1170  						Value:        9e5,
  1171  						MaxSwapCount: 1,
  1172  					},
  1173  				},
  1174  				MaxFeeRate:    maxFeeRate,
  1175  				FeeSuggestion: feeSuggestion,
  1176  				Options: map[string]string{
  1177  					multiSplitKey: "false",
  1178  				},
  1179  			},
  1180  			maxLock: 50e5,
  1181  			utxos: []*ListUnspentResult{
  1182  				{
  1183  					Confirmations: 1,
  1184  					Spendable:     true,
  1185  					TxID:          txIDs[0],
  1186  					RedeemScript:  nil,
  1187  					ScriptPubKey:  scriptPubKeys(0),
  1188  					Address:       addresses(0),
  1189  					Amount:        11e5 / 1e8,
  1190  					Vout:          0,
  1191  				},
  1192  				{
  1193  					Confirmations: 1,
  1194  					Spendable:     true,
  1195  					TxID:          txIDs[1],
  1196  					RedeemScript:  nil,
  1197  					ScriptPubKey:  scriptPubKeys(1),
  1198  					Address:       addresses(1),
  1199  					Amount:        13e5 / 1e8,
  1200  					Vout:          0,
  1201  				},
  1202  				{
  1203  					Confirmations: 1,
  1204  					Spendable:     true,
  1205  					TxID:          txIDs[2],
  1206  					RedeemScript:  nil,
  1207  					ScriptPubKey:  scriptPubKeys(2),
  1208  					Address:       addresses(2),
  1209  					Amount:        26e5 / 1e8,
  1210  					Vout:          0,
  1211  				},
  1212  			},
  1213  			balance: 50e5,
  1214  			expectedCoins: []asset.Coins{
  1215  				{NewOutput(txHashes[2], 0, 26e5)},
  1216  				{NewOutput(txHashes[1], 0, 13e5)},
  1217  				{NewOutput(txHashes[0], 0, 11e5)},
  1218  			},
  1219  			expectedRedeemScripts: [][]dex.Bytes{
  1220  				{nil},
  1221  				{nil},
  1222  				{nil},
  1223  			},
  1224  			expectedLockedCoins: []*RPCOutpoint{
  1225  				{txHashes[0].String(), 0},
  1226  				{txHashes[1].String(), 0},
  1227  				{txHashes[2].String(), 0},
  1228  			},
  1229  		},
  1230  		{ // "split allowed, no split required"
  1231  			name: "split allowed, no split required",
  1232  			multiOrder: &asset.MultiOrder{
  1233  				Values: []*asset.MultiOrderValue{
  1234  					{
  1235  						Value:        1e6,
  1236  						MaxSwapCount: 1,
  1237  					},
  1238  					{
  1239  						Value:        2e6,
  1240  						MaxSwapCount: 2,
  1241  					},
  1242  				},
  1243  				MaxFeeRate:    maxFeeRate,
  1244  				FeeSuggestion: feeSuggestion,
  1245  				Options: map[string]string{
  1246  					multiSplitKey: "true",
  1247  				},
  1248  			},
  1249  			allOrNothing: false,
  1250  			maxLock:      43e5,
  1251  			utxos: []*ListUnspentResult{
  1252  				{
  1253  					Confirmations: 1,
  1254  					Spendable:     true,
  1255  					TxID:          txIDs[2],
  1256  					RedeemScript:  nil,
  1257  					ScriptPubKey:  scriptPubKeys(2),
  1258  					Address:       addresses(2),
  1259  					Amount:        1e6 / 1e8,
  1260  					Vout:          0,
  1261  				},
  1262  				{
  1263  					Confirmations: 1,
  1264  					Spendable:     true,
  1265  					TxID:          txIDs[0],
  1266  					RedeemScript:  nil,
  1267  					ScriptPubKey:  scriptPubKeys(0),
  1268  					Address:       addresses(0),
  1269  					Amount:        11e5 / 1e8,
  1270  					Vout:          0,
  1271  				},
  1272  				{
  1273  					Confirmations: 1,
  1274  					Spendable:     true,
  1275  					TxID:          txIDs[1],
  1276  					RedeemScript:  nil,
  1277  					ScriptPubKey:  scriptPubKeys(1),
  1278  					Address:       addresses(1),
  1279  					Amount:        22e5 / 1e8,
  1280  					Vout:          0,
  1281  				},
  1282  			},
  1283  			balance: 43e5,
  1284  			expectedCoins: []asset.Coins{
  1285  				{NewOutput(txHashes[0], 0, 11e5)},
  1286  				{NewOutput(txHashes[1], 0, 22e5)},
  1287  			},
  1288  			expectedRedeemScripts: [][]dex.Bytes{
  1289  				{nil},
  1290  				{nil},
  1291  			},
  1292  			expectedLockedCoins: []*RPCOutpoint{
  1293  				{txHashes[0].String(), 0},
  1294  				{txHashes[1].String(), 0},
  1295  			},
  1296  		},
  1297  		{ // "split allowed, can fund both with split"
  1298  			name: "split allowed, can fund both with split",
  1299  			multiOrder: &asset.MultiOrder{
  1300  				Values: []*asset.MultiOrderValue{
  1301  					{
  1302  						Value:        15e5,
  1303  						MaxSwapCount: 2,
  1304  					},
  1305  					{
  1306  						Value:        15e5,
  1307  						MaxSwapCount: 2,
  1308  					},
  1309  				},
  1310  				MaxFeeRate:    maxFeeRate,
  1311  				FeeSuggestion: feeSuggestion,
  1312  				Options: map[string]string{
  1313  					multiSplitKey: "true",
  1314  				},
  1315  			},
  1316  			utxos: []*ListUnspentResult{
  1317  				{
  1318  					Confirmations: 1,
  1319  					Spendable:     true,
  1320  					TxID:          txIDs[0],
  1321  					RedeemScript:  nil,
  1322  					ScriptPubKey:  scriptPubKeys(0),
  1323  					Address:       addresses(0),
  1324  					Amount:        1e6 / 1e8,
  1325  					Vout:          0,
  1326  				},
  1327  				{
  1328  					Confirmations: 1,
  1329  					Spendable:     true,
  1330  					TxID:          txIDs[1],
  1331  					RedeemScript:  nil,
  1332  					ScriptPubKey:  scriptPubKeys(1),
  1333  					Address:       addresses(1),
  1334  					Amount:        (2*float64(requiredForOrder(15e5, 2)) + float64(expectedSplitFee(2, 2)) - 1e6) / 1e8,
  1335  					Vout:          0,
  1336  				},
  1337  			},
  1338  			maxLock:         2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(2, 2),
  1339  			balance:         2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(2, 2),
  1340  			expectSendRawTx: true,
  1341  			expectedInputs: []*wire.TxIn{
  1342  				{
  1343  					PreviousOutPoint: wire.OutPoint{
  1344  						Hash:  *txHashes[1],
  1345  						Index: 0,
  1346  					},
  1347  				},
  1348  				{
  1349  					PreviousOutPoint: wire.OutPoint{
  1350  						Hash:  *txHashes[0],
  1351  						Index: 0,
  1352  					},
  1353  				},
  1354  			},
  1355  			expectedOutputs: []*wire.TxOut{
  1356  				wire.NewTxOut(requiredForOrder(15e5, 2), []byte{}),
  1357  				wire.NewTxOut(requiredForOrder(15e5, 2), []byte{}),
  1358  			},
  1359  			expectedSplitFee: expectedSplitFee(2, 2),
  1360  			expectedRedeemScripts: [][]dex.Bytes{
  1361  				{nil},
  1362  				{nil},
  1363  			},
  1364  		},
  1365  		{ // "split allowed, cannot fund both with split"
  1366  			name: "split allowed, cannot fund both with split",
  1367  			multiOrder: &asset.MultiOrder{
  1368  				Values: []*asset.MultiOrderValue{
  1369  					{
  1370  						Value:        15e5,
  1371  						MaxSwapCount: 2,
  1372  					},
  1373  					{
  1374  						Value:        15e5,
  1375  						MaxSwapCount: 2,
  1376  					},
  1377  				},
  1378  				MaxFeeRate:    maxFeeRate,
  1379  				FeeSuggestion: feeSuggestion,
  1380  				Options: map[string]string{
  1381  					multiSplitKey: "true",
  1382  				},
  1383  			},
  1384  			utxos: []*ListUnspentResult{
  1385  				{
  1386  					Confirmations: 1,
  1387  					Spendable:     true,
  1388  					TxID:          txIDs[0],
  1389  					RedeemScript:  nil,
  1390  					ScriptPubKey:  scriptPubKeys(0),
  1391  					Address:       addresses(0),
  1392  					Amount:        1e6 / 1e8,
  1393  					Vout:          0,
  1394  				},
  1395  				{
  1396  					Confirmations: 1,
  1397  					Spendable:     true,
  1398  					TxID:          txIDs[1],
  1399  					RedeemScript:  nil,
  1400  					ScriptPubKey:  scriptPubKeys(1),
  1401  					Address:       addresses(1),
  1402  					Amount:        (2*float64(requiredForOrder(15e5, 2)) + float64(expectedSplitFee(2, 2)) - 1e6) / 1e8,
  1403  					Vout:          0,
  1404  				},
  1405  			},
  1406  			maxLock:   2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(2, 2) - 1,
  1407  			balance:   2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(2, 2) - 1,
  1408  			expectErr: true,
  1409  		},
  1410  		{ // "can fund both with split and respect maxLock"
  1411  			name: "can fund both with split and respect maxLock",
  1412  			multiOrder: &asset.MultiOrder{
  1413  				Values: []*asset.MultiOrderValue{
  1414  					{
  1415  						Value:        15e5,
  1416  						MaxSwapCount: 2,
  1417  					},
  1418  					{
  1419  						Value:        15e5,
  1420  						MaxSwapCount: 2,
  1421  					},
  1422  				},
  1423  				MaxFeeRate:    maxFeeRate,
  1424  				FeeSuggestion: feeSuggestion,
  1425  				Options: map[string]string{
  1426  					multiSplitKey: "true",
  1427  				},
  1428  			},
  1429  			utxos: []*ListUnspentResult{
  1430  				{
  1431  					Confirmations: 1,
  1432  					Spendable:     true,
  1433  					TxID:          txIDs[0],
  1434  					RedeemScript:  nil,
  1435  					ScriptPubKey:  scriptPubKeys(0),
  1436  					Address:       addresses(0),
  1437  					Amount:        float64(50e5) / 1e8,
  1438  					Vout:          0,
  1439  				},
  1440  			},
  1441  			balance:         50e5,
  1442  			maxLock:         2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(1, 2),
  1443  			expectSendRawTx: true,
  1444  			expectedInputs: []*wire.TxIn{
  1445  				{
  1446  					PreviousOutPoint: wire.OutPoint{
  1447  						Hash:  *txHashes[0],
  1448  						Index: 0,
  1449  					},
  1450  				},
  1451  			},
  1452  			expectedOutputs: []*wire.TxOut{
  1453  				wire.NewTxOut(requiredForOrder(15e5, 2), []byte{}),
  1454  				wire.NewTxOut(requiredForOrder(15e5, 2), []byte{}),
  1455  			},
  1456  			expectedChange:   50e5 - (2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(1, 3)),
  1457  			expectedSplitFee: expectedSplitFee(1, 3),
  1458  			expectedRedeemScripts: [][]dex.Bytes{
  1459  				{nil},
  1460  				{nil},
  1461  			},
  1462  		},
  1463  		{ // "cannot fund both with split and respect maxLock"
  1464  			name: "cannot fund both with split and respect maxLock",
  1465  			multiOrder: &asset.MultiOrder{
  1466  				Values: []*asset.MultiOrderValue{
  1467  					{
  1468  						Value:        15e5,
  1469  						MaxSwapCount: 2,
  1470  					},
  1471  					{
  1472  						Value:        15e5,
  1473  						MaxSwapCount: 2,
  1474  					},
  1475  				},
  1476  				MaxFeeRate:    maxFeeRate,
  1477  				FeeSuggestion: feeSuggestion,
  1478  				Options: map[string]string{
  1479  					multiSplitKey: "true",
  1480  				},
  1481  			},
  1482  			utxos: []*ListUnspentResult{
  1483  				{
  1484  					Confirmations: 1,
  1485  					Spendable:     true,
  1486  					TxID:          txIDs[0],
  1487  					RedeemScript:  nil,
  1488  					ScriptPubKey:  scriptPubKeys(0),
  1489  					Address:       addresses(0),
  1490  					Amount:        float64(50e5) / 1e8,
  1491  					Vout:          0,
  1492  				},
  1493  			},
  1494  			balance:   50e5,
  1495  			maxLock:   2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(1, 2) - 1,
  1496  			expectErr: true,
  1497  		},
  1498  		{ // "split allowed, can fund both with split with bond reserves"
  1499  			name: "split allowed, can fund both with split with bond reserves",
  1500  			multiOrder: &asset.MultiOrder{
  1501  				Values: []*asset.MultiOrderValue{
  1502  					{
  1503  						Value:        15e5,
  1504  						MaxSwapCount: 2,
  1505  					},
  1506  					{
  1507  						Value:        15e5,
  1508  						MaxSwapCount: 2,
  1509  					},
  1510  				},
  1511  				MaxFeeRate:    maxFeeRate,
  1512  				FeeSuggestion: feeSuggestion,
  1513  				Options: map[string]string{
  1514  					multiSplitKey: "true",
  1515  				},
  1516  			},
  1517  			bondReserves: 2e6,
  1518  			utxos: []*ListUnspentResult{
  1519  				{
  1520  					Confirmations: 1,
  1521  					Spendable:     true,
  1522  					TxID:          txIDs[0],
  1523  					RedeemScript:  nil,
  1524  					ScriptPubKey:  scriptPubKeys(0),
  1525  					Address:       addresses(0),
  1526  					Amount:        (2*float64(requiredForOrder(15e5, 2)) + 2e6 + float64(expectedSplitFee(1, 3))) / 1e8,
  1527  					Vout:          0,
  1528  				},
  1529  			},
  1530  			balance:         2e6 + 2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(1, 3),
  1531  			maxLock:         2e6 + 2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(1, 3),
  1532  			expectSendRawTx: true,
  1533  			expectedInputs: []*wire.TxIn{
  1534  				{
  1535  					PreviousOutPoint: wire.OutPoint{
  1536  						Hash:  *txHashes[0],
  1537  						Index: 0,
  1538  					},
  1539  				},
  1540  			},
  1541  			expectedOutputs: []*wire.TxOut{
  1542  				wire.NewTxOut(requiredForOrder(15e5, 2), []byte{}),
  1543  				wire.NewTxOut(requiredForOrder(15e5, 2), []byte{}),
  1544  			},
  1545  			expectedChange:   2e6,
  1546  			expectedSplitFee: expectedSplitFee(1, 3),
  1547  			expectedRedeemScripts: [][]dex.Bytes{
  1548  				{nil},
  1549  				{nil},
  1550  			},
  1551  		},
  1552  		{ // "split allowed, cannot fund both with split and keep and bond reserves"
  1553  			name: "split allowed, cannot fund both with split and keep and bond reserves",
  1554  			multiOrder: &asset.MultiOrder{
  1555  				Values: []*asset.MultiOrderValue{
  1556  					{
  1557  						Value:        15e5,
  1558  						MaxSwapCount: 2,
  1559  					},
  1560  					{
  1561  						Value:        15e5,
  1562  						MaxSwapCount: 2,
  1563  					},
  1564  				},
  1565  				MaxFeeRate:    maxFeeRate,
  1566  				FeeSuggestion: feeSuggestion,
  1567  				Options: map[string]string{
  1568  					multiSplitKey: "true",
  1569  				},
  1570  			},
  1571  			bondReserves: 2e6,
  1572  			utxos: []*ListUnspentResult{
  1573  				{
  1574  					Confirmations: 1,
  1575  					Spendable:     true,
  1576  					TxID:          txIDs[0],
  1577  					RedeemScript:  nil,
  1578  					ScriptPubKey:  scriptPubKeys(0),
  1579  					Address:       addresses(0),
  1580  					Amount:        ((2*float64(requiredForOrder(15e5, 2)) + 2e6 + float64(expectedSplitFee(1, 3))) / 1e8) - 1/1e8,
  1581  					Vout:          0,
  1582  				},
  1583  			},
  1584  			balance:   2e6 + 2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(1, 3) - 1,
  1585  			maxLock:   2e6 + 2*uint64(requiredForOrder(15e5, 2)) + expectedSplitFee(1, 3) - 1,
  1586  			expectErr: true,
  1587  		},
  1588  		{ // "split with buffer"
  1589  			name: "split with buffer",
  1590  			multiOrder: &asset.MultiOrder{
  1591  				Values: []*asset.MultiOrderValue{
  1592  					{
  1593  						Value:        15e5,
  1594  						MaxSwapCount: 2,
  1595  					},
  1596  					{
  1597  						Value:        15e5,
  1598  						MaxSwapCount: 2,
  1599  					},
  1600  				},
  1601  				MaxFeeRate:    maxFeeRate,
  1602  				FeeSuggestion: feeSuggestion,
  1603  				Options: map[string]string{
  1604  					multiSplitKey:       "true",
  1605  					multiSplitBufferKey: "10",
  1606  				},
  1607  			},
  1608  			utxos: []*ListUnspentResult{
  1609  				{
  1610  					Confirmations: 1,
  1611  					Spendable:     true,
  1612  					TxID:          txIDs[0],
  1613  					RedeemScript:  nil,
  1614  					ScriptPubKey:  scriptPubKeys(0),
  1615  					Address:       addresses(0),
  1616  					Amount:        (2*float64(requiredForOrder(15e5, 2)*110/100) + float64(expectedSplitFee(1, 2))) / 1e8,
  1617  					Vout:          0,
  1618  				},
  1619  			},
  1620  			balance:         2*uint64(requiredForOrder(15e5, 2)*110/100) + expectedSplitFee(1, 2),
  1621  			maxLock:         2*uint64(requiredForOrder(15e5, 2)*110/100) + expectedSplitFee(1, 2),
  1622  			expectSendRawTx: true,
  1623  			expectedInputs: []*wire.TxIn{
  1624  				{
  1625  					PreviousOutPoint: wire.OutPoint{
  1626  						Hash:  *txHashes[0],
  1627  						Index: 0,
  1628  					},
  1629  				},
  1630  			},
  1631  			expectedOutputs: []*wire.TxOut{
  1632  				wire.NewTxOut(requiredForOrder(15e5, 2)*110/100, []byte{}),
  1633  				wire.NewTxOut(requiredForOrder(15e5, 2)*110/100, []byte{}),
  1634  			},
  1635  			expectedSplitFee: expectedSplitFee(1, 2),
  1636  			expectedRedeemScripts: [][]dex.Bytes{
  1637  				{nil},
  1638  				{nil},
  1639  			},
  1640  		},
  1641  		{ // "split, maxLock too low to fund buffer"
  1642  			name: "split, maxLock too low to fund buffer",
  1643  			multiOrder: &asset.MultiOrder{
  1644  				Values: []*asset.MultiOrderValue{
  1645  					{
  1646  						Value:        15e5,
  1647  						MaxSwapCount: 2,
  1648  					},
  1649  					{
  1650  						Value:        15e5,
  1651  						MaxSwapCount: 2,
  1652  					},
  1653  				},
  1654  				MaxFeeRate:    maxFeeRate,
  1655  				FeeSuggestion: feeSuggestion,
  1656  				Options: map[string]string{
  1657  					multiSplitKey:       "true",
  1658  					multiSplitBufferKey: "10",
  1659  				},
  1660  			},
  1661  			utxos: []*ListUnspentResult{
  1662  				{
  1663  					Confirmations: 1,
  1664  					Spendable:     true,
  1665  					TxID:          txIDs[0],
  1666  					RedeemScript:  nil,
  1667  					ScriptPubKey:  scriptPubKeys(0),
  1668  					Address:       addresses(0),
  1669  					Amount:        (2*float64(requiredForOrder(15e5, 2)*110/100) + float64(expectedSplitFee(1, 2))) / 1e8,
  1670  					Vout:          0,
  1671  				},
  1672  			},
  1673  			balance:   2*uint64(requiredForOrder(15e5, 2)*110/100) + expectedSplitFee(1, 2),
  1674  			maxLock:   2*uint64(requiredForOrder(15e5, 2)*110/100) + expectedSplitFee(1, 2) - 1,
  1675  			expectErr: true,
  1676  		},
  1677  		{ // "only one order needs a split, rest can be funded without"
  1678  			name: "only one order needs a split, rest can be funded without",
  1679  			multiOrder: &asset.MultiOrder{
  1680  				Values: []*asset.MultiOrderValue{
  1681  					{
  1682  						Value:        1e6,
  1683  						MaxSwapCount: 2,
  1684  					},
  1685  					{
  1686  						Value:        1e6,
  1687  						MaxSwapCount: 2,
  1688  					},
  1689  					{
  1690  						Value:        1e6,
  1691  						MaxSwapCount: 2,
  1692  					},
  1693  				},
  1694  				MaxFeeRate:    maxFeeRate,
  1695  				FeeSuggestion: feeSuggestion,
  1696  				Options: map[string]string{
  1697  					multiSplitKey: "true",
  1698  				},
  1699  			},
  1700  			utxos: []*ListUnspentResult{
  1701  				{
  1702  					Confirmations: 1,
  1703  					Spendable:     true,
  1704  					TxID:          txIDs[0],
  1705  					RedeemScript:  nil,
  1706  					ScriptPubKey:  scriptPubKeys(0),
  1707  					Address:       addresses(0),
  1708  					Amount:        12e5 / 1e8,
  1709  					Vout:          0,
  1710  				},
  1711  				{
  1712  					Confirmations: 1,
  1713  					Spendable:     true,
  1714  					TxID:          txIDs[1],
  1715  					RedeemScript:  nil,
  1716  					ScriptPubKey:  scriptPubKeys(1),
  1717  					Address:       addresses(1),
  1718  					Amount:        12e5 / 1e8,
  1719  					Vout:          0,
  1720  				},
  1721  				{
  1722  					Confirmations: 1,
  1723  					Spendable:     true,
  1724  					TxID:          txIDs[2],
  1725  					RedeemScript:  nil,
  1726  					ScriptPubKey:  scriptPubKeys(2),
  1727  					Address:       addresses(2),
  1728  					Amount:        120e5 / 1e8,
  1729  					Vout:          0,
  1730  				},
  1731  			},
  1732  			maxLock:         50e5,
  1733  			balance:         144e5,
  1734  			expectSendRawTx: true,
  1735  			expectedInputs: []*wire.TxIn{
  1736  				{
  1737  					PreviousOutPoint: wire.OutPoint{
  1738  						Hash:  *txHashes[2],
  1739  						Index: 0,
  1740  					},
  1741  				},
  1742  			},
  1743  			expectedOutputs: []*wire.TxOut{
  1744  				wire.NewTxOut(requiredForOrder(1e6, 2), []byte{}),
  1745  				wire.NewTxOut(120e5-requiredForOrder(1e6, 2)-int64(expectedSplitFee(1, 2)), []byte{}),
  1746  			},
  1747  			expectedSplitFee: expectedSplitFee(1, 2),
  1748  			expectedRedeemScripts: [][]dex.Bytes{
  1749  				{nil},
  1750  				{nil},
  1751  				{nil},
  1752  			},
  1753  			expectedCoins: []asset.Coins{
  1754  				{NewOutput(txHashes[0], 0, 12e5)},
  1755  				{NewOutput(txHashes[1], 0, 12e5)},
  1756  				nil,
  1757  			},
  1758  		},
  1759  		{ // "only one order needs a split due to bond reserves, rest funded without"
  1760  			name: "only one order needs a split, rest can be funded without",
  1761  			multiOrder: &asset.MultiOrder{
  1762  				Values: []*asset.MultiOrderValue{
  1763  					{
  1764  						Value:        1e6,
  1765  						MaxSwapCount: 2,
  1766  					},
  1767  					{
  1768  						Value:        1e6,
  1769  						MaxSwapCount: 2,
  1770  					},
  1771  					{
  1772  						Value:        1e6,
  1773  						MaxSwapCount: 2,
  1774  					},
  1775  				},
  1776  				MaxFeeRate:    maxFeeRate,
  1777  				FeeSuggestion: feeSuggestion,
  1778  				Options: map[string]string{
  1779  					multiSplitKey: "true",
  1780  				},
  1781  			},
  1782  			utxos: []*ListUnspentResult{
  1783  				{
  1784  					Confirmations: 1,
  1785  					Spendable:     true,
  1786  					TxID:          txIDs[0],
  1787  					RedeemScript:  nil,
  1788  					ScriptPubKey:  scriptPubKeys(0),
  1789  					Address:       addresses(0),
  1790  					Amount:        12e5 / 1e8,
  1791  					Vout:          0,
  1792  				},
  1793  				{
  1794  					Confirmations: 1,
  1795  					Spendable:     true,
  1796  					TxID:          txIDs[1],
  1797  					RedeemScript:  nil,
  1798  					ScriptPubKey:  scriptPubKeys(1),
  1799  					Address:       addresses(1),
  1800  					Amount:        12e5 / 1e8,
  1801  					Vout:          0,
  1802  				},
  1803  				{
  1804  					Confirmations: 1,
  1805  					Spendable:     true,
  1806  					TxID:          txIDs[2],
  1807  					RedeemScript:  nil,
  1808  					ScriptPubKey:  scriptPubKeys(2),
  1809  					Address:       addresses(2),
  1810  					Amount:        120e5 / 1e8,
  1811  					Vout:          0,
  1812  				},
  1813  			},
  1814  			maxLock:         0,
  1815  			bondReserves:    1e6,
  1816  			balance:         144e5,
  1817  			expectSendRawTx: true,
  1818  			expectedInputs: []*wire.TxIn{
  1819  				{
  1820  					PreviousOutPoint: wire.OutPoint{
  1821  						Hash:  *txHashes[2],
  1822  						Index: 0,
  1823  					},
  1824  				},
  1825  			},
  1826  			expectedOutputs: []*wire.TxOut{
  1827  				wire.NewTxOut(requiredForOrder(1e6, 2), []byte{}),
  1828  				wire.NewTxOut(120e5-requiredForOrder(1e6, 2)-int64(expectedSplitFee(1, 2)), []byte{}),
  1829  			},
  1830  			expectedSplitFee: expectedSplitFee(1, 2),
  1831  			expectedRedeemScripts: [][]dex.Bytes{
  1832  				{nil},
  1833  				{nil},
  1834  				{nil},
  1835  			},
  1836  			expectedCoins: []asset.Coins{
  1837  				{NewOutput(txHashes[0], 0, 12e5)},
  1838  				{NewOutput(txHashes[1], 0, 12e5)},
  1839  				nil,
  1840  			},
  1841  		},
  1842  	}
  1843  
  1844  	for _, test := range tests {
  1845  		node.listUnspent = test.utxos
  1846  		node.sentRawTx = nil
  1847  		node.lockedCoins = nil
  1848  		node.getBalances = &GetBalancesResult{
  1849  			Mine: Balances{
  1850  				Trusted: toBTC(test.balance),
  1851  			},
  1852  		}
  1853  		wallet.cm.lockedOutputs = make(map[OutPoint]*UTxO)
  1854  		wallet.bondReserves.Store(test.bondReserves)
  1855  
  1856  		allCoins, _, splitFee, err := wallet.FundMultiOrder(test.multiOrder, test.maxLock)
  1857  		if test.expectErr {
  1858  			if err == nil {
  1859  				t.Fatalf("%s: no error returned", test.name)
  1860  			}
  1861  			if strings.Contains(err.Error(), "insufficient funds") {
  1862  				t.Fatalf("%s: unexpected insufficient funds error", test.name)
  1863  			}
  1864  			continue
  1865  		}
  1866  		if err != nil {
  1867  			t.Fatalf("%s: unexpected error: %v", test.name, err)
  1868  		}
  1869  
  1870  		if !test.expectSendRawTx { // no split
  1871  			if node.sentRawTx != nil {
  1872  				t.Fatalf("%s: unexpected transaction sent", test.name)
  1873  			}
  1874  			if len(allCoins) != len(test.expectedCoins) {
  1875  				t.Fatalf("%s: expected %d coins, got %d", test.name, len(test.expectedCoins), len(allCoins))
  1876  			}
  1877  			for i := range allCoins {
  1878  				if len(allCoins[i]) != len(test.expectedCoins[i]) {
  1879  					t.Fatalf("%s: expected %d coins in set %d, got %d", test.name, len(test.expectedCoins[i]), i, len(allCoins[i]))
  1880  				}
  1881  				actual := allCoins[i]
  1882  				expected := test.expectedCoins[i]
  1883  				sort.Slice(actual, func(i, j int) bool {
  1884  					return bytes.Compare(actual[i].ID(), actual[j].ID()) < 0
  1885  				})
  1886  				sort.Slice(expected, func(i, j int) bool {
  1887  					return bytes.Compare(expected[i].ID(), expected[j].ID()) < 0
  1888  				})
  1889  				for j := range actual {
  1890  					if !bytes.Equal(actual[j].ID(), expected[j].ID()) {
  1891  						t.Fatalf("%s: unexpected coin in set %d. expected %s, got %s", test.name, i, expected[j].ID(), actual[j].ID())
  1892  					}
  1893  					if actual[j].Value() != expected[j].Value() {
  1894  						t.Fatalf("%s: unexpected coin value in set %d. expected %d, got %d", test.name, i, expected[j].Value(), actual[j].Value())
  1895  					}
  1896  				}
  1897  			}
  1898  		} else { // expectSplit
  1899  			if node.sentRawTx == nil {
  1900  				t.Fatalf("%s: SendRawTransaction not called", test.name)
  1901  			}
  1902  			if len(node.sentRawTx.TxIn) != len(test.expectedInputs) {
  1903  				t.Fatalf("%s: expected %d inputs, got %d", test.name, len(test.expectedInputs), len(node.sentRawTx.TxIn))
  1904  			}
  1905  			for i, actualIn := range node.sentRawTx.TxIn {
  1906  				expectedIn := test.expectedInputs[i]
  1907  				if !bytes.Equal(actualIn.PreviousOutPoint.Hash[:], expectedIn.PreviousOutPoint.Hash[:]) {
  1908  					t.Fatalf("%s: unexpected input %d hash. expected %s, got %s", test.name, i, expectedIn.PreviousOutPoint.Hash, actualIn.PreviousOutPoint.Hash)
  1909  				}
  1910  				if actualIn.PreviousOutPoint.Index != expectedIn.PreviousOutPoint.Index {
  1911  					t.Fatalf("%s: unexpected input %d index. expected %d, got %d", test.name, i, expectedIn.PreviousOutPoint.Index, actualIn.PreviousOutPoint.Index)
  1912  				}
  1913  			}
  1914  			expectedNumOutputs := len(test.expectedOutputs)
  1915  			if test.expectedChange > 0 {
  1916  				expectedNumOutputs++
  1917  			}
  1918  			if len(node.sentRawTx.TxOut) != expectedNumOutputs {
  1919  				t.Fatalf("%s: expected %d outputs, got %d", test.name, expectedNumOutputs, len(node.sentRawTx.TxOut))
  1920  			}
  1921  
  1922  			for i, expectedOut := range test.expectedOutputs {
  1923  				actualOut := node.sentRawTx.TxOut[i]
  1924  				if actualOut.Value != expectedOut.Value {
  1925  					t.Fatalf("%s: unexpected output %d value. expected %d, got %d", test.name, i, expectedOut.Value, actualOut.Value)
  1926  				}
  1927  			}
  1928  			if test.expectedChange > 0 {
  1929  				actualOut := node.sentRawTx.TxOut[len(node.sentRawTx.TxOut)-1]
  1930  				if uint64(actualOut.Value) != test.expectedChange {
  1931  					t.Fatalf("%s: unexpected change value. expected %d, got %d", test.name, test.expectedChange, actualOut.Value)
  1932  				}
  1933  			}
  1934  
  1935  			if len(test.multiOrder.Values) != len(allCoins) {
  1936  				t.Fatalf("%s: expected %d coins, got %d", test.name, len(test.multiOrder.Values), len(allCoins))
  1937  			}
  1938  			splitTxID := node.sentRawTx.TxHash()
  1939  
  1940  			// This means all coins are split outputs
  1941  			if test.expectedCoins == nil {
  1942  				for i, actualCoin := range allCoins {
  1943  					actualOut := actualCoin[0].(*Output)
  1944  					expectedOut := node.sentRawTx.TxOut[i]
  1945  					if uint64(expectedOut.Value) != actualOut.Val {
  1946  						t.Fatalf("%s: unexpected output %d value. expected %d, got %d", test.name, i, expectedOut.Value, actualOut.Val)
  1947  					}
  1948  					if !bytes.Equal(actualOut.Pt.TxHash[:], splitTxID[:]) {
  1949  						t.Fatalf("%s: unexpected output %d txid. expected %s, got %s", test.name, i, splitTxID, actualOut.Pt.TxHash)
  1950  					}
  1951  				}
  1952  			} else {
  1953  				var splitTxOutputIndex int
  1954  				for i := range allCoins {
  1955  					actual := allCoins[i]
  1956  					expected := test.expectedCoins[i]
  1957  
  1958  					// This means the coins are the split outputs
  1959  					if expected == nil {
  1960  						actualOut := actual[0].(*Output)
  1961  						expectedOut := node.sentRawTx.TxOut[splitTxOutputIndex]
  1962  						if uint64(expectedOut.Value) != actualOut.Val {
  1963  							t.Fatalf("%s: unexpected output %d value. expected %d, got %d", test.name, i, expectedOut.Value, actualOut.Val)
  1964  						}
  1965  						if !bytes.Equal(actualOut.Pt.TxHash[:], splitTxID[:]) {
  1966  							t.Fatalf("%s: unexpected output %d txid. expected %s, got %s", test.name, i, splitTxID, actualOut.Pt.TxHash)
  1967  						}
  1968  						splitTxOutputIndex++
  1969  						continue
  1970  					}
  1971  
  1972  					if len(actual) != len(expected) {
  1973  						t.Fatalf("%s: expected %d coins in set %d, got %d", test.name, len(test.expectedCoins[i]), i, len(allCoins[i]))
  1974  					}
  1975  					sort.Slice(actual, func(i, j int) bool {
  1976  						return bytes.Compare(actual[i].ID(), actual[j].ID()) < 0
  1977  					})
  1978  					sort.Slice(expected, func(i, j int) bool {
  1979  						return bytes.Compare(expected[i].ID(), expected[j].ID()) < 0
  1980  					})
  1981  					for j := range actual {
  1982  						if !bytes.Equal(actual[j].ID(), expected[j].ID()) {
  1983  							t.Fatalf("%s: unexpected coin in set %d. expected %s, got %s", test.name, i, expected[j].ID(), actual[j].ID())
  1984  						}
  1985  						if actual[j].Value() != expected[j].Value() {
  1986  							t.Fatalf("%s: unexpected coin value in set %d. expected %d, got %d", test.name, i, expected[j].Value(), actual[j].Value())
  1987  						}
  1988  					}
  1989  				}
  1990  			}
  1991  
  1992  			// Each split output should be locked
  1993  			if len(node.lockedCoins) != len(allCoins)+len(test.expectedInputs) {
  1994  				t.Fatalf("%s: expected %d locked coins, got %d", test.name, len(allCoins)+len(test.expectedInputs), len(node.lockedCoins))
  1995  			}
  1996  
  1997  		}
  1998  
  1999  		// Check that the right coins are locked and in the fundingCoins map
  2000  		var totalNumCoins int
  2001  		for _, coins := range allCoins {
  2002  			totalNumCoins += len(coins)
  2003  		}
  2004  		if totalNumCoins != len(wallet.cm.lockedOutputs) {
  2005  			t.Fatalf("%s: expected %d funding coins in wallet, got %d", test.name, totalNumCoins, len(wallet.cm.lockedOutputs))
  2006  		}
  2007  		totalNumCoins += len(test.expectedInputs)
  2008  		if totalNumCoins != len(node.lockedCoins) {
  2009  			t.Fatalf("%s: expected %d locked coins, got %d", test.name, totalNumCoins, len(node.lockedCoins))
  2010  		}
  2011  		lockedCoins := make(map[RPCOutpoint]any)
  2012  		for _, coin := range node.lockedCoins {
  2013  			lockedCoins[*coin] = true
  2014  		}
  2015  		checkLockedCoin := func(txHash chainhash.Hash, vout uint32) {
  2016  			if _, ok := lockedCoins[RPCOutpoint{TxID: txHash.String(), Vout: vout}]; !ok {
  2017  				t.Fatalf("%s: expected locked coin %s:%d not found", test.name, txHash, vout)
  2018  			}
  2019  		}
  2020  		checkFundingCoin := func(txHash chainhash.Hash, vout uint32) {
  2021  			if _, ok := wallet.cm.lockedOutputs[OutPoint{TxHash: txHash, Vout: vout}]; !ok {
  2022  				t.Fatalf("%s: expected locked coin %s:%d not found in wallet", test.name, txHash, vout)
  2023  			}
  2024  		}
  2025  		for _, coins := range allCoins {
  2026  			for _, coin := range coins {
  2027  				// decode coin to output
  2028  				out := coin.(*Output)
  2029  				checkLockedCoin(out.Pt.TxHash, out.Pt.Vout)
  2030  				checkFundingCoin(out.Pt.TxHash, out.Pt.Vout)
  2031  			}
  2032  		}
  2033  		for _, expectedIn := range test.expectedInputs {
  2034  			checkLockedCoin(expectedIn.PreviousOutPoint.Hash, expectedIn.PreviousOutPoint.Index)
  2035  		}
  2036  
  2037  		if test.expectedSplitFee != splitFee {
  2038  			t.Fatalf("%s: unexpected split fee. expected %d, got %d", test.name, test.expectedSplitFee, splitFee)
  2039  		}
  2040  	}
  2041  }
  2042  
  2043  func TestMaxFundingFees(t *testing.T) {
  2044  	runRubric(t, testMaxFundingFees)
  2045  }
  2046  
  2047  func testMaxFundingFees(t *testing.T, segwit bool, walletType string) {
  2048  	wallet, _, shutdown := tNewWallet(segwit, walletType)
  2049  	defer shutdown()
  2050  
  2051  	maxFeeRate := uint64(100)
  2052  
  2053  	useSplitOptions := map[string]string{
  2054  		multiSplitKey: "true",
  2055  	}
  2056  	noSplitOptions := map[string]string{
  2057  		multiSplitKey: "false",
  2058  	}
  2059  
  2060  	var inputSize, outputSize uint64
  2061  	if segwit {
  2062  		inputSize = dexbtc.RedeemP2WPKHInputTotalSize
  2063  		outputSize = dexbtc.P2WPKHOutputSize
  2064  	} else {
  2065  		inputSize = dexbtc.RedeemP2PKHInputSize
  2066  		outputSize = dexbtc.P2PKHOutputSize
  2067  	}
  2068  
  2069  	const maxSwaps = 3
  2070  	const numInputs = 12
  2071  	maxFundingFees := wallet.MaxFundingFees(maxSwaps, maxFeeRate, useSplitOptions)
  2072  	expectedFees := maxFeeRate * (inputSize*numInputs + outputSize*(maxSwaps+1) + dexbtc.MinimumTxOverhead)
  2073  	if maxFundingFees != expectedFees {
  2074  		t.Fatalf("unexpected max funding fees. expected %d, got %d", expectedFees, maxFundingFees)
  2075  	}
  2076  
  2077  	maxFundingFees = wallet.MaxFundingFees(maxSwaps, maxFeeRate, noSplitOptions)
  2078  	if maxFundingFees != 0 {
  2079  		t.Fatalf("unexpected max funding fees. expected 0, got %d", maxFundingFees)
  2080  	}
  2081  }
  2082  
  2083  func TestAvailableFund(t *testing.T) {
  2084  	runRubric(t, testAvailableFund)
  2085  }
  2086  
  2087  func testAvailableFund(t *testing.T, segwit bool, walletType string) {
  2088  	wallet, node, shutdown := tNewWallet(segwit, walletType)
  2089  	defer shutdown()
  2090  
  2091  	// With an empty list returned, there should be no error, but the value zero
  2092  	// should be returned.
  2093  	unspents := make([]*ListUnspentResult, 0)
  2094  	node.listUnspent = unspents // only needed for Fund, not Balance
  2095  	node.listLockUnspent = []*RPCOutpoint{}
  2096  	var bals GetBalancesResult
  2097  	node.getBalances = &bals
  2098  	bal, err := wallet.Balance()
  2099  	if err != nil {
  2100  		t.Fatalf("error for zero utxos: %v", err)
  2101  	}
  2102  	if bal.Available != 0 {
  2103  		t.Fatalf("expected available = 0, got %d", bal.Available)
  2104  	}
  2105  	if bal.Immature != 0 {
  2106  		t.Fatalf("expected unconf = 0, got %d", bal.Immature)
  2107  	}
  2108  
  2109  	node.getBalancesErr = tErr
  2110  	_, err = wallet.Balance()
  2111  	if err == nil {
  2112  		t.Fatalf("no wallet error for rpc error")
  2113  	}
  2114  	node.getBalancesErr = nil
  2115  	var littleLots uint64 = 12
  2116  	littleOrder := tLotSize * littleLots
  2117  	littleFunds := calc.RequiredOrderFunds(littleOrder, dexbtc.RedeemP2PKHInputSize, littleLots, tSwapSizeBase, tSwapSize, tBTC.MaxFeeRate)
  2118  	littleUTXO := &ListUnspentResult{
  2119  		TxID:          tTxID,
  2120  		Address:       "1Bggq7Vu5oaoLFV1NNp5KhAzcku83qQhgi",
  2121  		Amount:        float64(littleFunds) / 1e8,
  2122  		Confirmations: 0,
  2123  		ScriptPubKey:  tP2PKH,
  2124  		Spendable:     true,
  2125  		Solvable:      true,
  2126  		SafePtr:       boolPtr(true),
  2127  	}
  2128  	unspents = append(unspents, littleUTXO)
  2129  	node.listUnspent = unspents
  2130  	bals.Mine.Trusted = float64(littleFunds) / 1e8
  2131  	node.getBalances = &bals
  2132  	lockedVal := uint64(1e6)
  2133  	node.listLockUnspent = []*RPCOutpoint{
  2134  		{
  2135  			TxID: tTxID,
  2136  			Vout: 1,
  2137  		},
  2138  	}
  2139  
  2140  	msgTx := makeRawTx([]dex.Bytes{{0x01}, {0x02}}, []*wire.TxIn{dummyInput()})
  2141  	msgTx.TxOut[1].Value = int64(lockedVal)
  2142  	txBuf := bytes.NewBuffer(make([]byte, 0, dexbtc.MsgTxVBytes(msgTx)))
  2143  	msgTx.Serialize(txBuf)
  2144  	const blockHeight = 5
  2145  	blockHash, _ := node.addRawTx(blockHeight, msgTx)
  2146  
  2147  	node.getTransactionMap = map[string]*GetTransactionResult{
  2148  		"any": {
  2149  			BlockHash:  blockHash.String(),
  2150  			BlockIndex: blockHeight,
  2151  			Bytes:      txBuf.Bytes(),
  2152  		}}
  2153  
  2154  	bal, err = wallet.Balance()
  2155  	if err != nil {
  2156  		t.Fatalf("error for 1 utxo: %v", err)
  2157  	}
  2158  	if bal.Available != littleFunds-lockedVal {
  2159  		t.Fatalf("expected available = %d for confirmed utxos, got %d", littleOrder-lockedVal, bal.Available)
  2160  	}
  2161  	if bal.Immature != 0 {
  2162  		t.Fatalf("expected immature = 0, got %d", bal.Immature)
  2163  	}
  2164  	if bal.Locked != lockedVal {
  2165  		t.Fatalf("expected locked = %d, got %d", lockedVal, bal.Locked)
  2166  	}
  2167  
  2168  	var lottaLots uint64 = 100
  2169  	lottaOrder := tLotSize * lottaLots
  2170  	// Add funding for an extra input to accommodate the later combined tests.
  2171  	lottaFunds := calc.RequiredOrderFunds(lottaOrder, 2*dexbtc.RedeemP2PKHInputSize, lottaLots, tSwapSizeBase, tSwapSize, tBTC.MaxFeeRate)
  2172  	lottaUTXO := &ListUnspentResult{
  2173  		TxID:          tTxID,
  2174  		Address:       "1Bggq7Vu5oaoLFV1NNp5KhAzcku83qQhgi",
  2175  		Amount:        float64(lottaFunds) / 1e8,
  2176  		Confirmations: 1,
  2177  		Vout:          1,
  2178  		ScriptPubKey:  tP2PKH,
  2179  		Spendable:     true,
  2180  		Solvable:      true,
  2181  		SafePtr:       boolPtr(true),
  2182  	}
  2183  	unspents = append(unspents, lottaUTXO)
  2184  	littleUTXO.Confirmations = 1
  2185  	node.listUnspent = unspents
  2186  	bals.Mine.Trusted += float64(lottaFunds) / 1e8
  2187  	node.getBalances = &bals
  2188  	bal, err = wallet.Balance()
  2189  	if err != nil {
  2190  		t.Fatalf("error for 2 utxos: %v", err)
  2191  	}
  2192  	if bal.Available != littleFunds+lottaFunds-lockedVal {
  2193  		t.Fatalf("expected available = %d for 2 outputs, got %d", littleFunds+lottaFunds-lockedVal, bal.Available)
  2194  	}
  2195  	if bal.Immature != 0 {
  2196  		t.Fatalf("expected immature = 0 for 2 outputs, got %d", bal.Immature)
  2197  	}
  2198  
  2199  	ord := &asset.Order{
  2200  		Version:       version,
  2201  		Value:         0,
  2202  		MaxSwapCount:  1,
  2203  		MaxFeeRate:    tBTC.MaxFeeRate,
  2204  		FeeSuggestion: feeSuggestion,
  2205  	}
  2206  
  2207  	setOrderValue := func(v uint64) {
  2208  		ord.Value = v
  2209  		ord.MaxSwapCount = v / tLotSize
  2210  	}
  2211  
  2212  	// Zero value
  2213  	_, _, _, err = wallet.FundOrder(ord)
  2214  	if err == nil {
  2215  		t.Fatalf("no funding error for zero value")
  2216  	}
  2217  
  2218  	// Nothing to spend
  2219  	node.listUnspent = []*ListUnspentResult{}
  2220  	setOrderValue(littleOrder)
  2221  	_, _, _, err = wallet.FundOrder(ord)
  2222  	if err == nil {
  2223  		t.Fatalf("no error for zero utxos")
  2224  	}
  2225  	node.listUnspent = unspents
  2226  
  2227  	// RPC error
  2228  	node.listUnspentErr = tErr
  2229  	_, _, _, err = wallet.FundOrder(ord)
  2230  	if err == nil {
  2231  		t.Fatalf("no funding error for rpc error")
  2232  	}
  2233  	node.listUnspentErr = nil
  2234  
  2235  	// Negative response when locking outputs.
  2236  	// There is no way to error locking outpoints in spv
  2237  	if walletType != walletTypeSPV {
  2238  		node.lockUnspentErr = tErr
  2239  		_, _, _, err = wallet.FundOrder(ord)
  2240  		if err == nil {
  2241  			t.Fatalf("no error for lockunspent result = false: %v", err)
  2242  		}
  2243  		node.lockUnspentErr = nil
  2244  	}
  2245  
  2246  	// Fund a little bit, with unsafe littleUTXO.
  2247  	littleUTXO.SafePtr = boolPtr(false)
  2248  	littleUTXO.Confirmations = 0
  2249  	node.listUnspent = unspents
  2250  	spendables, _, _, err := wallet.FundOrder(ord)
  2251  	if err != nil {
  2252  		t.Fatalf("error funding small amount: %v", err)
  2253  	}
  2254  	if len(spendables) != 1 {
  2255  		t.Fatalf("expected 1 spendable, got %d", len(spendables))
  2256  	}
  2257  	v := spendables[0].Value()
  2258  	if v != lottaFunds { // has to pick the larger output
  2259  		t.Fatalf("expected spendable of value %d, got %d", lottaFunds, v)
  2260  	}
  2261  
  2262  	// Return/unlock the reserved coins to avoid warning in subsequent tests
  2263  	// about fundingCoins map containing the coins already. i.e.
  2264  	// "Known order-funding coin %v returned by listunspent"
  2265  
  2266  	_ = wallet.ReturnCoins(spendables)
  2267  
  2268  	// Now with safe confirmed littleUTXO.
  2269  	littleUTXO.SafePtr = boolPtr(true)
  2270  	littleUTXO.Confirmations = 2
  2271  	node.listUnspent = unspents
  2272  	spendables, _, _, err = wallet.FundOrder(ord)
  2273  	if err != nil {
  2274  		t.Fatalf("error funding small amount: %v", err)
  2275  	}
  2276  	if len(spendables) != 1 {
  2277  		t.Fatalf("expected 1 spendable, got %d", len(spendables))
  2278  	}
  2279  	v = spendables[0].Value()
  2280  	if v != littleFunds {
  2281  		t.Fatalf("expected spendable of value %d, got %d", littleFunds, v)
  2282  	}
  2283  	_ = wallet.ReturnCoins(spendables)
  2284  
  2285  	// Adding a fee bump should now require the larger UTXO.
  2286  	ord.Options = map[string]string{swapFeeBumpKey: "1.5"}
  2287  	spendables, _, _, err = wallet.FundOrder(ord)
  2288  	if err != nil {
  2289  		t.Fatalf("error funding bumped fees: %v", err)
  2290  	}
  2291  	if len(spendables) != 1 {
  2292  		t.Fatalf("expected 1 spendable, got %d", len(spendables))
  2293  	}
  2294  	v = spendables[0].Value()
  2295  	if v != lottaFunds { // picks the bigger output because it is confirmed
  2296  		t.Fatalf("expected bumped fee utxo of value %d, got %d", littleFunds, v)
  2297  	}
  2298  	ord.Options = nil
  2299  	littleUTXO.Confirmations = 0
  2300  	_ = wallet.ReturnCoins(spendables)
  2301  
  2302  	// Make lottaOrder unconfirmed like littleOrder, favoring little now.
  2303  	lottaUTXO.Confirmations = 0
  2304  	node.listUnspent = unspents
  2305  	spendables, _, _, err = wallet.FundOrder(ord)
  2306  	if err != nil {
  2307  		t.Fatalf("error funding small amount: %v", err)
  2308  	}
  2309  	if len(spendables) != 1 {
  2310  		t.Fatalf("expected 1 spendable, got %d", len(spendables))
  2311  	}
  2312  	v = spendables[0].Value()
  2313  	if v != littleFunds { // now picks the smaller output
  2314  		t.Fatalf("expected spendable of value %d, got %d", littleFunds, v)
  2315  	}
  2316  	_ = wallet.ReturnCoins(spendables)
  2317  
  2318  	// Fund a lotta bit, covered by just the lottaBit UTXO.
  2319  	setOrderValue(lottaOrder)
  2320  	spendables, _, fees, err := wallet.FundOrder(ord)
  2321  	if err != nil {
  2322  		t.Fatalf("error funding large amount: %v", err)
  2323  	}
  2324  	if len(spendables) != 1 {
  2325  		t.Fatalf("expected 1 spendable, got %d", len(spendables))
  2326  	}
  2327  	if fees != 0 {
  2328  		t.Fatalf("expected no fees, got %d", fees)
  2329  	}
  2330  	v = spendables[0].Value()
  2331  	if v != lottaFunds {
  2332  		t.Fatalf("expected spendable of value %d, got %d", lottaFunds, v)
  2333  	}
  2334  	_ = wallet.ReturnCoins(spendables)
  2335  
  2336  	// require both spendables
  2337  	extraLottaOrder := littleOrder + lottaOrder
  2338  	extraLottaLots := littleLots + lottaLots
  2339  	setOrderValue(extraLottaOrder)
  2340  	spendables, _, fees, err = wallet.FundOrder(ord)
  2341  	if err != nil {
  2342  		t.Fatalf("error funding large amount: %v", err)
  2343  	}
  2344  	if len(spendables) != 2 {
  2345  		t.Fatalf("expected 2 spendable, got %d", len(spendables))
  2346  	}
  2347  	if fees != 0 {
  2348  		t.Fatalf("expected no fees, got %d", fees)
  2349  	}
  2350  	v = spendables[0].Value()
  2351  	if v != lottaFunds {
  2352  		t.Fatalf("expected spendable of value %d, got %d", lottaFunds, v)
  2353  	}
  2354  	_ = wallet.ReturnCoins(spendables)
  2355  
  2356  	// Not enough to cover transaction fees.
  2357  	tweak := float64(littleFunds+lottaFunds-calc.RequiredOrderFunds(extraLottaOrder, 2*dexbtc.RedeemP2PKHInputSize, extraLottaLots, tSwapSizeBase, tSwapSize, tBTC.MaxFeeRate)+1) / 1e8
  2358  	lottaUTXO.Amount -= tweak
  2359  	node.listUnspent = unspents
  2360  	_, _, _, err = wallet.FundOrder(ord)
  2361  	if err == nil {
  2362  		t.Fatalf("no error when not enough to cover tx fees")
  2363  	}
  2364  	lottaUTXO.Amount += tweak
  2365  	node.listUnspent = unspents
  2366  
  2367  	// Prepare for a split transaction.
  2368  	baggageFees := tBTC.MaxFeeRate * splitTxBaggage
  2369  	if segwit {
  2370  		node.changeAddr = tP2WPKHAddr
  2371  		node.newAddress = tP2WPKHAddr
  2372  	} else {
  2373  		node.changeAddr = tP2PKHAddr
  2374  		node.newAddress = tP2PKHAddr
  2375  	}
  2376  	node.walletCfg.useSplitTx = true
  2377  	// No error when no split performed cuz math.
  2378  	coins, _, fees, err := wallet.FundOrder(ord)
  2379  	if err != nil {
  2380  		t.Fatalf("error for no-split split: %v", err)
  2381  	}
  2382  	if fees != 0 {
  2383  		t.Fatalf("no-split split returned non-zero fees: %d", fees)
  2384  	}
  2385  	// Should be both coins.
  2386  	if len(coins) != 2 {
  2387  		t.Fatalf("no-split split didn't return both coins")
  2388  	}
  2389  	_ = wallet.ReturnCoins(coins)
  2390  
  2391  	// No split because not standing order.
  2392  	ord.Immediate = true
  2393  	coins, _, fees, err = wallet.FundOrder(ord)
  2394  	if err != nil {
  2395  		t.Fatalf("error for no-split split: %v", err)
  2396  	}
  2397  	ord.Immediate = false
  2398  	if len(coins) != 2 {
  2399  		t.Fatalf("no-split split didn't return both coins")
  2400  	}
  2401  	if fees != 0 {
  2402  		t.Fatalf("no-split split returned non-zero fees: %d", fees)
  2403  	}
  2404  	_ = wallet.ReturnCoins(coins)
  2405  
  2406  	var inputSize, outputSize uint64
  2407  	if wallet.segwit {
  2408  		inputSize = dexbtc.RedeemP2WPKHInputTotalSize
  2409  		outputSize = dexbtc.P2WPKHOutputSize
  2410  	} else {
  2411  		inputSize = dexbtc.RedeemP2PKHInputSize
  2412  		outputSize = dexbtc.P2PKHOutputSize
  2413  	}
  2414  	expectedTxSize := dexbtc.MinimumTxOverhead + 2*inputSize + 2*outputSize
  2415  	if wallet.segwit {
  2416  		expectedTxSize -= 1 // double counted wittness flag
  2417  	}
  2418  	expectedFees := expectedTxSize * feeSuggestion
  2419  
  2420  	// With a little more locked, the split should be performed.
  2421  	node.signFunc = func(tx *wire.MsgTx) {
  2422  		signFunc(tx, 0, wallet.segwit)
  2423  	}
  2424  	lottaUTXO.Amount += float64(baggageFees) / 1e8
  2425  	node.listUnspent = unspents
  2426  	coins, _, fees, err = wallet.FundOrder(ord)
  2427  	if err != nil {
  2428  		t.Fatalf("error for split tx: %v", err)
  2429  	}
  2430  	// Should be just one coin.
  2431  	if len(coins) != 1 {
  2432  		t.Fatalf("split failed - coin count != 1")
  2433  	}
  2434  	if node.sentRawTx == nil {
  2435  		t.Fatalf("split failed - no tx sent")
  2436  	}
  2437  	if fees != expectedFees {
  2438  		t.Fatalf("split returned unexpected fees. wanted %d, got %d", expectedFees, fees)
  2439  	}
  2440  	_ = wallet.ReturnCoins(coins)
  2441  
  2442  	// The split should also be added if we set the option at order time.
  2443  	node.walletCfg.useSplitTx = false
  2444  	ord.Options = map[string]string{splitKey: "true"}
  2445  	coins, _, fees, err = wallet.FundOrder(ord)
  2446  	if err != nil {
  2447  		t.Fatalf("error for forced split tx: %v", err)
  2448  	}
  2449  	// Should be just one coin still.
  2450  	if len(coins) != 1 {
  2451  		t.Fatalf("forced split failed - coin count != 1")
  2452  	}
  2453  	if fees != expectedFees {
  2454  		t.Fatalf("split returned unexpected fees. wanted %d, got %d", expectedFees, fees)
  2455  	}
  2456  	_ = wallet.ReturnCoins(coins)
  2457  
  2458  	// // Hit some error paths.
  2459  
  2460  	// Split transaction requires valid fee suggestion.
  2461  	// TODO:
  2462  	// 1.0: Error when no suggestion.
  2463  	// ord.FeeSuggestion = 0
  2464  	// _, _, err = wallet.FundOrder(ord)
  2465  	// if err == nil {
  2466  	// 	t.Fatalf("no error for no fee suggestions on split tx")
  2467  	// }
  2468  	ord.FeeSuggestion = tBTC.MaxFeeRate + 1
  2469  	_, _, _, err = wallet.FundOrder(ord)
  2470  	if err == nil {
  2471  		t.Fatalf("no error for no fee suggestions on split tx")
  2472  	}
  2473  	// Check success again.
  2474  	ord.FeeSuggestion = tBTC.MaxFeeRate
  2475  	coins, _, _, err = wallet.FundOrder(ord)
  2476  	if err != nil {
  2477  		t.Fatalf("error fixing split tx: %v", err)
  2478  	}
  2479  	if fees != expectedFees {
  2480  		t.Fatalf("expected fees of %d, got %d", expectedFees, fees)
  2481  	}
  2482  	_ = wallet.ReturnCoins(coins)
  2483  
  2484  	// GetRawChangeAddress error
  2485  	node.changeAddrErr = tErr
  2486  	_, _, _, err = wallet.FundOrder(ord)
  2487  	if err == nil {
  2488  		t.Fatalf("no error for split tx change addr error")
  2489  	}
  2490  	node.changeAddrErr = nil
  2491  
  2492  	// SendRawTx error
  2493  	node.sendErr = tErr
  2494  	_, _, _, err = wallet.FundOrder(ord)
  2495  	if err == nil {
  2496  		t.Fatalf("no error for split tx send error")
  2497  	}
  2498  	node.sendErr = nil
  2499  
  2500  	// Success again.
  2501  	spendables, _, _, err = wallet.FundOrder(ord)
  2502  	if err != nil {
  2503  		t.Fatalf("error for split tx recovery run")
  2504  	}
  2505  	_ = wallet.ReturnCoins(spendables)
  2506  }
  2507  
  2508  // Since ReturnCoins takes the asset.Coin interface, make sure any interface
  2509  // is acceptable.
  2510  type tCoin struct{ id []byte }
  2511  
  2512  func (c *tCoin) ID() dex.Bytes {
  2513  	if len(c.id) > 0 {
  2514  		return c.id
  2515  	}
  2516  	return make([]byte, 36)
  2517  }
  2518  func (c *tCoin) String() string { return hex.EncodeToString(c.id) }
  2519  func (c *tCoin) TxID() string   { return hex.EncodeToString(c.id) }
  2520  func (c *tCoin) Value() uint64  { return 100 }
  2521  
  2522  func TestReturnCoins(t *testing.T) {
  2523  	wallet, node, shutdown := tNewWallet(true, walletTypeRPC)
  2524  	defer shutdown()
  2525  
  2526  	// Test it with the local output type.
  2527  	coins := asset.Coins{
  2528  		NewOutput(tTxHash, 0, 1),
  2529  	}
  2530  	err := wallet.ReturnCoins(coins)
  2531  	if err != nil {
  2532  		t.Fatalf("error with output type coins: %v", err)
  2533  	}
  2534  
  2535  	// Should error for no coins.
  2536  	err = wallet.ReturnCoins(asset.Coins{})
  2537  	if err == nil {
  2538  		t.Fatalf("no error for zero coins")
  2539  	}
  2540  
  2541  	// nil unlocks all
  2542  	wallet.cm.lockedOutputs[OutPoint{*tTxHash, 0}] = &UTxO{}
  2543  	err = wallet.ReturnCoins(nil)
  2544  	if err != nil {
  2545  		t.Fatalf("error for nil coins: %v", err)
  2546  	}
  2547  	if len(wallet.cm.lockedOutputs) != 0 {
  2548  		t.Errorf("all funding coins not unlocked")
  2549  	}
  2550  
  2551  	// Have the RPC return negative response.
  2552  	node.lockUnspentErr = tErr
  2553  	err = wallet.ReturnCoins(coins)
  2554  	if err == nil {
  2555  		t.Fatalf("no error for RPC failure")
  2556  	}
  2557  	node.lockUnspentErr = nil
  2558  
  2559  	// ReturnCoins should accept any type that implements asset.Coin.
  2560  	err = wallet.ReturnCoins(asset.Coins{&tCoin{}, &tCoin{}})
  2561  	if err != nil {
  2562  		t.Fatalf("error with custom coin type: %v", err)
  2563  	}
  2564  }
  2565  
  2566  func TestFundingCoins(t *testing.T) {
  2567  	// runRubric(t, testFundingCoins)
  2568  	testFundingCoins(t, false, walletTypeRPC)
  2569  }
  2570  
  2571  func testFundingCoins(t *testing.T, segwit bool, walletType string) {
  2572  	wallet, node, shutdown := tNewWallet(segwit, walletType)
  2573  	defer shutdown()
  2574  
  2575  	const vout0 = 1
  2576  	const txBlockHeight = 3
  2577  	tx0 := makeRawTx([]dex.Bytes{{0x01}, tP2PKH}, []*wire.TxIn{dummyInput()})
  2578  	txHash0 := tx0.TxHash()
  2579  	_, _ = node.addRawTx(txBlockHeight, tx0)
  2580  	coinID0 := ToCoinID(&txHash0, vout0)
  2581  	// Make spendable (confs > 0)
  2582  	node.addRawTx(txBlockHeight+1, dummyTx())
  2583  
  2584  	p2pkhUnspent0 := &ListUnspentResult{
  2585  		TxID:         txHash0.String(),
  2586  		Vout:         vout0,
  2587  		ScriptPubKey: tP2PKH,
  2588  		Spendable:    true,
  2589  		Solvable:     true,
  2590  		SafePtr:      boolPtr(true),
  2591  		Amount:       1,
  2592  	}
  2593  	unspents := []*ListUnspentResult{p2pkhUnspent0}
  2594  
  2595  	// Add a second funding coin to make sure more than one iteration of the
  2596  	// utxo loops is required.
  2597  	const vout1 = 0
  2598  	tx1 := makeRawTx([]dex.Bytes{tP2PKH, {0x02}}, []*wire.TxIn{dummyInput()})
  2599  	txHash1 := tx1.TxHash()
  2600  	_, _ = node.addRawTx(txBlockHeight, tx1)
  2601  	coinID1 := ToCoinID(&txHash1, vout1)
  2602  	// Make spendable (confs > 0)
  2603  	node.addRawTx(txBlockHeight+1, dummyTx())
  2604  
  2605  	p2pkhUnspent1 := &ListUnspentResult{
  2606  		TxID:         txHash1.String(),
  2607  		Vout:         vout1,
  2608  		ScriptPubKey: tP2PKH,
  2609  		Spendable:    true,
  2610  		Solvable:     true,
  2611  		SafePtr:      boolPtr(true),
  2612  		Amount:       1,
  2613  	}
  2614  	unspents = append(unspents, p2pkhUnspent1)
  2615  
  2616  	node.listLockUnspent = []*RPCOutpoint{}
  2617  	node.listUnspent = unspents
  2618  	coinIDs := []dex.Bytes{coinID0, coinID1}
  2619  
  2620  	ensureGood := func() {
  2621  		t.Helper()
  2622  		coins, err := wallet.FundingCoins(coinIDs)
  2623  		if err != nil {
  2624  			t.Fatalf("FundingCoins error: %v", err)
  2625  		}
  2626  		if len(coins) != 2 {
  2627  			t.Fatalf("expected 2 coins, got %d", len(coins))
  2628  		}
  2629  	}
  2630  	ensureGood()
  2631  
  2632  	ensureErr := func(tag string) {
  2633  		t.Helper()
  2634  		// Clear the cache.
  2635  		wallet.cm.lockedOutputs = make(map[OutPoint]*UTxO)
  2636  		_, err := wallet.FundingCoins(coinIDs)
  2637  		if err == nil {
  2638  			t.Fatalf("%s: no error", tag)
  2639  		}
  2640  	}
  2641  
  2642  	// No coins
  2643  	node.listUnspent = []*ListUnspentResult{}
  2644  	ensureErr("no coins")
  2645  	node.listUnspent = unspents
  2646  
  2647  	// RPC error
  2648  	node.listUnspentErr = tErr
  2649  	ensureErr("rpc coins")
  2650  	node.listUnspentErr = nil
  2651  
  2652  	// Bad coin ID.
  2653  	ogIDs := coinIDs
  2654  	coinIDs = []dex.Bytes{randBytes(35)}
  2655  	ensureErr("bad coin ID")
  2656  	coinIDs = ogIDs
  2657  
  2658  	// Coins locked but not in wallet.fundingCoins.
  2659  	irrelevantTx := dummyTx()
  2660  	node.addRawTx(txBlockHeight+1, irrelevantTx)
  2661  	node.listLockUnspent = []*RPCOutpoint{
  2662  		{TxID: p2pkhUnspent0.TxID, Vout: p2pkhUnspent0.Vout},
  2663  		{TxID: p2pkhUnspent1.TxID, Vout: p2pkhUnspent1.Vout},
  2664  	}
  2665  	node.listUnspent = []*ListUnspentResult{}
  2666  
  2667  	txRaw0, _ := serializeMsgTx(tx0)
  2668  	getTxRes0 := &GetTransactionResult{
  2669  		Bytes: txRaw0,
  2670  	}
  2671  	txRaw1, _ := serializeMsgTx(tx1)
  2672  	getTxRes1 := &GetTransactionResult{
  2673  		Bytes: txRaw1,
  2674  	}
  2675  
  2676  	node.getTransactionMap = map[string]*GetTransactionResult{
  2677  		txHash0.String(): getTxRes0,
  2678  		txHash1.String(): getTxRes1,
  2679  	}
  2680  
  2681  	ensureGood()
  2682  }
  2683  
  2684  func checkMaxOrder(t *testing.T, wallet asset.Wallet, lots, swapVal, maxFees, estWorstCase, estBestCase uint64) {
  2685  	t.Helper()
  2686  	maxOrder, err := wallet.MaxOrder(&asset.MaxOrderForm{
  2687  		LotSize:       tLotSize,
  2688  		FeeSuggestion: feeSuggestion,
  2689  		AssetVersion:  version,
  2690  		MaxFeeRate:    tBTC.MaxFeeRate,
  2691  	})
  2692  	if err != nil {
  2693  		t.Fatalf("MaxOrder error: %v", err)
  2694  	}
  2695  	checkSwapEstimate(t, maxOrder, lots, swapVal, maxFees, estWorstCase, estBestCase)
  2696  }
  2697  
  2698  func checkSwapEstimate(t *testing.T, est *asset.SwapEstimate, lots, swapVal, maxFees, estWorstCase, estBestCase uint64) {
  2699  	t.Helper()
  2700  	if est.Lots != lots {
  2701  		t.Fatalf("Estimate has wrong Lots. wanted %d, got %d", lots, est.Lots)
  2702  	}
  2703  	if est.Value != swapVal {
  2704  		t.Fatalf("Estimate has wrong Value. wanted %d, got %d", swapVal, est.Value)
  2705  	}
  2706  	if est.MaxFees != maxFees {
  2707  		t.Fatalf("Estimate has wrong MaxFees. wanted %d, got %d", maxFees, est.MaxFees)
  2708  	}
  2709  	if est.RealisticWorstCase != estWorstCase {
  2710  		t.Fatalf("Estimate has wrong RealisticWorstCase. wanted %d, got %d", estWorstCase, est.RealisticWorstCase)
  2711  	}
  2712  	if est.RealisticBestCase != estBestCase {
  2713  		t.Fatalf("Estimate has wrong RealisticBestCase. wanted %d, got %d", estBestCase, est.RealisticBestCase)
  2714  	}
  2715  }
  2716  
  2717  func TestFundEdges(t *testing.T) {
  2718  	wallet, node, shutdown := tNewWallet(false, walletTypeRPC)
  2719  	defer shutdown()
  2720  	swapVal := uint64(1e7)
  2721  	lots := swapVal / tLotSize
  2722  
  2723  	// Base Fees
  2724  	// fee_rate: 34 satoshi / vbyte (MaxFeeRate)
  2725  	// swap_size: 225 bytes (InitTxSize)
  2726  	// p2pkh input: 149 bytes (RedeemP2PKHInputSize)
  2727  
  2728  	// NOTE: Shouldn't swap_size_base be 73 bytes?
  2729  
  2730  	// swap_size_base: 76 bytes (225 - 149 p2pkh input) (InitTxSizeBase)
  2731  	// lot_size: 1e6
  2732  	// swap_value: 1e7
  2733  	// lots = swap_value / lot_size = 10
  2734  	//   total_bytes = first_swap_size + chained_swap_sizes
  2735  	//   chained_swap_sizes = (lots - 1) * swap_size
  2736  	//   first_swap_size = swap_size_base + backing_bytes
  2737  	//   total_bytes = swap_size_base + backing_bytes + (lots - 1) * swap_size
  2738  	//   base_tx_bytes = total_bytes - backing_bytes
  2739  	// base_tx_bytes = (lots - 1) * swap_size + swap_size_base = 9 * 225 + 76 = 2101
  2740  	// base_fees = base_tx_bytes * fee_rate = 2101 * 34 = 71434
  2741  	// backing_bytes: 1x P2PKH inputs = dexbtc.P2PKHInputSize = 149 bytes
  2742  	// backing_fees: 149 * fee_rate(34 atoms/byte) = 5066 atoms
  2743  	// total_bytes  = base_tx_bytes + backing_bytes = 2101 + 149 = 2250
  2744  	// total_fees: base_fees + backing_fees = 71434 + 5066 = 76500 atoms
  2745  	//          OR total_bytes * fee_rate = 2250 * 34 = 76500
  2746  	// base_best_case_bytes = swap_size_base + backing_bytes
  2747  	//                      = 76 + 149 = 225
  2748  	const swapSize = 225
  2749  	const totalBytes = 2250
  2750  	const bestCaseBytes = 225
  2751  	backingFees := uint64(totalBytes) * tBTC.MaxFeeRate // total_bytes * fee_rate
  2752  	p2pkhUnspent := &ListUnspentResult{
  2753  		TxID:          tTxID,
  2754  		Address:       tP2PKHAddr,
  2755  		Amount:        float64(swapVal+backingFees-1) / 1e8,
  2756  		Confirmations: 5,
  2757  		ScriptPubKey:  tP2PKH,
  2758  		Spendable:     true,
  2759  		Solvable:      true,
  2760  		SafePtr:       boolPtr(true),
  2761  	}
  2762  	unspents := []*ListUnspentResult{p2pkhUnspent}
  2763  	node.listUnspent = unspents
  2764  	ord := &asset.Order{
  2765  		Version:       version,
  2766  		Value:         swapVal,
  2767  		MaxSwapCount:  lots,
  2768  		MaxFeeRate:    tBTC.MaxFeeRate,
  2769  		FeeSuggestion: feeSuggestion,
  2770  	}
  2771  
  2772  	var feeReduction uint64 = swapSize * tBTC.MaxFeeRate
  2773  	estFeeReduction := swapSize * feeSuggestion
  2774  	splitFees := splitTxBaggage * tBTC.MaxFeeRate
  2775  	checkMaxOrder(t, wallet, lots-1, swapVal-tLotSize, backingFees+splitFees-feeReduction,
  2776  		(totalBytes+splitTxBaggage)*feeSuggestion-estFeeReduction,
  2777  		(bestCaseBytes+splitTxBaggage)*feeSuggestion)
  2778  
  2779  	_, _, _, err := wallet.FundOrder(ord)
  2780  	if err == nil {
  2781  		t.Fatalf("no error when not enough funds in single p2pkh utxo")
  2782  	}
  2783  	// Now add the needed satoshi and try again.
  2784  	p2pkhUnspent.Amount = float64(swapVal+backingFees) / 1e8
  2785  	node.listUnspent = unspents
  2786  
  2787  	checkMaxOrder(t, wallet, lots, swapVal, backingFees, totalBytes*feeSuggestion,
  2788  		bestCaseBytes*feeSuggestion)
  2789  
  2790  	spendables, _, _, err := wallet.FundOrder(ord)
  2791  	if err != nil {
  2792  		t.Fatalf("error when should be enough funding in single p2pkh utxo: %v", err)
  2793  	}
  2794  	_ = wallet.ReturnCoins(spendables)
  2795  
  2796  	// For a split transaction, we would need to cover the splitTxBaggage as
  2797  	// well.
  2798  	node.walletCfg.useSplitTx = true
  2799  	node.changeAddr = tP2WPKHAddr
  2800  	node.newAddress = tP2WPKHAddr
  2801  	node.signFunc = func(tx *wire.MsgTx) {
  2802  		signFunc(tx, 0, wallet.segwit)
  2803  	}
  2804  	backingFees = uint64(totalBytes+splitTxBaggage) * tBTC.MaxFeeRate
  2805  	// 1 too few atoms
  2806  	v := swapVal + backingFees - 1
  2807  	p2pkhUnspent.Amount = float64(v) / 1e8
  2808  	node.listUnspent = unspents
  2809  
  2810  	coins, _, _, err := wallet.FundOrder(ord)
  2811  	if err != nil {
  2812  		t.Fatalf("error when skipping split tx due to baggage: %v", err)
  2813  	}
  2814  	if coins[0].Value() != v {
  2815  		t.Fatalf("split performed when baggage wasn't covered")
  2816  	}
  2817  	_ = wallet.ReturnCoins(spendables)
  2818  
  2819  	// Just enough.
  2820  	v = swapVal + backingFees
  2821  	p2pkhUnspent.Amount = float64(v) / 1e8
  2822  	node.listUnspent = unspents
  2823  
  2824  	checkMaxOrder(t, wallet, lots, swapVal, backingFees,
  2825  		(totalBytes+splitTxBaggage)*feeSuggestion,
  2826  		(bestCaseBytes+splitTxBaggage)*feeSuggestion)
  2827  
  2828  	coins, _, _, err = wallet.FundOrder(ord)
  2829  	if err != nil {
  2830  		t.Fatalf("error funding split tx: %v", err)
  2831  	}
  2832  	if coins[0].Value() == v {
  2833  		t.Fatalf("split performed when baggage wasn't covered")
  2834  	}
  2835  	_ = wallet.ReturnCoins(spendables)
  2836  	node.walletCfg.useSplitTx = false
  2837  
  2838  	// P2SH(P2PKH) p2sh pkScript = 23 bytes, p2pkh pkScript (redeemscript) = 25 bytes
  2839  	// sigScript = signature(1 + 73) + pubkey(1 + 33) + redeemscript(1 + 25) = 134
  2840  	// P2SH input size = overhead(40) + sigScriptData(1 + 134) = 40 + 135 = 175 bytes
  2841  	// backing fees: 175 bytes * fee_rate(34) = 5950 satoshi
  2842  	// Use 1 P2SH AND 1 P2PKH from the previous test.
  2843  	// total: 71434 + 5950 + 5066 = 82450 satoshi
  2844  	p2shRedeem, _ := hex.DecodeString("76a914db1755408acd315baa75c18ebbe0e8eaddf64a9788ac") // 25, p2pkh redeem script
  2845  	scriptAddr := "37XDx4CwPVEg5mC3awSPGCKA5Fe5FdsAS2"
  2846  	p2shScriptPubKey, _ := hex.DecodeString("a9143ff6a24a50135f69be9ffed744443da08408fc1a87") // 23, p2sh pkScript
  2847  	backingFees = 82450
  2848  	halfSwap := swapVal / 2
  2849  	p2shUnspent := &ListUnspentResult{
  2850  		TxID:          tTxID,
  2851  		Address:       scriptAddr,
  2852  		Amount:        float64(halfSwap) / 1e8,
  2853  		Confirmations: 10,
  2854  		ScriptPubKey:  p2shScriptPubKey,
  2855  		RedeemScript:  p2shRedeem,
  2856  		Spendable:     true,
  2857  		Solvable:      true,
  2858  		SafePtr:       boolPtr(true),
  2859  	}
  2860  	p2pkhUnspent.Amount = float64(halfSwap+backingFees-1) / 1e8
  2861  	unspents = []*ListUnspentResult{p2pkhUnspent, p2shUnspent}
  2862  	node.listUnspent = unspents
  2863  	_, _, _, err = wallet.FundOrder(ord)
  2864  	if err == nil {
  2865  		t.Fatalf("no error when not enough funds in two utxos")
  2866  	}
  2867  	p2pkhUnspent.Amount = float64(halfSwap+backingFees) / 1e8
  2868  	node.listUnspent = unspents
  2869  	_, _, _, err = wallet.FundOrder(ord)
  2870  	if err != nil {
  2871  		t.Fatalf("error when should be enough funding in two utxos: %v", err)
  2872  	}
  2873  	_ = wallet.ReturnCoins(spendables)
  2874  
  2875  	// P2WPKH witness: RedeemP2WPKHInputWitnessWeight = 109
  2876  	// P2WPKH input size = overhead(40) + no sigScript(1+0) + witness(ceil(109/4)) = 69 vbytes
  2877  	// backing fees: 69 * fee_rate(34) = 2346 satoshi
  2878  	// total: base_fees(71434) + 2346 = 73780 satoshi
  2879  	backingFees = 73780
  2880  	p2wpkhAddr := tP2WPKHAddr
  2881  	p2wpkhPkScript, _ := hex.DecodeString("0014054a40a6aa7c1f6bd15f286debf4f33cef0e21a1")
  2882  	p2wpkhUnspent := &ListUnspentResult{
  2883  		TxID:          tTxID,
  2884  		Address:       p2wpkhAddr,
  2885  		Amount:        float64(swapVal+backingFees-1) / 1e8,
  2886  		Confirmations: 3,
  2887  		ScriptPubKey:  p2wpkhPkScript,
  2888  		Spendable:     true,
  2889  		Solvable:      true,
  2890  		SafePtr:       boolPtr(true),
  2891  	}
  2892  	unspents = []*ListUnspentResult{p2wpkhUnspent}
  2893  	node.listUnspent = unspents
  2894  	_, _, _, err = wallet.FundOrder(ord)
  2895  	if err == nil {
  2896  		t.Fatalf("no error when not enough funds in single p2wpkh utxo")
  2897  	}
  2898  	p2wpkhUnspent.Amount = float64(swapVal+backingFees) / 1e8
  2899  	node.listUnspent = unspents
  2900  	_, _, _, err = wallet.FundOrder(ord)
  2901  	if err != nil {
  2902  		t.Fatalf("error when should be enough funding in single p2wpkh utxo: %v", err)
  2903  	}
  2904  	_ = wallet.ReturnCoins(spendables)
  2905  
  2906  	// P2WSH(P2WPKH)
  2907  	//  p2wpkh redeem script length, btc.P2WPKHPkScriptSize: 22
  2908  	// witness: version(1) + signature(1 + 73) + pubkey(1 + 33) + redeemscript(1 + 22) = 132
  2909  	// input size: overhead(40) + no sigScript(1+0) + witness(132)/4 = 74 vbyte
  2910  	// backing fees: 74 * 34 = 2516 satoshi
  2911  	// total: base_fees(71434) + 2516 = 73950 satoshi
  2912  	backingFees = 73950
  2913  	p2wpkhRedeemScript, _ := hex.DecodeString("0014b71554f9a66ef4fa4dbeddb9fa491f5a1d938ebc") //22
  2914  	p2wshAddr := "bc1q9heng7q275grmy483cueqrr00dvyxpd8w6kes3nzptm7087d6lvsvffpqf"
  2915  	p2wshPkScript, _ := hex.DecodeString("00202df334780af5103d92a78e39900c6f7b584305a776ad9846620af7e79fcdd7d9") //34
  2916  	p2wpshUnspent := &ListUnspentResult{
  2917  		TxID:          tTxID,
  2918  		Address:       p2wshAddr,
  2919  		Amount:        float64(swapVal+backingFees-1) / 1e8,
  2920  		Confirmations: 7,
  2921  		ScriptPubKey:  p2wshPkScript,
  2922  		RedeemScript:  p2wpkhRedeemScript,
  2923  		Spendable:     true,
  2924  		Solvable:      true,
  2925  		SafePtr:       boolPtr(true),
  2926  	}
  2927  	unspents = []*ListUnspentResult{p2wpshUnspent}
  2928  	node.listUnspent = unspents
  2929  	_, _, _, err = wallet.FundOrder(ord)
  2930  	if err == nil {
  2931  		t.Fatalf("no error when not enough funds in single p2wsh utxo")
  2932  	}
  2933  	p2wpshUnspent.Amount = float64(swapVal+backingFees) / 1e8
  2934  	node.listUnspent = unspents
  2935  	_, _, _, err = wallet.FundOrder(ord)
  2936  	if err != nil {
  2937  		t.Fatalf("error when should be enough funding in single p2wsh utxo: %v", err)
  2938  	}
  2939  	_ = wallet.ReturnCoins(spendables)
  2940  }
  2941  
  2942  func TestFundEdgesSegwit(t *testing.T) {
  2943  	wallet, node, shutdown := tNewWallet(true, walletTypeRPC)
  2944  	defer shutdown()
  2945  	swapVal := uint64(1e7)
  2946  	lots := swapVal / tLotSize
  2947  
  2948  	// Base Fees
  2949  	// fee_rate: 34 satoshi / vbyte (MaxFeeRate)
  2950  
  2951  	// swap_size: 153 bytes (InitTxSizeSegwit)
  2952  	// p2wpkh input, incl. marker and flag: 69 bytes (RedeemP2WPKHInputSize + ((RedeemP2WPKHInputWitnessWeight + 2 + 3) / 4))
  2953  	// swap_size_base: 84 bytes (153 - 69 p2pkh input) (InitTxSizeBaseSegwit)
  2954  
  2955  	// lot_size: 1e6
  2956  	// swap_value: 1e7
  2957  	// lots = swap_value / lot_size = 10
  2958  	//   total_bytes = first_swap_size + chained_swap_sizes
  2959  	//   chained_swap_sizes = (lots - 1) * swap_size
  2960  	//   first_swap_size = swap_size_base + backing_bytes
  2961  	//   total_bytes  = swap_size_base + backing_bytes + (lots - 1) * swap_size
  2962  	//   base_tx_bytes = total_bytes - backing_bytes
  2963  	// base_tx_bytes = (lots - 1) * swap_size + swap_size_base = 9 * 153 + 84 = 1461
  2964  	// base_fees = base_tx_bytes * fee_rate = 1461 * 34 = 49674
  2965  	// backing_bytes: 1x P2WPKH-spending input = p2wpkh input = 69 bytes
  2966  	// backing_fees: 69 * fee_rate(34 atoms/byte) = 2346 atoms
  2967  	// total_bytes  = base_tx_bytes + backing_bytes = 1461 + 69 = 1530
  2968  	// total_fees: base_fees + backing_fees = 49674 + 2346 = 52020 atoms
  2969  	//          OR total_bytes * fee_rate = 1530 * 34 = 52020
  2970  	// base_best_case_bytes = swap_size_base + backing_bytes
  2971  	//                      = 84 + 69 = 153
  2972  	const swapSize = 153
  2973  	const totalBytes = 1530
  2974  	const bestCaseBytes = 153
  2975  	backingFees := uint64(totalBytes) * tBTC.MaxFeeRate // total_bytes * fee_rate
  2976  	p2wpkhUnspent := &ListUnspentResult{
  2977  		TxID:          tTxID,
  2978  		Address:       tP2WPKHAddr,
  2979  		Amount:        float64(swapVal+backingFees-1) / 1e8,
  2980  		Confirmations: 5,
  2981  		ScriptPubKey:  tP2WPKH,
  2982  		Spendable:     true,
  2983  		Solvable:      true,
  2984  		SafePtr:       boolPtr(true),
  2985  	}
  2986  	unspents := []*ListUnspentResult{p2wpkhUnspent}
  2987  	node.listUnspent = unspents
  2988  	ord := &asset.Order{
  2989  		Version:       version,
  2990  		Value:         swapVal,
  2991  		MaxSwapCount:  lots,
  2992  		MaxFeeRate:    tBTC.MaxFeeRate,
  2993  		FeeSuggestion: feeSuggestion,
  2994  	}
  2995  
  2996  	var feeReduction uint64 = swapSize * tBTC.MaxFeeRate
  2997  	estFeeReduction := swapSize * feeSuggestion
  2998  	splitFees := splitTxBaggageSegwit * tBTC.MaxFeeRate
  2999  	checkMaxOrder(t, wallet, lots-1, swapVal-tLotSize, backingFees+splitFees-feeReduction,
  3000  		(totalBytes+splitTxBaggageSegwit)*feeSuggestion-estFeeReduction,
  3001  		(bestCaseBytes+splitTxBaggageSegwit)*feeSuggestion)
  3002  
  3003  	_, _, _, err := wallet.FundOrder(ord)
  3004  	if err == nil {
  3005  		t.Fatalf("no error when not enough funds in single p2wpkh utxo")
  3006  	}
  3007  	// Now add the needed satoshi and try again.
  3008  	p2wpkhUnspent.Amount = float64(swapVal+backingFees) / 1e8
  3009  	node.listUnspent = unspents
  3010  
  3011  	checkMaxOrder(t, wallet, lots, swapVal, backingFees, totalBytes*feeSuggestion,
  3012  		bestCaseBytes*feeSuggestion)
  3013  
  3014  	spendables, _, _, err := wallet.FundOrder(ord)
  3015  	if err != nil {
  3016  		t.Fatalf("error when should be enough funding in single p2wpkh utxo: %v", err)
  3017  	}
  3018  	_ = wallet.ReturnCoins(spendables)
  3019  
  3020  	// For a split transaction, we would need to cover the splitTxBaggage as
  3021  	// well.
  3022  	node.walletCfg.useSplitTx = true
  3023  	node.changeAddr = tP2WPKHAddr
  3024  	node.newAddress = tP2WPKHAddr
  3025  	node.signFunc = func(tx *wire.MsgTx) {
  3026  		signFunc(tx, 0, wallet.segwit)
  3027  	}
  3028  	backingFees = uint64(totalBytes+splitTxBaggageSegwit) * tBTC.MaxFeeRate
  3029  	v := swapVal + backingFees - 1
  3030  	p2wpkhUnspent.Amount = float64(v) / 1e8
  3031  	node.listUnspent = unspents
  3032  	coins, _, _, err := wallet.FundOrder(ord)
  3033  	if err != nil {
  3034  		t.Fatalf("error when skipping split tx because not enough to cover baggage: %v", err)
  3035  	}
  3036  	if coins[0].Value() != v {
  3037  		t.Fatalf("split performed when baggage wasn't covered")
  3038  	}
  3039  	_ = wallet.ReturnCoins(spendables)
  3040  	// Now get the split.
  3041  	v = swapVal + backingFees
  3042  	p2wpkhUnspent.Amount = float64(v) / 1e8
  3043  	node.listUnspent = unspents
  3044  
  3045  	checkMaxOrder(t, wallet, lots, swapVal, backingFees,
  3046  		(totalBytes+splitTxBaggageSegwit)*feeSuggestion,
  3047  		(bestCaseBytes+splitTxBaggageSegwit)*feeSuggestion)
  3048  
  3049  	coins, _, _, err = wallet.FundOrder(ord)
  3050  	if err != nil {
  3051  		t.Fatalf("error funding split tx: %v", err)
  3052  	}
  3053  	_ = wallet.ReturnCoins(spendables)
  3054  	if coins[0].Value() == v {
  3055  		t.Fatalf("split performed when baggage wasn't covered")
  3056  	}
  3057  	node.walletCfg.useSplitTx = false
  3058  }
  3059  
  3060  func TestSwap(t *testing.T) {
  3061  	runRubric(t, testSwap)
  3062  }
  3063  
  3064  func testSwap(t *testing.T, segwit bool, walletType string) {
  3065  	wallet, node, shutdown := tNewWallet(segwit, walletType)
  3066  	defer shutdown()
  3067  
  3068  	swapVal := toSatoshi(5)
  3069  	coins := asset.Coins{
  3070  		NewOutput(tTxHash, 0, toSatoshi(3)),
  3071  		NewOutput(tTxHash, 0, toSatoshi(3)),
  3072  	}
  3073  	addrStr := tP2PKHAddr
  3074  	if segwit {
  3075  		addrStr = tP2WPKHAddr
  3076  	}
  3077  
  3078  	node.newAddress = addrStr
  3079  	node.changeAddr = addrStr
  3080  
  3081  	privBytes, _ := hex.DecodeString("b07209eec1a8fb6cfe5cb6ace36567406971a75c330db7101fb21bc679bc5330")
  3082  	privKey, _ := btcec.PrivKeyFromBytes(privBytes)
  3083  	wif, err := btcutil.NewWIF(privKey, &chaincfg.MainNetParams, true)
  3084  	if err != nil {
  3085  		t.Fatalf("error encoding wif: %v", err)
  3086  	}
  3087  	node.privKeyForAddr = wif
  3088  
  3089  	secretHash, _ := hex.DecodeString("5124208c80d33507befa517c08ed01aa8d33adbf37ecd70fb5f9352f7a51a88d")
  3090  	contract := &asset.Contract{
  3091  		Address:    addrStr,
  3092  		Value:      swapVal,
  3093  		SecretHash: secretHash,
  3094  		LockTime:   uint64(time.Now().Unix()),
  3095  	}
  3096  
  3097  	swaps := &asset.Swaps{
  3098  		Inputs:     coins,
  3099  		Contracts:  []*asset.Contract{contract},
  3100  		LockChange: true,
  3101  		FeeRate:    tBTC.MaxFeeRate,
  3102  	}
  3103  
  3104  	// Aim for 3 signature cycles.
  3105  	sigSizer := 0
  3106  	node.signFunc = func(tx *wire.MsgTx) {
  3107  		var sizeTweak int
  3108  		if sigSizer%2 == 0 {
  3109  			sizeTweak = -2
  3110  		}
  3111  		sigSizer++
  3112  		signFunc(tx, sizeTweak, wallet.segwit)
  3113  	}
  3114  
  3115  	// This time should succeed.
  3116  	_, changeCoin, feesPaid, err := wallet.Swap(swaps)
  3117  	if err != nil {
  3118  		t.Fatalf("swap error: %v", err)
  3119  	}
  3120  
  3121  	// Make sure the change coin is locked.
  3122  	if len(node.lockedCoins) != 1 {
  3123  		t.Fatalf("did not lock change coin")
  3124  	}
  3125  	txHash, _, _ := decodeCoinID(changeCoin.ID())
  3126  	if node.lockedCoins[0].TxID != txHash.String() {
  3127  		t.Fatalf("wrong coin locked during swap")
  3128  	}
  3129  
  3130  	// Fees should be returned.
  3131  	minFees := tBTC.MaxFeeRate * dexbtc.MsgTxVBytes(node.sentRawTx)
  3132  	if feesPaid < minFees {
  3133  		t.Fatalf("sent fees, %d, less than required fees, %d", feesPaid, minFees)
  3134  	}
  3135  
  3136  	// Not enough funds
  3137  	swaps.Inputs = coins[:1]
  3138  	_, _, _, err = wallet.Swap(swaps)
  3139  	if err == nil {
  3140  		t.Fatalf("no error for listunspent not enough funds")
  3141  	}
  3142  	swaps.Inputs = coins
  3143  
  3144  	// AddressPKH error
  3145  	node.newAddressErr = tErr
  3146  	_, _, _, err = wallet.Swap(swaps)
  3147  	if err == nil {
  3148  		t.Fatalf("no error for getnewaddress rpc error")
  3149  	}
  3150  	node.newAddressErr = nil
  3151  
  3152  	// ChangeAddress error
  3153  	node.changeAddrErr = tErr
  3154  	_, _, _, err = wallet.Swap(swaps)
  3155  	if err == nil {
  3156  		t.Fatalf("no error for getrawchangeaddress rpc error")
  3157  	}
  3158  	node.changeAddrErr = nil
  3159  
  3160  	// SignTx error
  3161  	node.signTxErr = tErr
  3162  	_, _, _, err = wallet.Swap(swaps)
  3163  	if err == nil {
  3164  		t.Fatalf("no error for signrawtransactionwithwallet rpc error")
  3165  	}
  3166  	node.signTxErr = nil
  3167  
  3168  	// incomplete signatures
  3169  	node.sigIncomplete = true
  3170  	_, _, _, err = wallet.Swap(swaps)
  3171  	if err == nil {
  3172  		t.Fatalf("no error for incomplete signature rpc error")
  3173  	}
  3174  	node.sigIncomplete = false
  3175  
  3176  	// Make sure we can succeed again.
  3177  	_, _, _, err = wallet.Swap(swaps)
  3178  	if err != nil {
  3179  		t.Fatalf("re-swap error: %v", err)
  3180  	}
  3181  }
  3182  
  3183  type TAuditInfo struct{}
  3184  
  3185  func (ai *TAuditInfo) Recipient() string     { return tP2PKHAddr }
  3186  func (ai *TAuditInfo) Expiration() time.Time { return time.Time{} }
  3187  func (ai *TAuditInfo) Coin() asset.Coin      { return &tCoin{} }
  3188  func (ai *TAuditInfo) Contract() dex.Bytes   { return nil }
  3189  func (ai *TAuditInfo) SecretHash() dex.Bytes { return nil }
  3190  
  3191  func TestRedeem(t *testing.T) {
  3192  	runRubric(t, testRedeem)
  3193  }
  3194  
  3195  func testRedeem(t *testing.T, segwit bool, walletType string) {
  3196  	wallet, node, shutdown := tNewWallet(segwit, walletType)
  3197  	defer shutdown()
  3198  	swapVal := toSatoshi(5)
  3199  
  3200  	secret, _, _, contract, addr, _, lockTime := makeSwapContract(segwit, time.Hour*12)
  3201  
  3202  	coin := NewOutput(tTxHash, 0, swapVal)
  3203  	ci := &asset.AuditInfo{
  3204  		Coin:       coin,
  3205  		Contract:   contract,
  3206  		Recipient:  addr.String(),
  3207  		Expiration: lockTime,
  3208  	}
  3209  
  3210  	redemption := &asset.Redemption{
  3211  		Spends: ci,
  3212  		Secret: secret,
  3213  	}
  3214  
  3215  	privBytes, _ := hex.DecodeString("b07209eec1a8fb6cfe5cb6ace36567406971a75c330db7101fb21bc679bc5330")
  3216  	privKey, _ := btcec.PrivKeyFromBytes(privBytes)
  3217  	wif, err := btcutil.NewWIF(privKey, &chaincfg.MainNetParams, true)
  3218  	if err != nil {
  3219  		t.Fatalf("error encoding wif: %v", err)
  3220  	}
  3221  
  3222  	addrStr := tP2PKHAddr
  3223  	if segwit {
  3224  		addrStr = tP2WPKHAddr
  3225  	}
  3226  
  3227  	node.changeAddr = addrStr
  3228  	node.newAddress = addrStr
  3229  	node.privKeyForAddr = wif
  3230  
  3231  	redemptions := &asset.RedeemForm{
  3232  		Redemptions: []*asset.Redemption{redemption},
  3233  	}
  3234  
  3235  	_, _, feesPaid, err := wallet.Redeem(redemptions)
  3236  	if err != nil {
  3237  		t.Fatalf("redeem error: %v", err)
  3238  	}
  3239  
  3240  	// Check that fees are returned.
  3241  	minFees := optimalFeeRate * dexbtc.MsgTxVBytes(node.sentRawTx)
  3242  	if feesPaid < minFees {
  3243  		t.Fatalf("sent fees, %d, less than expected minimum fees, %d", feesPaid, minFees)
  3244  	}
  3245  
  3246  	// No audit info
  3247  	redemption.Spends = nil
  3248  	_, _, _, err = wallet.Redeem(redemptions)
  3249  	if err == nil {
  3250  		t.Fatalf("no error for nil AuditInfo")
  3251  	}
  3252  	redemption.Spends = ci
  3253  
  3254  	// Spoofing AuditInfo is not allowed.
  3255  	redemption.Spends = &asset.AuditInfo{}
  3256  	_, _, _, err = wallet.Redeem(redemptions)
  3257  	if err == nil {
  3258  		t.Fatalf("no error for spoofed AuditInfo")
  3259  	}
  3260  	redemption.Spends = ci
  3261  
  3262  	// Wrong secret hash
  3263  	redemption.Secret = randBytes(32)
  3264  	_, _, _, err = wallet.Redeem(redemptions)
  3265  	if err == nil {
  3266  		t.Fatalf("no error for wrong secret")
  3267  	}
  3268  	redemption.Secret = secret
  3269  
  3270  	// too low of value
  3271  	coin.Val = 200
  3272  	_, _, _, err = wallet.Redeem(redemptions)
  3273  	if err == nil {
  3274  		t.Fatalf("no error for redemption not worth the fees")
  3275  	}
  3276  	coin.Val = swapVal
  3277  
  3278  	// New address error
  3279  	node.newAddressErr = tErr
  3280  	_, _, _, err = wallet.Redeem(redemptions)
  3281  	if err == nil {
  3282  		t.Fatalf("no error for new address error")
  3283  	}
  3284  	node.newAddressErr = nil
  3285  
  3286  	// Missing priv key error
  3287  	node.privKeyForAddrErr = tErr
  3288  	_, _, _, err = wallet.Redeem(redemptions)
  3289  	if err == nil {
  3290  		t.Fatalf("no error for missing private key")
  3291  	}
  3292  	node.privKeyForAddrErr = nil
  3293  
  3294  	// Send error
  3295  	node.sendErr = tErr
  3296  	_, _, _, err = wallet.Redeem(redemptions)
  3297  	if err == nil {
  3298  		t.Fatalf("no error for send error")
  3299  	}
  3300  	node.sendErr = nil
  3301  
  3302  	// Wrong hash
  3303  	var h chainhash.Hash
  3304  	h[0] = 0x01
  3305  	node.badSendHash = &h
  3306  	_, _, _, err = wallet.Redeem(redemptions)
  3307  	if err == nil {
  3308  		t.Fatalf("no error for wrong return hash")
  3309  	}
  3310  	node.badSendHash = nil
  3311  }
  3312  
  3313  func TestSignMessage(t *testing.T) {
  3314  	runRubric(t, testSignMessage)
  3315  }
  3316  
  3317  func testSignMessage(t *testing.T, segwit bool, walletType string) {
  3318  	wallet, node, shutdown := tNewWallet(segwit, walletType)
  3319  	defer shutdown()
  3320  
  3321  	vout := uint32(5)
  3322  	privBytes, _ := hex.DecodeString("b07209eec1a8fb6cfe5cb6ace36567406971a75c330db7101fb21bc679bc5330")
  3323  	privKey, pubKey := btcec.PrivKeyFromBytes(privBytes)
  3324  	wif, err := btcutil.NewWIF(privKey, &chaincfg.MainNetParams, true)
  3325  	if err != nil {
  3326  		t.Fatalf("error encoding wif: %v", err)
  3327  	}
  3328  
  3329  	msg := randBytes(36)
  3330  	msgHash := chainhash.HashB(msg)
  3331  	pk := pubKey.SerializeCompressed()
  3332  	signature := ecdsa.Sign(privKey, msgHash)
  3333  	sig := signature.Serialize()
  3334  
  3335  	pt := NewOutPoint(tTxHash, vout)
  3336  	utxo := &UTxO{Address: tP2PKHAddr}
  3337  	wallet.cm.lockedOutputs[pt] = utxo
  3338  	node.privKeyForAddr = wif
  3339  	node.signMsgFunc = func(params []json.RawMessage) (json.RawMessage, error) {
  3340  		if len(params) != 2 {
  3341  			t.Fatalf("expected 2 params, found %d", len(params))
  3342  		}
  3343  		var sentKey string
  3344  		var sentMsg dex.Bytes
  3345  		err := json.Unmarshal(params[0], &sentKey)
  3346  		if err != nil {
  3347  			t.Fatalf("unmarshal error: %v", err)
  3348  		}
  3349  		_ = sentMsg.UnmarshalJSON(params[1])
  3350  		if sentKey != wif.String() {
  3351  			t.Fatalf("received wrong key. expected '%s', got '%s'", wif.String(), sentKey)
  3352  		}
  3353  		var checkMsg dex.Bytes = msg
  3354  		if sentMsg.String() != checkMsg.String() {
  3355  			t.Fatalf("received wrong message. expected '%s', got '%s'", checkMsg.String(), sentMsg.String())
  3356  		}
  3357  		msgHash := chainhash.HashB(sentMsg)
  3358  		sig := ecdsa.Sign(wif.PrivKey, msgHash)
  3359  		r, _ := json.Marshal(base64.StdEncoding.EncodeToString(sig.Serialize()))
  3360  		return r, nil
  3361  	}
  3362  
  3363  	var coin asset.Coin = NewOutput(tTxHash, vout, 5e7)
  3364  	pubkeys, sigs, err := wallet.SignMessage(coin, msg)
  3365  	if err != nil {
  3366  		t.Fatalf("SignMessage error: %v", err)
  3367  	}
  3368  	if len(pubkeys) != 1 {
  3369  		t.Fatalf("expected 1 pubkey, received %d", len(pubkeys))
  3370  	}
  3371  	if len(sigs) != 1 {
  3372  		t.Fatalf("expected 1 sig, received %d", len(sigs))
  3373  	}
  3374  	if !bytes.Equal(pk, pubkeys[0]) {
  3375  		t.Fatalf("wrong pubkey. expected %x, got %x", pubkeys[0], pk)
  3376  	}
  3377  	if !bytes.Equal(sig, sigs[0]) {
  3378  		t.Fatalf("wrong signature. exptected %x, got %x", sigs[0], sig)
  3379  	}
  3380  
  3381  	// Unknown UTXO
  3382  	delete(wallet.cm.lockedOutputs, pt)
  3383  	_, _, err = wallet.SignMessage(coin, msg)
  3384  	if err == nil {
  3385  		t.Fatalf("no error for unknown utxo")
  3386  	}
  3387  	wallet.cm.lockedOutputs[pt] = utxo
  3388  
  3389  	// dumpprivkey error
  3390  	node.privKeyForAddrErr = tErr
  3391  	_, _, err = wallet.SignMessage(coin, msg)
  3392  	if err == nil {
  3393  		t.Fatalf("no error for dumpprivkey rpc error")
  3394  	}
  3395  	node.privKeyForAddrErr = nil
  3396  
  3397  	// bad coin
  3398  	badCoin := &tCoin{id: make([]byte, 15)}
  3399  	_, _, err = wallet.SignMessage(badCoin, msg)
  3400  	if err == nil {
  3401  		t.Fatalf("no error for bad coin")
  3402  	}
  3403  }
  3404  
  3405  func TestAuditContract(t *testing.T) {
  3406  	runRubric(t, testAuditContract)
  3407  }
  3408  
  3409  func testAuditContract(t *testing.T, segwit bool, walletType string) {
  3410  	wallet, _, shutdown := tNewWallet(segwit, walletType)
  3411  	defer shutdown()
  3412  	secretHash, _ := hex.DecodeString("5124208c80d33507befa517c08ed01aa8d33adbf37ecd70fb5f9352f7a51a88d")
  3413  	lockTime := time.Now().Add(time.Hour * 12)
  3414  	addr, _ := btcutil.DecodeAddress(tP2PKHAddr, &chaincfg.MainNetParams)
  3415  	if segwit {
  3416  		addr, _ = btcutil.DecodeAddress(tP2WPKHAddr, &chaincfg.MainNetParams)
  3417  	}
  3418  
  3419  	contract, err := dexbtc.MakeContract(addr, addr, secretHash, lockTime.Unix(), segwit, &chaincfg.MainNetParams)
  3420  	if err != nil {
  3421  		t.Fatalf("error making swap contract: %v", err)
  3422  	}
  3423  
  3424  	var contractAddr btcutil.Address
  3425  	if segwit {
  3426  		h := sha256.Sum256(contract)
  3427  		contractAddr, _ = btcutil.NewAddressWitnessScriptHash(h[:], &chaincfg.MainNetParams)
  3428  	} else {
  3429  		contractAddr, _ = btcutil.NewAddressScriptHash(contract, &chaincfg.MainNetParams)
  3430  	}
  3431  	pkScript, _ := txscript.PayToAddrScript(contractAddr)
  3432  	tx := makeRawTx([]dex.Bytes{pkScript}, []*wire.TxIn{dummyInput()})
  3433  	txData, err := serializeMsgTx(tx)
  3434  	if err != nil {
  3435  		t.Fatalf("error making contract tx data: %v", err)
  3436  	}
  3437  
  3438  	txHash := tx.TxHash()
  3439  	const vout = 0
  3440  
  3441  	audit, err := wallet.AuditContract(ToCoinID(&txHash, vout), contract, txData, false)
  3442  	if err != nil {
  3443  		t.Fatalf("audit error: %v", err)
  3444  	}
  3445  	if audit.Recipient != addr.String() {
  3446  		t.Fatalf("wrong recipient. wanted '%s', got '%s'", addr, audit.Recipient)
  3447  	}
  3448  	if !bytes.Equal(audit.Contract, contract) {
  3449  		t.Fatalf("contract not set to coin redeem script")
  3450  	}
  3451  	if audit.Expiration.Equal(lockTime) {
  3452  		t.Fatalf("wrong lock time. wanted %d, got %d", lockTime.Unix(), audit.Expiration.Unix())
  3453  	}
  3454  
  3455  	// Invalid txid
  3456  	_, err = wallet.AuditContract(make([]byte, 15), contract, txData, false)
  3457  	if err == nil {
  3458  		t.Fatalf("no error for bad txid")
  3459  	}
  3460  
  3461  	// Wrong contract
  3462  	pkh, _ := hex.DecodeString("c6a704f11af6cbee8738ff19fc28cdc70aba0b82")
  3463  	wrongAddr, _ := btcutil.NewAddressPubKeyHash(pkh, &chaincfg.MainNetParams)
  3464  	badContract, _ := txscript.PayToAddrScript(wrongAddr)
  3465  	_, err = wallet.AuditContract(ToCoinID(&txHash, vout), badContract, nil, false)
  3466  	if err == nil {
  3467  		t.Fatalf("no error for wrong contract")
  3468  	}
  3469  }
  3470  
  3471  type tReceipt struct {
  3472  	coin       *tCoin
  3473  	contract   []byte
  3474  	expiration uint64
  3475  }
  3476  
  3477  func (r *tReceipt) Expiration() time.Time { return time.Unix(int64(r.expiration), 0).UTC() }
  3478  func (r *tReceipt) Coin() asset.Coin      { return r.coin }
  3479  func (r *tReceipt) Contract() dex.Bytes   { return r.contract }
  3480  
  3481  func TestFindRedemption(t *testing.T) {
  3482  	runRubric(t, testFindRedemption)
  3483  }
  3484  
  3485  func testFindRedemption(t *testing.T, segwit bool, walletType string) {
  3486  	wallet, node, shutdown := tNewWallet(segwit, walletType)
  3487  	defer shutdown()
  3488  
  3489  	contractHeight := node.GetBestBlockHeight() + 1
  3490  	otherTxid := "7a7b3b5c3638516bc8e7f19b4a3dec00f052a599fed5036c2b89829de2367bb6"
  3491  	otherTxHash, _ := chainhash.NewHashFromStr(otherTxid)
  3492  	contractVout := uint32(1)
  3493  
  3494  	secret, _, pkScript, contract, addr, _, _ := makeSwapContract(segwit, time.Hour*12)
  3495  	otherScript, _ := txscript.PayToAddrScript(addr)
  3496  
  3497  	var redemptionWitness, otherWitness [][]byte
  3498  	var redemptionSigScript, otherSigScript []byte
  3499  	if segwit {
  3500  		redemptionWitness = dexbtc.RedeemP2WSHContract(contract, randBytes(73), randBytes(33), secret)
  3501  		otherWitness = [][]byte{randBytes(73), randBytes(33)}
  3502  	} else {
  3503  		redemptionSigScript, _ = dexbtc.RedeemP2SHContract(contract, randBytes(73), randBytes(33), secret)
  3504  		otherSigScript, _ = txscript.NewScriptBuilder().
  3505  			AddData(randBytes(73)).
  3506  			AddData(randBytes(33)).
  3507  			Script()
  3508  	}
  3509  
  3510  	// Prepare the "blockchain"
  3511  	inputs := []*wire.TxIn{makeRPCVin(otherTxHash, 0, otherSigScript, otherWitness)}
  3512  	// Add the contract transaction. Put the pay-to-contract script at index 1.
  3513  	contractTx := makeRawTx([]dex.Bytes{otherScript, pkScript}, inputs)
  3514  	contractTxHash := contractTx.TxHash()
  3515  	coinID := ToCoinID(&contractTxHash, contractVout)
  3516  	blockHash, _ := node.addRawTx(contractHeight, contractTx)
  3517  	txHex, err := makeTxHex([]dex.Bytes{otherScript, pkScript}, inputs)
  3518  	if err != nil {
  3519  		t.Fatalf("error generating hex for contract tx: %v", err)
  3520  	}
  3521  	getTxRes := &GetTransactionResult{
  3522  		BlockHash:  blockHash.String(),
  3523  		BlockIndex: contractHeight,
  3524  		Bytes:      txHex,
  3525  	}
  3526  	node.getTransactionMap = map[string]*GetTransactionResult{
  3527  		"any": getTxRes}
  3528  
  3529  	// Add an intermediate block for good measure.
  3530  	node.addRawTx(contractHeight+1, makeRawTx([]dex.Bytes{otherScript}, inputs))
  3531  
  3532  	// Now add the redemption.
  3533  	redeemVin := makeRPCVin(&contractTxHash, contractVout, redemptionSigScript, redemptionWitness)
  3534  	inputs = append(inputs, redeemVin)
  3535  	redeemBlockHash, _ := node.addRawTx(contractHeight+2, makeRawTx([]dex.Bytes{otherScript}, inputs))
  3536  	node.getCFilterScripts[*redeemBlockHash] = [][]byte{pkScript}
  3537  
  3538  	// Update currentTip from "RPC". Normally run() would do this.
  3539  	wallet.reportNewTip(tCtx, &BlockVector{
  3540  		Hash:   *redeemBlockHash,
  3541  		Height: contractHeight + 2,
  3542  	})
  3543  
  3544  	// Check find redemption result.
  3545  	_, checkSecret, err := wallet.FindRedemption(tCtx, coinID, nil)
  3546  	if err != nil {
  3547  		t.Fatalf("error finding redemption: %v", err)
  3548  	}
  3549  	if !bytes.Equal(checkSecret, secret) {
  3550  		t.Fatalf("wrong secret. expected %x, got %x", secret, checkSecret)
  3551  	}
  3552  
  3553  	// gettransaction error
  3554  	node.getTransactionErr = tErr
  3555  	_, _, err = wallet.FindRedemption(tCtx, coinID, nil)
  3556  	if err == nil {
  3557  		t.Fatalf("no error for gettransaction rpc error")
  3558  	}
  3559  	node.getTransactionErr = nil
  3560  
  3561  	// timeout finding missing redemption
  3562  	redeemVin.PreviousOutPoint.Hash = *otherTxHash
  3563  	delete(node.getCFilterScripts, *redeemBlockHash)
  3564  	timedCtx, cancel := context.WithTimeout(tCtx, 500*time.Millisecond) // 0.5 seconds is long enough
  3565  	defer cancel()
  3566  	_, k, err := wallet.FindRedemption(timedCtx, coinID, nil)
  3567  	if timedCtx.Err() == nil || k != nil {
  3568  		// Expected ctx to cancel after timeout and no secret should be found.
  3569  		t.Fatalf("unexpected result for missing redemption: secret: %v, err: %v", k, err)
  3570  	}
  3571  
  3572  	node.blockchainMtx.Lock()
  3573  	redeemVin.PreviousOutPoint.Hash = contractTxHash
  3574  	node.getCFilterScripts[*redeemBlockHash] = [][]byte{pkScript}
  3575  	node.blockchainMtx.Unlock()
  3576  
  3577  	// Canceled context
  3578  	deadCtx, cancelCtx := context.WithCancel(tCtx)
  3579  	cancelCtx()
  3580  	_, _, err = wallet.FindRedemption(deadCtx, coinID, nil)
  3581  	if err == nil {
  3582  		t.Fatalf("no error for canceled context")
  3583  	}
  3584  
  3585  	// Expect FindRedemption to error because of bad input sig.
  3586  	node.blockchainMtx.Lock()
  3587  	redeemVin.Witness = [][]byte{randBytes(100)}
  3588  	redeemVin.SignatureScript = randBytes(100)
  3589  
  3590  	node.blockchainMtx.Unlock()
  3591  	_, _, err = wallet.FindRedemption(tCtx, coinID, nil)
  3592  	if err == nil {
  3593  		t.Fatalf("no error for wrong redemption")
  3594  	}
  3595  	node.blockchainMtx.Lock()
  3596  	redeemVin.Witness = redemptionWitness
  3597  	redeemVin.SignatureScript = redemptionSigScript
  3598  	node.blockchainMtx.Unlock()
  3599  
  3600  	// Sanity check to make sure it passes again.
  3601  	_, _, err = wallet.FindRedemption(tCtx, coinID, nil)
  3602  	if err != nil {
  3603  		t.Fatalf("error after clearing errors: %v", err)
  3604  	}
  3605  }
  3606  
  3607  func TestRefund(t *testing.T) {
  3608  	runRubric(t, testRefund)
  3609  }
  3610  
  3611  func testRefund(t *testing.T, segwit bool, walletType string) {
  3612  	wallet, node, shutdown := tNewWallet(segwit, walletType)
  3613  	defer shutdown()
  3614  
  3615  	_, _, pkScript, contract, addr, _, _ := makeSwapContract(segwit, time.Hour*12)
  3616  
  3617  	bigTxOut := newTxOutResult(nil, 1e8, 2)
  3618  	node.txOutRes = bigTxOut // rpc
  3619  	node.changeAddr = addr.String()
  3620  	node.newAddress = addr.String()
  3621  	const feeSuggestion = 100
  3622  
  3623  	privBytes, _ := hex.DecodeString("b07209eec1a8fb6cfe5cb6ace36567406971a75c330db7101fb21bc679bc5330")
  3624  	privKey, _ := btcec.PrivKeyFromBytes(privBytes)
  3625  	wif, err := btcutil.NewWIF(privKey, &chaincfg.MainNetParams, true)
  3626  	if err != nil {
  3627  		t.Fatalf("error encoding wif: %v", err)
  3628  	}
  3629  	node.privKeyForAddr = wif
  3630  
  3631  	tx := makeRawTx([]dex.Bytes{pkScript}, []*wire.TxIn{dummyInput()})
  3632  	const vout = 0
  3633  	tx.TxOut[vout].Value = 1e8
  3634  	txHash := tx.TxHash()
  3635  	outPt := NewOutPoint(&txHash, vout)
  3636  	blockHash, _ := node.addRawTx(1, tx)
  3637  	node.getCFilterScripts[*blockHash] = [][]byte{pkScript}
  3638  	node.getTransactionErr = WalletTransactionNotFound
  3639  
  3640  	contractOutput := NewOutput(&txHash, 0, 1e8)
  3641  	_, err = wallet.Refund(contractOutput.ID(), contract, feeSuggestion)
  3642  	if err != nil {
  3643  		t.Fatalf("refund error: %v", err)
  3644  	}
  3645  
  3646  	// Invalid coin
  3647  	badReceipt := &tReceipt{
  3648  		coin: &tCoin{id: make([]byte, 15)},
  3649  	}
  3650  	_, err = wallet.Refund(badReceipt.coin.id, badReceipt.Contract(), feeSuggestion)
  3651  	if err == nil {
  3652  		t.Fatalf("no error for bad receipt")
  3653  	}
  3654  
  3655  	ensureErr := func(tag string) {
  3656  		delete(node.checkpoints, outPt)
  3657  		_, err = wallet.Refund(contractOutput.ID(), contract, feeSuggestion)
  3658  		if err == nil {
  3659  			t.Fatalf("no error for %q", tag)
  3660  		}
  3661  	}
  3662  
  3663  	// gettxout error
  3664  	node.txOutErr = tErr
  3665  	node.getCFilterScripts[*blockHash] = nil
  3666  	ensureErr("no utxo")
  3667  	node.getCFilterScripts[*blockHash] = [][]byte{pkScript}
  3668  	node.txOutErr = nil
  3669  
  3670  	// bad contract
  3671  	badContractOutput := NewOutput(tTxHash, 0, 1e8)
  3672  	badContract := randBytes(50)
  3673  	_, err = wallet.Refund(badContractOutput.ID(), badContract, feeSuggestion)
  3674  	if err == nil {
  3675  		t.Fatalf("no error for bad contract")
  3676  	}
  3677  
  3678  	// Too small.
  3679  	node.txOutRes = newTxOutResult(nil, 100, 2)
  3680  	tx.TxOut[0].Value = 2
  3681  	ensureErr("value < fees")
  3682  	node.txOutRes = bigTxOut
  3683  	tx.TxOut[0].Value = 1e8
  3684  
  3685  	// getrawchangeaddress error
  3686  	node.newAddressErr = tErr
  3687  	ensureErr("new address error")
  3688  	node.newAddressErr = nil
  3689  
  3690  	// signature error
  3691  	node.privKeyForAddrErr = tErr
  3692  	ensureErr("dumpprivkey error")
  3693  	node.privKeyForAddrErr = nil
  3694  
  3695  	// send error
  3696  	node.sendErr = tErr
  3697  	ensureErr("send error")
  3698  	node.sendErr = nil
  3699  
  3700  	// bad checkhash
  3701  	var badHash chainhash.Hash
  3702  	badHash[0] = 0x05
  3703  	node.badSendHash = &badHash
  3704  	ensureErr("checkhash error")
  3705  	node.badSendHash = nil
  3706  
  3707  	// Sanity check that we can succeed again.
  3708  	_, err = wallet.Refund(contractOutput.ID(), contract, feeSuggestion)
  3709  	if err != nil {
  3710  		t.Fatalf("re-refund error: %v", err)
  3711  	}
  3712  
  3713  	// TODO test spv spent
  3714  }
  3715  
  3716  func TestLockUnlock(t *testing.T) {
  3717  	runRubric(t, testLockUnlock)
  3718  }
  3719  
  3720  func testLockUnlock(t *testing.T, segwit bool, walletType string) {
  3721  	w, node, shutdown := tNewWallet(segwit, walletType)
  3722  	defer shutdown()
  3723  
  3724  	pw := []byte("pass")
  3725  	wallet := &ExchangeWalletFullNode{w, &authAddOn{w.node}}
  3726  
  3727  	// just checking that the errors come through.
  3728  	err := wallet.Unlock(pw)
  3729  	if err != nil {
  3730  		t.Fatalf("unlock error: %v", err)
  3731  	}
  3732  	node.unlockErr = tErr
  3733  	err = wallet.Unlock(pw)
  3734  	if err == nil {
  3735  		t.Fatalf("no error for walletpassphrase error")
  3736  	}
  3737  
  3738  	// Locking can't error on SPV.
  3739  	if walletType == walletTypeRPC {
  3740  		err = wallet.Lock()
  3741  		if err != nil {
  3742  			t.Fatalf("lock error: %v", err)
  3743  		}
  3744  		node.lockErr = tErr
  3745  		err = wallet.Lock()
  3746  		if err == nil {
  3747  			t.Fatalf("no error for walletlock rpc error")
  3748  		}
  3749  	}
  3750  
  3751  }
  3752  
  3753  type tSenderType byte
  3754  
  3755  const (
  3756  	tSendSender tSenderType = iota
  3757  	tWithdrawSender
  3758  )
  3759  
  3760  func testSender(t *testing.T, senderType tSenderType, segwit bool, walletType string) {
  3761  	wallet, node, shutdown := tNewWallet(segwit, walletType)
  3762  	defer shutdown()
  3763  	const feeSuggestion = 100
  3764  	sender := func(addr string, val uint64) (asset.Coin, error) {
  3765  		return wallet.Send(addr, val, defaultFee)
  3766  	}
  3767  	if senderType == tWithdrawSender {
  3768  		sender = func(addr string, val uint64) (asset.Coin, error) {
  3769  			return wallet.Withdraw(addr, val, feeSuggestion)
  3770  		}
  3771  	}
  3772  
  3773  	node.signFunc = func(tx *wire.MsgTx) {
  3774  		signFunc(tx, 0, wallet.segwit)
  3775  	}
  3776  
  3777  	addr := btcAddr(segwit)
  3778  	node.setTxFee = true
  3779  	node.changeAddr = btcAddr(segwit).String()
  3780  
  3781  	pkScript, _ := txscript.PayToAddrScript(addr)
  3782  	tx := makeRawTx([]dex.Bytes{randBytes(5), pkScript}, []*wire.TxIn{dummyInput()})
  3783  	txHash := tx.TxHash()
  3784  
  3785  	changeAddr := btcAddr(segwit)
  3786  	node.changeAddr = changeAddr.String()
  3787  
  3788  	expectedFees := func(numInputs int) uint64 {
  3789  		txSize := dexbtc.MinimumTxOverhead
  3790  		if segwit {
  3791  			txSize += (dexbtc.P2WPKHOutputSize * 2) + (numInputs * dexbtc.RedeemP2WPKHInputTotalSize)
  3792  		} else {
  3793  			txSize += (dexbtc.P2PKHOutputSize * 2) + (numInputs * dexbtc.RedeemP2PKHInputSize)
  3794  		}
  3795  		return uint64(txSize * feeSuggestion)
  3796  	}
  3797  
  3798  	expectedSentVal := func(sendVal, fees uint64) uint64 {
  3799  		if senderType == tSendSender {
  3800  			return sendVal
  3801  		}
  3802  		return sendVal - fees
  3803  	}
  3804  
  3805  	expectedChangeVal := func(totalInput, sendVal, fees uint64) uint64 {
  3806  		if senderType == tSendSender {
  3807  			return totalInput - sendVal - fees
  3808  		}
  3809  		return totalInput - sendVal
  3810  	}
  3811  
  3812  	requiredVal := func(sendVal, fees uint64) uint64 {
  3813  		if senderType == tSendSender {
  3814  			return sendVal + fees
  3815  		}
  3816  		return sendVal
  3817  	}
  3818  
  3819  	type test struct {
  3820  		name         string
  3821  		val          uint64
  3822  		unspents     []*ListUnspentResult
  3823  		bondReserves uint64
  3824  
  3825  		expectedInputs []*OutPoint
  3826  		expectSentVal  uint64
  3827  		expectChange   uint64
  3828  		expectErr      bool
  3829  	}
  3830  	tests := []test{
  3831  		{
  3832  			name: "plenty of funds",
  3833  			val:  toSatoshi(5),
  3834  			unspents: []*ListUnspentResult{{
  3835  				TxID:          txHash.String(),
  3836  				Address:       addr.String(),
  3837  				Amount:        100,
  3838  				Confirmations: 1,
  3839  				Vout:          0,
  3840  				ScriptPubKey:  pkScript,
  3841  				SafePtr:       boolPtr(true),
  3842  				Spendable:     true,
  3843  			}},
  3844  			expectedInputs: []*OutPoint{
  3845  				{TxHash: txHash, Vout: 0},
  3846  			},
  3847  			expectSentVal: expectedSentVal(toSatoshi(5), expectedFees(1)),
  3848  			expectChange:  expectedChangeVal(toSatoshi(100), toSatoshi(5), expectedFees(1)),
  3849  		},
  3850  		{
  3851  			name: "just enough change for bond reserves",
  3852  			val:  toSatoshi(5),
  3853  			unspents: []*ListUnspentResult{{
  3854  				TxID:          txHash.String(),
  3855  				Address:       addr.String(),
  3856  				Amount:        5.2,
  3857  				Confirmations: 1,
  3858  				Vout:          0,
  3859  				ScriptPubKey:  pkScript,
  3860  				SafePtr:       boolPtr(true),
  3861  				Spendable:     true,
  3862  			}},
  3863  			expectedInputs: []*OutPoint{
  3864  				{TxHash: txHash,
  3865  					Vout: 0},
  3866  			},
  3867  			expectSentVal: expectedSentVal(toSatoshi(5), expectedFees(1)),
  3868  			expectChange:  expectedChangeVal(toSatoshi(5.2), toSatoshi(5), expectedFees(1)),
  3869  			bondReserves:  expectedChangeVal(toSatoshi(5.2), toSatoshi(5), expectedFees(1)),
  3870  		},
  3871  		{
  3872  			name: "not enough change for bond reserves",
  3873  			val:  toSatoshi(5),
  3874  			unspents: []*ListUnspentResult{{
  3875  				TxID:          txHash.String(),
  3876  				Address:       addr.String(),
  3877  				Amount:        5.2,
  3878  				Confirmations: 1,
  3879  				Vout:          0,
  3880  				ScriptPubKey:  pkScript,
  3881  				SafePtr:       boolPtr(true),
  3882  				Spendable:     true,
  3883  			}},
  3884  			expectedInputs: []*OutPoint{
  3885  				{TxHash: txHash, Vout: 0},
  3886  			},
  3887  			bondReserves: expectedChangeVal(toSatoshi(5.2), toSatoshi(5), expectedFees(1)) + 1,
  3888  			expectErr:    true,
  3889  		},
  3890  		{
  3891  			name: "1 satoshi less than needed",
  3892  			val:  toSatoshi(5),
  3893  			unspents: []*ListUnspentResult{{
  3894  				TxID:          txHash.String(),
  3895  				Address:       addr.String(),
  3896  				Amount:        toBTC(requiredVal(toSatoshi(5), expectedFees(1)) - 1),
  3897  				Confirmations: 1,
  3898  				Vout:          0,
  3899  				ScriptPubKey:  pkScript,
  3900  				SafePtr:       boolPtr(true),
  3901  				Spendable:     true,
  3902  			}},
  3903  			expectErr: true,
  3904  		},
  3905  		{
  3906  			name: "exact amount needed",
  3907  			val:  toSatoshi(5),
  3908  			unspents: []*ListUnspentResult{{
  3909  				TxID:          txHash.String(),
  3910  				Address:       addr.String(),
  3911  				Amount:        toBTC(requiredVal(toSatoshi(5), expectedFees(1))),
  3912  				Confirmations: 1,
  3913  				Vout:          0,
  3914  				ScriptPubKey:  pkScript,
  3915  				SafePtr:       boolPtr(true),
  3916  				Spendable:     true,
  3917  			}},
  3918  			expectedInputs: []*OutPoint{
  3919  				{TxHash: txHash, Vout: 0},
  3920  			},
  3921  			expectSentVal: expectedSentVal(toSatoshi(5), expectedFees(1)),
  3922  			expectChange:  0,
  3923  		},
  3924  	}
  3925  
  3926  	for _, test := range tests {
  3927  		node.listUnspent = test.unspents
  3928  		wallet.bondReserves.Store(test.bondReserves)
  3929  
  3930  		_, err := sender(addr.String(), test.val)
  3931  		if test.expectErr {
  3932  			if err == nil {
  3933  				t.Fatalf("%s: no error for expected error", test.name)
  3934  			}
  3935  			continue
  3936  		}
  3937  		if err != nil {
  3938  			t.Fatalf("%s: unexpected error: %v", test.name, err)
  3939  		}
  3940  
  3941  		tx := node.sentRawTx
  3942  		if len(test.expectedInputs) != len(tx.TxIn) {
  3943  			t.Fatalf("expected %d inputs, got %d", len(test.expectedInputs), len(tx.TxIn))
  3944  		}
  3945  
  3946  		for i, input := range tx.TxIn {
  3947  			if input.PreviousOutPoint.Hash != test.expectedInputs[i].TxHash ||
  3948  				input.PreviousOutPoint.Index != test.expectedInputs[i].Vout {
  3949  				t.Fatalf("expected input %d to be %v, got %v", i, test.expectedInputs[i], input.PreviousOutPoint)
  3950  			}
  3951  		}
  3952  
  3953  		if test.expectChange > 0 && len(tx.TxOut) != 2 {
  3954  			t.Fatalf("expected 2 outputs, got %d", len(tx.TxOut))
  3955  		}
  3956  		if test.expectChange == 0 && len(tx.TxOut) != 1 {
  3957  			t.Fatalf("expected 2 outputs, got %d", len(tx.TxOut))
  3958  		}
  3959  
  3960  		if tx.TxOut[0].Value != int64(test.expectSentVal) {
  3961  			t.Fatalf("expected sent value to be %d, got %d", test.expectSentVal, tx.TxOut[0].Value)
  3962  		}
  3963  
  3964  		if test.expectChange > 0 && tx.TxOut[1].Value != int64(test.expectChange) {
  3965  			t.Fatalf("expected change value to be %d, got %d", test.expectChange, tx.TxOut[1].Value)
  3966  		}
  3967  	}
  3968  }
  3969  
  3970  func TestEstimateSendTxFee(t *testing.T) {
  3971  	runRubric(t, testEstimateSendTxFee)
  3972  }
  3973  
  3974  func testEstimateSendTxFee(t *testing.T, segwit bool, walletType string) {
  3975  	wallet, node, shutdown := tNewWallet(segwit, walletType)
  3976  	defer shutdown()
  3977  
  3978  	addr := btcAddr(segwit)
  3979  	sendAddr := addr.String()
  3980  	node.changeAddr = btcAddr(segwit).String()
  3981  	pkScript, _ := txscript.PayToAddrScript(addr)
  3982  
  3983  	unspentVal := 100 // BTC
  3984  	unspents := []*ListUnspentResult{{
  3985  		Address:       addr.String(),
  3986  		Amount:        float64(unspentVal),
  3987  		Confirmations: 1,
  3988  		Vout:          1,
  3989  		ScriptPubKey:  pkScript,
  3990  		SafePtr:       boolPtr(true),
  3991  		Spendable:     true,
  3992  	}}
  3993  	node.listUnspent = unspents
  3994  	var bals GetBalancesResult
  3995  	node.getBalances = &bals
  3996  	bals.Mine.Trusted = float64(unspentVal)
  3997  	unspentSats := toSatoshi(unspents[0].Amount)
  3998  
  3999  	tx := wire.NewMsgTx(wire.TxVersion)
  4000  	tx.AddTxOut(wire.NewTxOut(int64(unspentSats), pkScript))
  4001  
  4002  	// single ouptput size.
  4003  	opSize := dexbtc.P2PKHOutputSize
  4004  	if segwit {
  4005  		opSize = dexbtc.P2WPKHOutputSize
  4006  	}
  4007  
  4008  	// bSize is the size for a single input.
  4009  	witnessWeight := 4
  4010  	bSize := dexbtc.TxInOverhead +
  4011  		wire.VarIntSerializeSize(uint64(dexbtc.RedeemP2PKHSigScriptSize)) +
  4012  		dexbtc.RedeemP2PKHSigScriptSize + (witnessWeight-1)/witnessWeight
  4013  	if segwit {
  4014  		bSize = dexbtc.TxInOverhead + 1 + (dexbtc.RedeemP2WPKHInputWitnessWeight+
  4015  			(witnessWeight-1))/witnessWeight
  4016  	}
  4017  
  4018  	txSize := bSize + tx.SerializeSize()
  4019  	minEstFee := optimalFeeRate * uint64(txSize)
  4020  
  4021  	// This should return fee estimate for one output.
  4022  	node.txFee = minEstFee
  4023  
  4024  	estimate, _, err := wallet.EstimateSendTxFee(sendAddr, unspentSats, optimalFeeRate, true, false)
  4025  	if err != nil {
  4026  		t.Fatal(err)
  4027  	}
  4028  	if estimate != minEstFee {
  4029  		t.Fatalf("expected estimate to be %v, got %v)", minEstFee, estimate)
  4030  	}
  4031  
  4032  	// This should return fee estimate for two output.
  4033  	minEstFeeWithEstChangeFee := uint64(txSize+opSize) * optimalFeeRate
  4034  	node.txFee = minEstFeeWithEstChangeFee
  4035  	estimate, _, err = wallet.EstimateSendTxFee(sendAddr, unspentSats/2, optimalFeeRate, false, false)
  4036  	if err != nil {
  4037  		t.Fatal(err)
  4038  	}
  4039  	if estimate != minEstFeeWithEstChangeFee {
  4040  		t.Fatalf("expected estimate to be %v, got %v)", minEstFeeWithEstChangeFee, estimate)
  4041  	}
  4042  
  4043  	dust := 0.00000016
  4044  	node.listUnspent[0].Amount += dust
  4045  	// This should return fee estimate for one output with dust added to fee.
  4046  	minFeeWithDust := minEstFee + toSatoshi(dust)
  4047  	node.txFee = minFeeWithDust
  4048  	estimate, _, err = wallet.EstimateSendTxFee(sendAddr, unspentSats, optimalFeeRate, true, false)
  4049  	if err != nil {
  4050  		t.Fatal(err)
  4051  	}
  4052  	if estimate != minFeeWithDust {
  4053  		t.Fatalf("expected estimate to be %v, got %v)", minFeeWithDust, estimate)
  4054  	}
  4055  
  4056  	// Invalid address
  4057  	_, valid, _ := wallet.EstimateSendTxFee("invalidsendAddr", unspentSats, optimalFeeRate, true, false)
  4058  	if valid {
  4059  		t.Fatal("Expected false for invalid send address")
  4060  	}
  4061  
  4062  	// Successful estimation without an address
  4063  	_, _, err = wallet.EstimateSendTxFee("", unspentSats, optimalFeeRate, true, false)
  4064  	if err != nil {
  4065  		t.Fatalf("Error for estimation without an address: %v", err)
  4066  	}
  4067  
  4068  	// Zero send amount
  4069  	_, _, err = wallet.EstimateSendTxFee(sendAddr, 0, optimalFeeRate, true, false)
  4070  	if err == nil {
  4071  		t.Fatal("Expected an error for zero send amount")
  4072  	}
  4073  
  4074  	// Output value is dust.
  4075  	_, _, err = wallet.EstimateSendTxFee(sendAddr, 500, optimalFeeRate, true, false)
  4076  	if err == nil {
  4077  		t.Fatal("Expected an error for dust output")
  4078  	}
  4079  }
  4080  
  4081  func TestWithdraw(t *testing.T) {
  4082  	runRubric(t, func(t *testing.T, segwit bool, walletType string) {
  4083  		testSender(t, tWithdrawSender, segwit, walletType)
  4084  	})
  4085  }
  4086  
  4087  func TestSend(t *testing.T) {
  4088  	runRubric(t, func(t *testing.T, segwit bool, walletType string) {
  4089  		testSender(t, tSendSender, segwit, walletType)
  4090  	})
  4091  }
  4092  
  4093  func TestConfirmations(t *testing.T) {
  4094  	runRubric(t, testConfirmations)
  4095  }
  4096  
  4097  func testConfirmations(t *testing.T, segwit bool, walletType string) {
  4098  	wallet, node, shutdown := tNewWallet(segwit, walletType)
  4099  	defer shutdown()
  4100  
  4101  	// coinID := make([]byte, 36)
  4102  	// copy(coinID[:32], tTxHash[:])
  4103  
  4104  	_, _, pkScript, contract, _, _, _ := makeSwapContract(segwit, time.Hour*12)
  4105  	const tipHeight = 10
  4106  	const swapHeight = 2
  4107  	const spendHeight = 4
  4108  	const expConfs = tipHeight - swapHeight + 1
  4109  
  4110  	tx := makeRawTx([]dex.Bytes{pkScript}, []*wire.TxIn{dummyInput()})
  4111  	blockHash, swapBlock := node.addRawTx(swapHeight, tx)
  4112  	txHash := tx.TxHash()
  4113  	coinID := ToCoinID(&txHash, 0)
  4114  	// Simulate a spending transaction, and advance the tip so that the swap
  4115  	// has two confirmations.
  4116  	spendingTx := dummyTx()
  4117  	spendingTx.TxIn[0].PreviousOutPoint.Hash = txHash
  4118  	spendingBlockHash, _ := node.addRawTx(spendHeight, spendingTx)
  4119  
  4120  	// Prime the blockchain
  4121  	for i := int64(1); i <= tipHeight; i++ {
  4122  		node.addRawTx(i, dummyTx())
  4123  	}
  4124  
  4125  	matchTime := swapBlock.Header.Timestamp
  4126  
  4127  	// Bad coin id
  4128  	_, _, err := wallet.SwapConfirmations(context.Background(), randBytes(35), contract, matchTime)
  4129  	if err == nil {
  4130  		t.Fatalf("no error for bad coin ID")
  4131  	}
  4132  
  4133  	// Short path.
  4134  	txOutRes := &btcjson.GetTxOutResult{
  4135  		Confirmations: expConfs,
  4136  		BestBlock:     blockHash.String(),
  4137  	}
  4138  	node.txOutRes = txOutRes
  4139  	node.getCFilterScripts[*blockHash] = [][]byte{pkScript}
  4140  	confs, _, err := wallet.SwapConfirmations(context.Background(), coinID, contract, matchTime)
  4141  	if err != nil {
  4142  		t.Fatalf("error for gettransaction path: %v", err)
  4143  	}
  4144  	if confs != expConfs {
  4145  		t.Fatalf("confs not retrieved from gettxout path. expected %d, got %d", expConfs, confs)
  4146  	}
  4147  
  4148  	// no tx output found
  4149  	node.txOutRes = nil
  4150  	node.getCFilterScripts[*blockHash] = nil
  4151  	node.getTransactionErr = tErr
  4152  	_, _, err = wallet.SwapConfirmations(context.Background(), coinID, contract, matchTime)
  4153  	if err == nil {
  4154  		t.Fatalf("no error for gettransaction error")
  4155  	}
  4156  	node.getCFilterScripts[*blockHash] = [][]byte{pkScript}
  4157  	node.getTransactionErr = nil
  4158  	txB, _ := serializeMsgTx(tx)
  4159  
  4160  	node.getTransactionMap = map[string]*GetTransactionResult{
  4161  		"any": {
  4162  			BlockHash: blockHash.String(),
  4163  			Bytes:     txB,
  4164  		}}
  4165  
  4166  	node.getCFilterScripts[*spendingBlockHash] = [][]byte{pkScript}
  4167  	node.walletTxSpent = true
  4168  	_, spent, err := wallet.SwapConfirmations(context.Background(), coinID, contract, matchTime)
  4169  	if err != nil {
  4170  		t.Fatalf("error for spent swap: %v", err)
  4171  	}
  4172  	if !spent {
  4173  		t.Fatalf("swap not spent")
  4174  	}
  4175  }
  4176  
  4177  func TestSendEdges(t *testing.T) {
  4178  	runRubric(t, testSendEdges)
  4179  }
  4180  
  4181  func testSendEdges(t *testing.T, segwit bool, walletType string) {
  4182  	wallet, node, shutdown := tNewWallet(segwit, walletType)
  4183  	defer shutdown()
  4184  
  4185  	const feeRate uint64 = 3
  4186  
  4187  	const swapVal = 2e8 // leaving untyped. NewTxOut wants int64
  4188  
  4189  	var addr, contractAddr btcutil.Address
  4190  	var dexReqFees, dustCoverage uint64
  4191  
  4192  	addr = btcAddr(segwit)
  4193  
  4194  	if segwit {
  4195  		contractAddr, _ = btcutil.NewAddressWitnessScriptHash(randBytes(32), &chaincfg.MainNetParams)
  4196  		// See dexbtc.IsDust for the source of this dustCoverage voodoo.
  4197  		dustCoverage = (dexbtc.P2WPKHOutputSize + 41 + (107 / 4)) * feeRate * 3
  4198  		dexReqFees = dexbtc.InitTxSizeSegwit * feeRate
  4199  	} else {
  4200  		contractAddr, _ = btcutil.NewAddressScriptHash(randBytes(20), &chaincfg.MainNetParams)
  4201  		dustCoverage = (dexbtc.P2PKHOutputSize + 41 + 107) * feeRate * 3
  4202  		dexReqFees = dexbtc.InitTxSize * feeRate
  4203  	}
  4204  
  4205  	pkScript, _ := txscript.PayToAddrScript(contractAddr)
  4206  
  4207  	newBaseTx := func() *wire.MsgTx {
  4208  		baseTx := wire.NewMsgTx(wire.TxVersion)
  4209  		baseTx.AddTxIn(wire.NewTxIn(new(wire.OutPoint), nil, nil))
  4210  		baseTx.AddTxOut(wire.NewTxOut(swapVal, pkScript))
  4211  		return baseTx
  4212  	}
  4213  
  4214  	node.signFunc = func(tx *wire.MsgTx) {
  4215  		signFunc(tx, 0, wallet.segwit)
  4216  	}
  4217  
  4218  	tests := []struct {
  4219  		name      string
  4220  		funding   uint64
  4221  		expChange bool
  4222  	}{
  4223  		{
  4224  			name:    "not enough for change output",
  4225  			funding: swapVal + dexReqFees - 1,
  4226  		},
  4227  		{
  4228  			// Still dust here, but a different path.
  4229  			name:    "exactly enough for change output",
  4230  			funding: swapVal + dexReqFees,
  4231  		},
  4232  		{
  4233  			name:    "more than enough for change output but still dust",
  4234  			funding: swapVal + dexReqFees + 1,
  4235  		},
  4236  		{
  4237  			name:    "1 atom short to not be dust",
  4238  			funding: swapVal + dexReqFees + dustCoverage - 1,
  4239  		},
  4240  		{
  4241  			name:      "exactly enough to not be dust",
  4242  			funding:   swapVal + dexReqFees + dustCoverage,
  4243  			expChange: true,
  4244  		},
  4245  	}
  4246  
  4247  	for _, tt := range tests {
  4248  		tx, err := wallet.sendWithReturn(newBaseTx(), addr, tt.funding, swapVal, feeRate)
  4249  		if err != nil {
  4250  			t.Fatalf("sendWithReturn error: %v", err)
  4251  		}
  4252  
  4253  		if len(tx.TxOut) == 1 && tt.expChange {
  4254  			t.Fatalf("%s: no change added", tt.name)
  4255  		} else if len(tx.TxOut) == 2 && !tt.expChange {
  4256  			t.Fatalf("%s: change output added for dust. Output value = %d", tt.name, tx.TxOut[1].Value)
  4257  		}
  4258  	}
  4259  }
  4260  
  4261  func TestSyncStatus(t *testing.T) {
  4262  	runRubric(t, testSyncStatus)
  4263  }
  4264  
  4265  func testSyncStatus(t *testing.T, segwit bool, walletType string) {
  4266  	wallet, node, shutdown := tNewWallet(segwit, walletType)
  4267  	defer shutdown()
  4268  
  4269  	// full node
  4270  	node.getBlockchainInfo = &GetBlockchainInfoResult{
  4271  		Headers: 100,
  4272  		Blocks:  99, // full node allowed to be synced when 1 block behind
  4273  	}
  4274  
  4275  	// spv
  4276  	blkHash, msgBlock := node.addRawTx(100, dummyTx())
  4277  	node.birthdayTime = msgBlock.Header.Timestamp.Add(-time.Minute) // SPV, wallet birthday is passed
  4278  	node.mainchain[100] = blkHash                                   // SPV, actually has to reach target
  4279  
  4280  	ss, err := wallet.SyncStatus()
  4281  	if err != nil {
  4282  		t.Fatalf("SyncStatus error (synced expected): %v", err)
  4283  	}
  4284  	if !ss.Synced {
  4285  		t.Fatalf("synced = false")
  4286  	}
  4287  	if ss.BlockProgress() < 1 {
  4288  		t.Fatalf("progress not complete when loading last block")
  4289  	}
  4290  
  4291  	node.getBlockchainInfoErr = tErr // rpc
  4292  	node.blockchainMtx.Lock()
  4293  	node.getBestBlockHashErr = tErr // spv BestBlock()
  4294  	node.blockchainMtx.Unlock()
  4295  	delete(node.mainchain, 100) // force spv to BestBlock() with no wallet block
  4296  	_, err = wallet.SyncStatus()
  4297  	if err == nil {
  4298  		t.Fatalf("SyncStatus error not propagated")
  4299  	}
  4300  	node.getBlockchainInfoErr = nil
  4301  	node.blockchainMtx.Lock()
  4302  	node.getBestBlockHashErr = nil
  4303  	node.blockchainMtx.Unlock()
  4304  
  4305  	wallet.tipAtConnect = 100
  4306  	node.getBlockchainInfo = &GetBlockchainInfoResult{
  4307  		Headers: 200,
  4308  		Blocks:  150,
  4309  	}
  4310  	node.addRawTx(150, makeRawTx([]dex.Bytes{randBytes(1)}, []*wire.TxIn{dummyInput()})) // spv needs this for BestBlock
  4311  	ss, err = wallet.SyncStatus()
  4312  	if err != nil {
  4313  		t.Fatalf("SyncStatus error (half-synced): %v", err)
  4314  	}
  4315  	if ss.Synced {
  4316  		t.Fatalf("synced = true for 50 blocks to go")
  4317  	}
  4318  	if ss.BlockProgress() > 0.500001 || ss.BlockProgress() < 0.4999999 {
  4319  		t.Fatalf("progress out of range. Expected 0.5, got %.2f", ss.BlockProgress())
  4320  	}
  4321  }
  4322  
  4323  func TestPreSwap(t *testing.T) {
  4324  	runRubric(t, testPreSwap)
  4325  }
  4326  
  4327  func testPreSwap(t *testing.T, segwit bool, walletType string) {
  4328  	wallet, node, shutdown := tNewWallet(segwit, walletType)
  4329  	defer shutdown()
  4330  
  4331  	// See math from TestFundEdges. 10 lots with max fee rate of 34 sats/vbyte.
  4332  
  4333  	swapVal := uint64(1e7)
  4334  	lots := swapVal / tLotSize // 10 lots
  4335  
  4336  	// var swapSize = 225
  4337  	var totalBytes uint64 = 2250
  4338  	var bestCaseBytes uint64 = 225
  4339  	pkScript := tP2PKH
  4340  	if segwit {
  4341  		// swapSize = 153
  4342  		totalBytes = 1530
  4343  		bestCaseBytes = 153
  4344  		pkScript = tP2WPKH
  4345  	}
  4346  
  4347  	backingFees := totalBytes * tBTC.MaxFeeRate // total_bytes * fee_rate
  4348  
  4349  	minReq := swapVal + backingFees
  4350  
  4351  	unspent := &ListUnspentResult{
  4352  		TxID:          tTxID,
  4353  		Address:       tP2PKHAddr,
  4354  		Confirmations: 5,
  4355  		ScriptPubKey:  pkScript,
  4356  		Spendable:     true,
  4357  		Solvable:      true,
  4358  		SafePtr:       boolPtr(true),
  4359  	}
  4360  	unspents := []*ListUnspentResult{unspent}
  4361  
  4362  	setFunds := func(v uint64) {
  4363  		unspent.Amount = float64(v) / 1e8
  4364  		node.listUnspent = unspents
  4365  	}
  4366  
  4367  	form := &asset.PreSwapForm{
  4368  		Version:       version,
  4369  		LotSize:       tLotSize,
  4370  		Lots:          lots,
  4371  		MaxFeeRate:    tBTC.MaxFeeRate,
  4372  		Immediate:     false,
  4373  		FeeSuggestion: feeSuggestion,
  4374  		// Redeem fields unneeded
  4375  	}
  4376  
  4377  	setFunds(minReq)
  4378  
  4379  	// Initial success.
  4380  	preSwap, err := wallet.PreSwap(form)
  4381  	if err != nil {
  4382  		t.Fatalf("PreSwap error: %v", err)
  4383  	}
  4384  
  4385  	maxFees := totalBytes * tBTC.MaxFeeRate
  4386  	estHighFees := totalBytes * feeSuggestion
  4387  	estLowFees := bestCaseBytes * feeSuggestion
  4388  	checkSwapEstimate(t, preSwap.Estimate, lots, swapVal, maxFees, estHighFees, estLowFees)
  4389  
  4390  	// Too little funding is an error.
  4391  	setFunds(minReq - 1)
  4392  	_, err = wallet.PreSwap(form)
  4393  	if err == nil {
  4394  		t.Fatalf("no PreSwap error for not enough funds")
  4395  	}
  4396  	setFunds(minReq)
  4397  
  4398  	// Success again.
  4399  	_, err = wallet.PreSwap(form)
  4400  	if err != nil {
  4401  		t.Fatalf("PreSwap error: %v", err)
  4402  	}
  4403  }
  4404  
  4405  func TestPreRedeem(t *testing.T) {
  4406  	runRubric(t, testPreRedeem)
  4407  }
  4408  
  4409  func testPreRedeem(t *testing.T, segwit bool, walletType string) {
  4410  	wallet, _, shutdown := tNewWallet(segwit, walletType)
  4411  	defer shutdown()
  4412  
  4413  	preRedeem, err := wallet.PreRedeem(&asset.PreRedeemForm{
  4414  		Version: version,
  4415  		Lots:    5,
  4416  	})
  4417  	// Shouldn't actually be any path to error.
  4418  	if err != nil {
  4419  		t.Fatalf("PreRedeem non-segwit error: %v", err)
  4420  	}
  4421  
  4422  	// Just a couple of sanity checks.
  4423  	if preRedeem.Estimate.RealisticBestCase >= preRedeem.Estimate.RealisticWorstCase {
  4424  		t.Fatalf("best case > worst case")
  4425  	}
  4426  }
  4427  
  4428  func TestTryRedemptionRequests(t *testing.T) {
  4429  	// runRubric(t, testTryRedemptionRequests)
  4430  	testTryRedemptionRequests(t, true, walletTypeSPV)
  4431  }
  4432  
  4433  func testTryRedemptionRequests(t *testing.T, segwit bool, walletType string) {
  4434  	wallet, node, shutdown := tNewWallet(segwit, walletType)
  4435  	defer shutdown()
  4436  
  4437  	const swapVout = 1
  4438  
  4439  	randHash := func() *chainhash.Hash {
  4440  		var h chainhash.Hash
  4441  		copy(h[:], randBytes(32))
  4442  		return &h
  4443  	}
  4444  
  4445  	otherScript, _ := txscript.PayToAddrScript(btcAddr(segwit))
  4446  	otherInput := []*wire.TxIn{makeRPCVin(randHash(), 0, randBytes(5), nil)}
  4447  	otherTx := func() *wire.MsgTx {
  4448  		return makeRawTx([]dex.Bytes{otherScript}, otherInput)
  4449  	}
  4450  
  4451  	addBlocks := func(n int) {
  4452  		var h int64
  4453  		// Make dummy transactions.
  4454  		for i := 0; i < n; i++ {
  4455  			node.addRawTx(h, otherTx())
  4456  			h++
  4457  		}
  4458  	}
  4459  
  4460  	getTx := func(blockHeight int64, txIdx int) (*wire.MsgTx, *chainhash.Hash) {
  4461  		if blockHeight == -1 {
  4462  			// mempool
  4463  			txHash := randHash()
  4464  			tx := otherTx()
  4465  			node.mempoolTxs[*txHash] = tx
  4466  			return tx, nil
  4467  		}
  4468  		blockHash, blk := node.getBlockAtHeight(blockHeight)
  4469  		for len(blk.msgBlock.Transactions) <= txIdx {
  4470  			blk.msgBlock.Transactions = append(blk.msgBlock.Transactions, otherTx())
  4471  		}
  4472  		return blk.msgBlock.Transactions[txIdx], blockHash
  4473  	}
  4474  
  4475  	type tRedeem struct {
  4476  		redeemTxIdx, redeemVin        int
  4477  		swapHeight, redeemBlockHeight int64
  4478  		notRedeemed                   bool
  4479  	}
  4480  
  4481  	redeemReq := func(r *tRedeem) *FindRedemptionReq {
  4482  		var swapBlockHash *chainhash.Hash
  4483  		var swapHeight int64
  4484  		if r.swapHeight >= 0 {
  4485  			swapHeight = r.swapHeight
  4486  			swapBlockHash, _ = node.getBlockAtHeight(swapHeight)
  4487  		}
  4488  
  4489  		swapTxHash := randHash()
  4490  		secret, _, pkScript, contract, _, _, _ := makeSwapContract(segwit, time.Hour*12)
  4491  
  4492  		if !r.notRedeemed {
  4493  			redeemTx, redeemBlockHash := getTx(r.redeemBlockHeight, r.redeemTxIdx)
  4494  
  4495  			// redemptionSigScript, _ := dexbtc.RedeemP2SHContract(contract, randBytes(73), randBytes(33), secret)
  4496  			for len(redeemTx.TxIn) < r.redeemVin {
  4497  				redeemTx.TxIn = append(redeemTx.TxIn, makeRPCVin(randHash(), 0, nil, nil))
  4498  			}
  4499  
  4500  			var redemptionSigScript []byte
  4501  			var redemptionWitness [][]byte
  4502  			if segwit {
  4503  				redemptionWitness = dexbtc.RedeemP2WSHContract(contract, randBytes(73), randBytes(33), secret)
  4504  			} else {
  4505  				redemptionSigScript, _ = dexbtc.RedeemP2SHContract(contract, randBytes(73), randBytes(33), secret)
  4506  			}
  4507  
  4508  			redeemTx.TxIn = append(redeemTx.TxIn, makeRPCVin(swapTxHash, swapVout, redemptionSigScript, redemptionWitness))
  4509  			if redeemBlockHash != nil {
  4510  				node.getCFilterScripts[*redeemBlockHash] = [][]byte{pkScript}
  4511  			}
  4512  		}
  4513  
  4514  		req := &FindRedemptionReq{
  4515  			outPt:        NewOutPoint(swapTxHash, swapVout),
  4516  			blockHash:    swapBlockHash,
  4517  			blockHeight:  int32(swapHeight),
  4518  			resultChan:   make(chan *FindRedemptionResult, 1),
  4519  			pkScript:     pkScript,
  4520  			contractHash: hashContract(segwit, contract),
  4521  		}
  4522  		wallet.rf.redemptions[req.outPt] = req
  4523  		return req
  4524  	}
  4525  
  4526  	type test struct {
  4527  		numBlocks        int
  4528  		startBlockHeight int64
  4529  		redeems          []*tRedeem
  4530  		forcedErr        bool
  4531  		canceledCtx      bool
  4532  	}
  4533  
  4534  	isMempoolTest := func(tt *test) bool {
  4535  		for _, r := range tt.redeems {
  4536  			if r.redeemBlockHeight == -1 {
  4537  				return true
  4538  			}
  4539  		}
  4540  		return false
  4541  	}
  4542  
  4543  	tests := []*test{
  4544  		{ // Normal redemption
  4545  			numBlocks: 2,
  4546  			redeems: []*tRedeem{{
  4547  				redeemBlockHeight: 1,
  4548  				redeemTxIdx:       1,
  4549  				redeemVin:         1,
  4550  			}},
  4551  		},
  4552  		{ // Mempool redemption
  4553  			numBlocks: 2,
  4554  			redeems: []*tRedeem{{
  4555  				redeemBlockHeight: -1,
  4556  				redeemTxIdx:       2,
  4557  				redeemVin:         2,
  4558  			}},
  4559  		},
  4560  		{ // A couple of redemptions, both in tip.
  4561  			numBlocks:        3,
  4562  			startBlockHeight: 1,
  4563  			redeems: []*tRedeem{{
  4564  				redeemBlockHeight: 2,
  4565  			}, {
  4566  				redeemBlockHeight: 2,
  4567  			}},
  4568  		},
  4569  		{ // A couple of redemptions, spread apart.
  4570  			numBlocks: 6,
  4571  			redeems: []*tRedeem{{
  4572  				redeemBlockHeight: 2,
  4573  			}, {
  4574  				redeemBlockHeight: 4,
  4575  			}},
  4576  		},
  4577  		{ // nil start block
  4578  			numBlocks:        5,
  4579  			startBlockHeight: -1,
  4580  			redeems: []*tRedeem{{
  4581  				swapHeight:        4,
  4582  				redeemBlockHeight: 4,
  4583  			}},
  4584  		},
  4585  		{ // A mix of mined and mempool redeems
  4586  			numBlocks:        3,
  4587  			startBlockHeight: 1,
  4588  			redeems: []*tRedeem{{
  4589  				redeemBlockHeight: -1,
  4590  			}, {
  4591  				redeemBlockHeight: 2,
  4592  			}},
  4593  		},
  4594  		{ // One found, one not found.
  4595  			numBlocks:        4,
  4596  			startBlockHeight: 1,
  4597  			redeems: []*tRedeem{{
  4598  				redeemBlockHeight: 2,
  4599  			}, {
  4600  				notRedeemed: true,
  4601  			}},
  4602  		},
  4603  		{ // One found in mempool, one not found.
  4604  			numBlocks:        4,
  4605  			startBlockHeight: 1,
  4606  			redeems: []*tRedeem{{
  4607  				redeemBlockHeight: -1,
  4608  			}, {
  4609  				notRedeemed: true,
  4610  			}},
  4611  		},
  4612  		{ // Swap not mined.
  4613  			numBlocks:        3,
  4614  			startBlockHeight: 1,
  4615  			redeems: []*tRedeem{{
  4616  				swapHeight:        -1,
  4617  				redeemBlockHeight: -1,
  4618  			}},
  4619  		},
  4620  		{ // Fatal error
  4621  			numBlocks: 2,
  4622  			forcedErr: true,
  4623  			redeems: []*tRedeem{{
  4624  				redeemBlockHeight: 1,
  4625  			}},
  4626  		},
  4627  		{ // Canceled context.
  4628  			numBlocks:   2,
  4629  			canceledCtx: true,
  4630  			redeems: []*tRedeem{{
  4631  				redeemBlockHeight: 1,
  4632  			}},
  4633  		},
  4634  	}
  4635  
  4636  	for _, tt := range tests {
  4637  		// Skip tests where we're expected to see mempool in SPV.
  4638  		if walletType == walletTypeSPV && isMempoolTest(tt) {
  4639  			continue
  4640  		}
  4641  
  4642  		node.truncateChains()
  4643  		wallet.rf.redemptions = make(map[OutPoint]*FindRedemptionReq)
  4644  		node.blockchainMtx.Lock()
  4645  		node.getBestBlockHashErr = nil
  4646  		if tt.forcedErr {
  4647  			node.getBestBlockHashErr = tErr
  4648  		}
  4649  		node.blockchainMtx.Unlock()
  4650  		addBlocks(tt.numBlocks)
  4651  		var startBlock *chainhash.Hash
  4652  		if tt.startBlockHeight >= 0 {
  4653  			startBlock, _ = node.getBlockAtHeight(tt.startBlockHeight)
  4654  		}
  4655  
  4656  		ctx := tCtx
  4657  		if tt.canceledCtx {
  4658  			timedCtx, cancel := context.WithTimeout(tCtx, time.Second)
  4659  			ctx = timedCtx
  4660  			cancel()
  4661  		}
  4662  
  4663  		reqs := make([]*FindRedemptionReq, 0, len(tt.redeems))
  4664  		for _, redeem := range tt.redeems {
  4665  			reqs = append(reqs, redeemReq(redeem))
  4666  		}
  4667  
  4668  		wallet.rf.tryRedemptionRequests(ctx, startBlock, reqs)
  4669  
  4670  		for i, req := range reqs {
  4671  			select {
  4672  			case res := <-req.resultChan:
  4673  				if res.err != nil {
  4674  					if !tt.forcedErr {
  4675  						t.Fatalf("result error: %v", res.err)
  4676  					}
  4677  				} else if tt.canceledCtx {
  4678  					t.Fatalf("got success with canceled context")
  4679  				}
  4680  			default:
  4681  				redeem := tt.redeems[i]
  4682  				if !redeem.notRedeemed && !tt.canceledCtx {
  4683  					t.Fatalf("redemption not found")
  4684  				}
  4685  			}
  4686  		}
  4687  	}
  4688  }
  4689  
  4690  func TestPrettyBTC(t *testing.T) {
  4691  	type test struct {
  4692  		v   uint64
  4693  		exp string
  4694  	}
  4695  
  4696  	tests := []*test{{
  4697  		v:   1,
  4698  		exp: "0.00000001",
  4699  	}, {
  4700  		v:   1e8,
  4701  		exp: "1",
  4702  	}, {
  4703  		v:   100000001,
  4704  		exp: "1.00000001",
  4705  	}, {
  4706  		v:   0,
  4707  		exp: "0",
  4708  	}, {
  4709  		v:   123000,
  4710  		exp: "0.00123",
  4711  	}, {
  4712  		v:   100123000,
  4713  		exp: "1.00123",
  4714  	}}
  4715  
  4716  	for _, tt := range tests {
  4717  		if prettyBTC(tt.v) != tt.exp {
  4718  			t.Fatalf("prettyBTC(%d) = %s != %q", tt.v, prettyBTC(tt.v), tt.exp)
  4719  		}
  4720  	}
  4721  }
  4722  
  4723  // TestAccelerateOrder tests the entire acceleration workflow, including
  4724  // AccelerateOrder, PreAccelerate, and AccelerationEstimate
  4725  func TestAccelerateOrder(t *testing.T) {
  4726  	runRubric(t, testAccelerateOrder)
  4727  }
  4728  
  4729  func testAccelerateOrder(t *testing.T, segwit bool, walletType string) {
  4730  	w, node, shutdown := tNewWallet(segwit, walletType)
  4731  	defer shutdown()
  4732  
  4733  	wallet := &ExchangeWalletAccelerator{&ExchangeWalletFullNode{w, &authAddOn{w.node}}}
  4734  
  4735  	var blockHash100 chainhash.Hash
  4736  	copy(blockHash100[:], encode.RandomBytes(32))
  4737  	node.verboseBlocks[blockHash100] = &msgBlockWithHeight{height: 100, msgBlock: &wire.MsgBlock{
  4738  		Header: wire.BlockHeader{Timestamp: time.Now()},
  4739  	}}
  4740  	node.mainchain[100] = &blockHash100
  4741  
  4742  	if segwit {
  4743  		node.changeAddr = tP2WPKHAddr
  4744  		node.newAddress = tP2WPKHAddr
  4745  	} else {
  4746  		node.changeAddr = tP2PKHAddr
  4747  		node.newAddress = tP2PKHAddr
  4748  	}
  4749  
  4750  	node.signFunc = func(tx *wire.MsgTx) {
  4751  		signFunc(tx, 0, wallet.segwit)
  4752  	}
  4753  
  4754  	sumFees := func(fees []float64, confs []uint64) uint64 {
  4755  		var totalFees uint64
  4756  		for i, fee := range fees {
  4757  			if confs[i] == 0 {
  4758  				totalFees += toSatoshi(fee)
  4759  			}
  4760  		}
  4761  		return totalFees
  4762  	}
  4763  
  4764  	sumTxSizes := func(txs []*wire.MsgTx, confirmations []uint64) uint64 {
  4765  		var totalSize uint64
  4766  		for i, tx := range txs {
  4767  			if confirmations[i] == 0 {
  4768  				totalSize += dexbtc.MsgTxVBytes(tx)
  4769  			}
  4770  		}
  4771  
  4772  		return totalSize
  4773  	}
  4774  
  4775  	loadTxsIntoNode := func(txs []*wire.MsgTx, fees []float64, confs []uint64, node *testData, withinTimeLimit bool, t *testing.T) {
  4776  		t.Helper()
  4777  		if len(txs) != len(fees) || len(txs) != len(confs) {
  4778  			t.Fatalf("len(txs) = %d, len(fees) = %d, len(confs) = %d", len(txs), len(fees), len(confs))
  4779  		}
  4780  
  4781  		serializedTxs := make([][]byte, 0, len(txs))
  4782  		for _, tx := range txs {
  4783  			serializedTx, err := serializeMsgTx(tx)
  4784  			if err != nil {
  4785  				t.Fatalf("unexpected error: %v", err)
  4786  			}
  4787  			serializedTxs = append(serializedTxs, serializedTx)
  4788  		}
  4789  
  4790  		currentTime := time.Now().Unix()
  4791  		for i := range txs {
  4792  			var blockHash string
  4793  			if confs[i] == 1 {
  4794  				blockHash = blockHash100.String()
  4795  			}
  4796  			node.getTransactionMap[txs[i].TxHash().String()] = &GetTransactionResult{
  4797  				TxID:          txs[i].TxHash().String(),
  4798  				Bytes:         serializedTxs[i],
  4799  				BlockHash:     blockHash,
  4800  				Confirmations: confs[i]}
  4801  
  4802  			if withinTimeLimit {
  4803  				node.getTransactionMap[txs[i].TxHash().String()].Time = uint64(currentTime) - minTimeBeforeAcceleration + 3
  4804  			} else {
  4805  				node.getTransactionMap[txs[i].TxHash().String()].Time = uint64(currentTime) - minTimeBeforeAcceleration - 1000
  4806  			}
  4807  		}
  4808  	}
  4809  
  4810  	// getAccelerationParams returns a chain of 4 swap transactions where the change
  4811  	// output of the last transaction has a certain value. If addAcceleration true,
  4812  	// the third transaction will be an acceleration transaction instead of one
  4813  	// that initiates a swap.
  4814  	getAccelerationParams := func(changeVal int64, addChangeToUnspent, addAcceleration bool, fees []float64, node *testData) ([]dex.Bytes, []dex.Bytes, dex.Bytes, []*wire.MsgTx) {
  4815  		txs := make([]*wire.MsgTx, 4)
  4816  
  4817  		// In order to be able to test using the SPV wallet, we need to properly
  4818  		// set the size of each of the outputs. The SPV wallet will parse these
  4819  		// transactions and calculate the fee on its own instead of just returning
  4820  		// what was set in getTransactionMap.
  4821  		changeOutputAmounts := make([]int64, 4)
  4822  		changeOutputAmounts[3] = changeVal
  4823  		swapAmount := int64(2e6)
  4824  		for i := 2; i >= 0; i-- {
  4825  			changeAmount := int64(toSatoshi(fees[i+1])) + changeOutputAmounts[i+1]
  4826  			if !(i == 1 && addAcceleration) {
  4827  				changeAmount += swapAmount
  4828  			}
  4829  			changeOutputAmounts[i] = changeAmount
  4830  		}
  4831  
  4832  		// The initial transaction in the chain has multiple inputs.
  4833  		fundingCoinsTotalOutput := changeOutputAmounts[0] + swapAmount + int64(toSatoshi(fees[0]))
  4834  		fundingTx := wire.MsgTx{
  4835  			TxIn: []*wire.TxIn{{
  4836  				PreviousOutPoint: wire.OutPoint{
  4837  					Hash:  *tTxHash,
  4838  					Index: 0,
  4839  				}},
  4840  				{PreviousOutPoint: wire.OutPoint{
  4841  					Hash:  *tTxHash,
  4842  					Index: 1,
  4843  				}},
  4844  			},
  4845  			TxOut: []*wire.TxOut{{
  4846  				Value: fundingCoinsTotalOutput / 2,
  4847  			}, {
  4848  				Value: fundingCoinsTotalOutput - fundingCoinsTotalOutput/2,
  4849  			}},
  4850  		}
  4851  		fudingTxHex, _ := serializeMsgTx(&fundingTx)
  4852  		node.getTransactionMap[fundingTx.TxHash().String()] = &GetTransactionResult{Bytes: fudingTxHex, BlockHash: blockHash100.String()}
  4853  
  4854  		txs[0] = &wire.MsgTx{
  4855  			TxIn: []*wire.TxIn{{
  4856  				PreviousOutPoint: wire.OutPoint{
  4857  					Hash:  fundingTx.TxHash(),
  4858  					Index: 0,
  4859  				}},
  4860  				{PreviousOutPoint: wire.OutPoint{
  4861  					Hash:  fundingTx.TxHash(),
  4862  					Index: 1,
  4863  				}},
  4864  			},
  4865  			TxOut: []*wire.TxOut{{
  4866  				Value: changeOutputAmounts[0],
  4867  			}, {
  4868  				Value: swapAmount,
  4869  			}},
  4870  		}
  4871  		for i := 1; i < 4; i++ {
  4872  			txs[i] = &wire.MsgTx{
  4873  				TxIn: []*wire.TxIn{{
  4874  					PreviousOutPoint: wire.OutPoint{
  4875  						Hash:  txs[i-1].TxHash(),
  4876  						Index: 0,
  4877  					}},
  4878  				},
  4879  				TxOut: []*wire.TxOut{{
  4880  					Value: changeOutputAmounts[i],
  4881  				}},
  4882  			}
  4883  			if !(i == 2 && addAcceleration) {
  4884  				txs[i].TxOut = append(txs[i].TxOut, &wire.TxOut{Value: swapAmount})
  4885  			}
  4886  		}
  4887  
  4888  		swapCoins := make([]dex.Bytes, 0, len(txs))
  4889  		accelerationCoins := make([]dex.Bytes, 0, 1)
  4890  		var changeCoin dex.Bytes
  4891  		for i, tx := range txs {
  4892  			hash := tx.TxHash()
  4893  
  4894  			if i == 2 && addAcceleration {
  4895  				accelerationCoins = append(accelerationCoins, ToCoinID(&hash, 0))
  4896  			} else {
  4897  				ToCoinID(&hash, 0)
  4898  				swapCoins = append(swapCoins, ToCoinID(&hash, 0))
  4899  			}
  4900  
  4901  			if i == len(txs)-1 {
  4902  				changeCoin = ToCoinID(&hash, 0)
  4903  				if addChangeToUnspent {
  4904  					node.listUnspent = append(node.listUnspent, &ListUnspentResult{
  4905  						TxID: hash.String(),
  4906  						Vout: 0,
  4907  					})
  4908  				}
  4909  			}
  4910  		}
  4911  
  4912  		return swapCoins, accelerationCoins, changeCoin, txs
  4913  	}
  4914  
  4915  	addUTXOToNode := func(confs uint32, segwit bool, amount uint64, node *testData) {
  4916  		var scriptPubKey []byte
  4917  		if segwit {
  4918  			scriptPubKey = tP2WPKH
  4919  		} else {
  4920  			scriptPubKey = tP2PKH
  4921  		}
  4922  
  4923  		node.listUnspent = append(node.listUnspent, &ListUnspentResult{
  4924  			TxID:          hex.EncodeToString(encode.RandomBytes(32)),
  4925  			Address:       "1Bggq7Vu5oaoLFV1NNp5KhAzcku83qQhgi",
  4926  			Amount:        toBTC(amount),
  4927  			Confirmations: confs,
  4928  			ScriptPubKey:  scriptPubKey,
  4929  			Spendable:     true,
  4930  			Solvable:      true,
  4931  		})
  4932  
  4933  		var prevChainHash chainhash.Hash
  4934  		copy(prevChainHash[:], encode.RandomBytes(32))
  4935  
  4936  		tx := &wire.MsgTx{
  4937  			TxIn: []*wire.TxIn{{
  4938  				PreviousOutPoint: wire.OutPoint{
  4939  					Hash:  prevChainHash,
  4940  					Index: 0,
  4941  				}},
  4942  			},
  4943  			TxOut: []*wire.TxOut{
  4944  				{
  4945  					Value: int64(amount),
  4946  				},
  4947  			}}
  4948  		unspentTxHex, err := serializeMsgTx(tx)
  4949  		if err != nil {
  4950  			panic(fmt.Sprintf("could not serialize: %v", err))
  4951  		}
  4952  		var blockHash string
  4953  		if confs == 0 {
  4954  			blockHash = blockHash100.String()
  4955  		}
  4956  
  4957  		node.getTransactionMap[node.listUnspent[len(node.listUnspent)-1].TxID] = &GetTransactionResult{
  4958  			TxID:          tx.TxHash().String(),
  4959  			Bytes:         unspentTxHex,
  4960  			BlockHash:     blockHash,
  4961  			Confirmations: uint64(confs)}
  4962  	}
  4963  
  4964  	totalInputOutput := func(tx *wire.MsgTx) (uint64, uint64) {
  4965  		var in, out uint64
  4966  		for _, input := range tx.TxIn {
  4967  			inputGtr, found := node.getTransactionMap[input.PreviousOutPoint.Hash.String()]
  4968  			if !found {
  4969  				t.Fatalf("tx id not found: %v", input.PreviousOutPoint.Hash.String())
  4970  			}
  4971  			inputTx, err := msgTxFromHex(inputGtr.Bytes.String())
  4972  			if err != nil {
  4973  				t.Fatalf("failed to deserialize tx: %v", err)
  4974  			}
  4975  			in += uint64(inputTx.TxOut[input.PreviousOutPoint.Index].Value)
  4976  		}
  4977  
  4978  		for _, output := range node.sentRawTx.TxOut {
  4979  			out += uint64(output.Value)
  4980  		}
  4981  
  4982  		return in, out
  4983  	}
  4984  
  4985  	calculateChangeTxSize := func(hasChange, segwit bool, numInputs int) uint64 {
  4986  		baseSize := dexbtc.MinimumTxOverhead
  4987  
  4988  		var inputSize, witnessSize, outputSize int
  4989  		if segwit {
  4990  			witnessSize = dexbtc.RedeemP2WPKHInputWitnessWeight*numInputs + 2
  4991  			inputSize = dexbtc.RedeemP2WPKHInputSize * numInputs
  4992  			outputSize = dexbtc.P2WPKHOutputSize
  4993  		} else {
  4994  			inputSize = numInputs * dexbtc.RedeemP2PKHInputSize
  4995  			outputSize = dexbtc.P2PKHOutputSize
  4996  		}
  4997  
  4998  		baseSize += inputSize
  4999  		if hasChange {
  5000  			baseSize += outputSize
  5001  		}
  5002  
  5003  		txWeight := baseSize*4 + witnessSize
  5004  		txSize := (txWeight + 3) / 4
  5005  		return uint64(txSize)
  5006  	}
  5007  
  5008  	var changeAmount int64 = 21350
  5009  	var newFeeRate uint64 = 50
  5010  	fees := []float64{0.00002, 0.000005, 0.00001, 0.00001}
  5011  	confs := []uint64{0, 0, 0, 0}
  5012  	_, _, _, txs := getAccelerationParams(changeAmount, false, false, fees, node)
  5013  	expectedFees := (sumTxSizes(txs, confs)+calculateChangeTxSize(false, segwit, 1))*newFeeRate - sumFees(fees, confs)
  5014  	_, _, _, txs = getAccelerationParams(changeAmount, true, false, fees, node)
  5015  	expectedFeesWithChange := (sumTxSizes(txs, confs)+calculateChangeTxSize(true, segwit, 1))*newFeeRate - sumFees(fees, confs)
  5016  
  5017  	// See dexbtc.IsDust for the source of this dustCoverage voodoo.
  5018  	var dustCoverage uint64
  5019  	if segwit {
  5020  		dustCoverage = (dexbtc.P2WPKHOutputSize + 41 + (107 / 4)) * 3 * newFeeRate
  5021  	} else {
  5022  		dustCoverage = (dexbtc.P2PKHOutputSize + 41 + 107) * 3 * newFeeRate
  5023  	}
  5024  
  5025  	type utxo struct {
  5026  		amount uint64
  5027  		confs  uint32
  5028  	}
  5029  
  5030  	tests := []struct {
  5031  		name                      string
  5032  		changeNotInUnspent        bool
  5033  		addPreviousAcceleration   bool
  5034  		numUtxosUsed              int
  5035  		changeAmount              int64
  5036  		lockChange                bool
  5037  		scrambleSwapCoins         bool
  5038  		expectChange              bool
  5039  		utxos                     []utxo
  5040  		fees                      []float64
  5041  		confs                     []uint64
  5042  		requiredForRemainingSwaps uint64
  5043  		expectChangeLocked        bool
  5044  		txTimeWithinLimit         bool
  5045  
  5046  		// needed to test AccelerateOrder and AccelerationEstimate
  5047  		expectAccelerateOrderErr      bool
  5048  		expectAccelerationEstimateErr bool
  5049  		newFeeRate                    uint64
  5050  
  5051  		// needed to test PreAccelerate
  5052  		suggestedFeeRate       uint64
  5053  		expectPreAccelerateErr bool
  5054  		expectTooEarly         bool
  5055  	}{
  5056  		{
  5057  			name:                          "change not in utxo set",
  5058  			changeAmount:                  int64(expectedFees),
  5059  			fees:                          []float64{0.00002, 0.000005, 0.00001, 0.00001},
  5060  			confs:                         []uint64{0, 0, 0, 0},
  5061  			newFeeRate:                    50,
  5062  			expectAccelerationEstimateErr: true,
  5063  			expectAccelerateOrderErr:      true,
  5064  			expectPreAccelerateErr:        true,
  5065  			changeNotInUnspent:            true,
  5066  		},
  5067  		{
  5068  			name:             "just enough without change",
  5069  			changeAmount:     int64(expectedFees),
  5070  			fees:             []float64{0.00002, 0.000005, 0.00001, 0.00001},
  5071  			confs:            confs,
  5072  			newFeeRate:       newFeeRate,
  5073  			suggestedFeeRate: 30,
  5074  		},
  5075  		{
  5076  			name:                    "works with acceleration",
  5077  			changeAmount:            int64(expectedFees),
  5078  			fees:                    []float64{0.00002, 0.000005, 0.00001, 0.00001},
  5079  			confs:                   confs,
  5080  			newFeeRate:              newFeeRate,
  5081  			addPreviousAcceleration: true,
  5082  		},
  5083  		{
  5084  			name:              "scramble swap coins",
  5085  			changeAmount:      int64(expectedFees),
  5086  			fees:              []float64{0.00002, 0.000005, 0.00001, 0.00001},
  5087  			confs:             []uint64{0, 0, 0, 0},
  5088  			newFeeRate:        newFeeRate,
  5089  			scrambleSwapCoins: true,
  5090  		},
  5091  		{
  5092  			name:                          "not enough with just change",
  5093  			changeAmount:                  int64(expectedFees - 1),
  5094  			fees:                          []float64{0.00002, 0.000005, 0.00001, 0.00001},
  5095  			confs:                         []uint64{0, 0, 0, 0},
  5096  			newFeeRate:                    newFeeRate,
  5097  			expectAccelerationEstimateErr: true,
  5098  			expectAccelerateOrderErr:      true,
  5099  		},
  5100  		{
  5101  			name:         "add greater than dust amount to change",
  5102  			changeAmount: int64(expectedFeesWithChange + dustCoverage),
  5103  			fees:         []float64{0.00002, 0.000005, 0.00001, 0.00001},
  5104  			confs:        []uint64{0, 0, 0, 0},
  5105  			newFeeRate:   newFeeRate,
  5106  			expectChange: true,
  5107  		},
  5108  		{
  5109  			name:         "add dust amount to change",
  5110  			changeAmount: int64(expectedFeesWithChange + dustCoverage - 1),
  5111  			fees:         []float64{0.00002, 0.000005, 0.00001, 0.00001},
  5112  			confs:        []uint64{0, 0, 0, 0},
  5113  			newFeeRate:   newFeeRate,
  5114  			expectChange: false,
  5115  		},
  5116  		{
  5117  			name:         "don't accelerate confirmed transactions",
  5118  			changeAmount: int64(expectedFeesWithChange + dustCoverage),
  5119  			fees:         []float64{0.00002, 0.000005, 0.00001, 0.00001},
  5120  			confs:        []uint64{1, 1, 0, 0},
  5121  			newFeeRate:   newFeeRate,
  5122  			expectChange: true,
  5123  		},
  5124  		{
  5125  			name:                          "not enough",
  5126  			changeAmount:                  int64(expectedFees - 1),
  5127  			fees:                          []float64{0.00002, 0.000005, 0.00001, 0.00001},
  5128  			confs:                         []uint64{0, 0, 0, 0},
  5129  			newFeeRate:                    newFeeRate,
  5130  			expectAccelerationEstimateErr: true,
  5131  			expectAccelerateOrderErr:      true,
  5132  		},
  5133  		{
  5134  			name:                          "not enough with 0-conf utxo in wallet",
  5135  			changeAmount:                  int64(expectedFees - 1),
  5136  			fees:                          []float64{0.00002, 0.000005, 0.00001, 0.00001},
  5137  			confs:                         []uint64{0, 0, 0, 0},
  5138  			newFeeRate:                    newFeeRate,
  5139  			expectAccelerationEstimateErr: true,
  5140  			expectAccelerateOrderErr:      true,
  5141  			utxos: []utxo{{
  5142  				confs:  0,
  5143  				amount: 5e9,
  5144  			}},
  5145  		},
  5146  		{
  5147  			name:         "enough with 1-conf utxo in wallet",
  5148  			changeAmount: int64(expectedFees - 1),
  5149  			fees:         []float64{0.00002, 0.000005, 0.00001, 0.00001},
  5150  			confs:        []uint64{0, 0, 0, 0},
  5151  			newFeeRate:   newFeeRate,
  5152  			numUtxosUsed: 1,
  5153  			expectChange: true,
  5154  			utxos: []utxo{{
  5155  				confs:  1,
  5156  				amount: 2e6,
  5157  			}},
  5158  		},
  5159  		{
  5160  			name:                          "not enough for remaining swaps",
  5161  			changeAmount:                  int64(expectedFees - 1),
  5162  			fees:                          fees,
  5163  			confs:                         []uint64{0, 0, 0, 0},
  5164  			newFeeRate:                    newFeeRate,
  5165  			numUtxosUsed:                  1,
  5166  			expectAccelerationEstimateErr: true,
  5167  			expectAccelerateOrderErr:      true,
  5168  			expectChange:                  true,
  5169  			requiredForRemainingSwaps:     2e6,
  5170  			utxos: []utxo{{
  5171  				confs:  1,
  5172  				amount: 2e6,
  5173  			}},
  5174  		},
  5175  		{
  5176  			name:                      "enough for remaining swaps",
  5177  			changeAmount:              int64(expectedFees - 1),
  5178  			fees:                      fees,
  5179  			confs:                     []uint64{0, 0, 0, 0},
  5180  			newFeeRate:                newFeeRate,
  5181  			numUtxosUsed:              2,
  5182  			expectChange:              true,
  5183  			expectChangeLocked:        true,
  5184  			requiredForRemainingSwaps: 2e6,
  5185  			utxos: []utxo{
  5186  				{
  5187  					confs:  1,
  5188  					amount: 2e6,
  5189  				},
  5190  				{
  5191  					confs:  1,
  5192  					amount: 1e6,
  5193  				},
  5194  			},
  5195  		},
  5196  		{
  5197  			name:                      "locked change, required for remaining >0",
  5198  			changeAmount:              int64(expectedFees - 1),
  5199  			fees:                      fees,
  5200  			confs:                     []uint64{0, 0, 0, 0},
  5201  			newFeeRate:                newFeeRate,
  5202  			numUtxosUsed:              2,
  5203  			expectChange:              true,
  5204  			expectChangeLocked:        true,
  5205  			requiredForRemainingSwaps: 2e6,
  5206  			lockChange:                true,
  5207  			utxos: []utxo{
  5208  				{
  5209  					confs:  1,
  5210  					amount: 2e6,
  5211  				},
  5212  				{
  5213  					confs:  1,
  5214  					amount: 1e6,
  5215  				},
  5216  			},
  5217  		},
  5218  		{
  5219  			name:                          "locked change, required for remaining == 0",
  5220  			changeAmount:                  int64(expectedFees - 1),
  5221  			fees:                          []float64{0.00002, 0.000005, 0.00001, 0.00001},
  5222  			confs:                         []uint64{0, 0, 0, 0},
  5223  			newFeeRate:                    newFeeRate,
  5224  			numUtxosUsed:                  2,
  5225  			expectChange:                  true,
  5226  			expectAccelerationEstimateErr: true,
  5227  			expectAccelerateOrderErr:      true,
  5228  			expectPreAccelerateErr:        true,
  5229  			lockChange:                    true,
  5230  			utxos: []utxo{
  5231  				{
  5232  					confs:  1,
  5233  					amount: 2e6,
  5234  				},
  5235  				{
  5236  					confs:  1,
  5237  					amount: 1e6,
  5238  				},
  5239  			},
  5240  		},
  5241  		{
  5242  			name:              "tx time within limit",
  5243  			txTimeWithinLimit: true,
  5244  			expectTooEarly:    true,
  5245  			changeAmount:      int64(expectedFees),
  5246  			fees:              []float64{0.00002, 0.000005, 0.00001, 0.00001},
  5247  			confs:             confs,
  5248  			newFeeRate:        newFeeRate,
  5249  			suggestedFeeRate:  30,
  5250  		},
  5251  	}
  5252  
  5253  	for _, test := range tests {
  5254  		node.listUnspent = []*ListUnspentResult{}
  5255  		swapCoins, accelerations, changeCoin, txs := getAccelerationParams(test.changeAmount, !test.changeNotInUnspent, test.addPreviousAcceleration, test.fees, node)
  5256  		expectedFees := (sumTxSizes(txs, test.confs)+calculateChangeTxSize(test.expectChange, segwit, 1+test.numUtxosUsed))*test.newFeeRate - sumFees(test.fees, test.confs)
  5257  		if !test.expectChange {
  5258  			expectedFees = uint64(test.changeAmount)
  5259  		}
  5260  		if test.scrambleSwapCoins {
  5261  			temp := swapCoins[0]
  5262  			swapCoins[0] = swapCoins[2]
  5263  			swapCoins[2] = swapCoins[1]
  5264  			swapCoins[1] = temp
  5265  		}
  5266  		if test.lockChange {
  5267  			changeTxID, changeVout, _ := decodeCoinID(changeCoin)
  5268  			node.listLockUnspent = []*RPCOutpoint{
  5269  				{
  5270  					TxID: changeTxID.String(),
  5271  					Vout: changeVout,
  5272  				},
  5273  			}
  5274  		}
  5275  		for _, utxo := range test.utxos {
  5276  			addUTXOToNode(utxo.confs, segwit, utxo.amount, node)
  5277  		}
  5278  
  5279  		loadTxsIntoNode(txs, test.fees, test.confs, node, test.txTimeWithinLimit, t)
  5280  
  5281  		testAccelerateOrder := func() {
  5282  			change, txID, err := wallet.AccelerateOrder(swapCoins, accelerations, changeCoin, test.requiredForRemainingSwaps, test.newFeeRate)
  5283  			if test.expectAccelerateOrderErr {
  5284  				if err == nil {
  5285  					t.Fatalf("%s: expected AccelerateOrder error but did not get", test.name)
  5286  				}
  5287  				return
  5288  			}
  5289  			if err != nil {
  5290  				t.Fatalf("%s: unexpected error: %v", test.name, err)
  5291  			}
  5292  
  5293  			in, out := totalInputOutput(node.sentRawTx)
  5294  			lastTxFee := in - out
  5295  			if expectedFees != lastTxFee {
  5296  				t.Fatalf("%s: expected fee to be %d but got %d", test.name, expectedFees, lastTxFee)
  5297  			}
  5298  
  5299  			if node.sentRawTx.TxHash().String() != txID {
  5300  				t.Fatalf("%s: expected tx id %s, but got %s", test.name, node.sentRawTx.TxHash().String(), txID)
  5301  			}
  5302  
  5303  			if test.expectChange {
  5304  				if out == 0 {
  5305  					t.Fatalf("%s: expected change but did not get", test.name)
  5306  				}
  5307  				if change == nil {
  5308  					t.Fatalf("%s: expected change, but got nil", test.name)
  5309  				}
  5310  
  5311  				changeScript := node.sentRawTx.TxOut[0].PkScript
  5312  
  5313  				changeAddr, err := btcutil.DecodeAddress(node.changeAddr, &chaincfg.MainNetParams)
  5314  				if err != nil {
  5315  					t.Fatalf("%s: unexpected error: %v", test.name, err)
  5316  				}
  5317  				expectedChangeScript, err := txscript.PayToAddrScript(changeAddr)
  5318  				if err != nil {
  5319  					t.Fatalf("%s: unexpected error: %v", test.name, err)
  5320  				}
  5321  
  5322  				if !bytes.Equal(changeScript, expectedChangeScript) {
  5323  					t.Fatalf("%s: expected change script != actual", test.name)
  5324  				}
  5325  
  5326  				changeTxHash, changeVout, err := decodeCoinID(change.ID())
  5327  				if err != nil {
  5328  					t.Fatalf("%s: unexpected error: %v", test.name, err)
  5329  				}
  5330  				if *changeTxHash != node.sentRawTx.TxHash() {
  5331  					t.Fatalf("%s: change tx hash %s != expected: %s", test.name, changeTxHash, node.sentRawTx.TxHash())
  5332  				}
  5333  				if changeVout != 0 {
  5334  					t.Fatalf("%s: change vout %v != expected: 0", test.name, changeVout)
  5335  				}
  5336  
  5337  				var changeLocked bool
  5338  				for _, coin := range node.lockedCoins {
  5339  					if changeVout == coin.Vout && changeTxHash.String() == coin.TxID {
  5340  						changeLocked = true
  5341  					}
  5342  				}
  5343  				if changeLocked != test.expectChangeLocked {
  5344  					t.Fatalf("%s: expected change locked = %v, but was %v", test.name, test.expectChangeLocked, changeLocked)
  5345  				}
  5346  			} else {
  5347  				if out > 0 {
  5348  					t.Fatalf("%s: not expecting change but got %v", test.name, out)
  5349  				}
  5350  				if change != nil {
  5351  					t.Fatalf("%s: not expecting change but accelerate returned: %+v", test.name, change)
  5352  				}
  5353  			}
  5354  
  5355  			if test.requiredForRemainingSwaps > out {
  5356  				t.Fatalf("%s: %d needed of remaining swaps, but output was only %d", test.name, test.requiredForRemainingSwaps, out)
  5357  			}
  5358  		}
  5359  		testAccelerateOrder()
  5360  
  5361  		testAccelerationEstimate := func() {
  5362  			estimate, err := wallet.AccelerationEstimate(swapCoins, accelerations, changeCoin, test.requiredForRemainingSwaps, test.newFeeRate)
  5363  			if test.expectAccelerationEstimateErr {
  5364  				if err == nil {
  5365  					t.Fatalf("%s: expected AccelerationEstimate error but did not get", test.name)
  5366  				}
  5367  				return
  5368  			}
  5369  			if err != nil {
  5370  				t.Fatalf("%s: unexpected error: %v", test.name, err)
  5371  			}
  5372  			if estimate != expectedFees {
  5373  				t.Fatalf("%s: estimate %v != expected fees %v", test.name, estimate, expectedFees)
  5374  			}
  5375  		}
  5376  		testAccelerationEstimate()
  5377  
  5378  		testPreAccelerate := func() {
  5379  			currentRate, suggestedRange, earlyAcceleration, err := wallet.PreAccelerate(swapCoins, accelerations, changeCoin, test.requiredForRemainingSwaps, test.suggestedFeeRate)
  5380  			if test.expectPreAccelerateErr {
  5381  				if err == nil {
  5382  					t.Fatalf("%s: expected PreAccelerate error but did not get", test.name)
  5383  				}
  5384  				return
  5385  			}
  5386  			if err != nil {
  5387  				t.Fatalf("%s: unexpected error: %v", test.name, err)
  5388  			}
  5389  
  5390  			if test.expectTooEarly != (earlyAcceleration != nil) {
  5391  				t.Fatalf("%s: expected early acceleration %v, but got %v", test.name, test.expectTooEarly, earlyAcceleration)
  5392  			}
  5393  
  5394  			var totalSize, totalFee uint64
  5395  			for i, tx := range txs {
  5396  				if test.confs[i] == 0 {
  5397  					totalSize += dexbtc.MsgTxVBytes(tx)
  5398  					totalFee += toSatoshi(test.fees[i])
  5399  				}
  5400  			}
  5401  
  5402  			expectedRate := totalFee / totalSize
  5403  			if expectedRate != currentRate {
  5404  				t.Fatalf("%s: expected current rate %v != actual %v", test.name, expectedRate, currentRate)
  5405  			}
  5406  
  5407  			totalFeePossible := totalFee + uint64(test.changeAmount) - test.requiredForRemainingSwaps
  5408  			var numConfirmedUTXO int
  5409  			for _, utxo := range test.utxos {
  5410  				if utxo.confs > 0 {
  5411  					totalFeePossible += utxo.amount
  5412  					numConfirmedUTXO++
  5413  				}
  5414  			}
  5415  			totalSize += calculateChangeTxSize(test.expectChange, segwit, 1+numConfirmedUTXO)
  5416  			maxRatePossible := totalFeePossible / totalSize
  5417  
  5418  			expectedRangeHigh := expectedRate * 5
  5419  			if test.suggestedFeeRate > expectedRate {
  5420  				expectedRangeHigh = test.suggestedFeeRate * 5
  5421  			}
  5422  			if maxRatePossible < expectedRangeHigh {
  5423  				expectedRangeHigh = maxRatePossible
  5424  			}
  5425  
  5426  			expectedRangeLowX := float64(expectedRate+1) / float64(expectedRate)
  5427  			if suggestedRange.Start.X != expectedRangeLowX {
  5428  				t.Fatalf("%s: start of range should be %v on X, got: %v", test.name, expectedRangeLowX, suggestedRange.Start.X)
  5429  			}
  5430  
  5431  			if suggestedRange.Start.Y != float64(expectedRate+1) {
  5432  				t.Fatalf("%s: expected start of range on Y to be: %v, but got %v", test.name, float64(expectedRate), suggestedRange.Start.Y)
  5433  			}
  5434  
  5435  			if suggestedRange.End.Y != float64(expectedRangeHigh) {
  5436  				t.Fatalf("%s: expected end of range on Y to be: %v, but got %v", test.name, float64(expectedRangeHigh), suggestedRange.End.Y)
  5437  			}
  5438  
  5439  			expectedRangeHighX := float64(expectedRangeHigh) / float64(expectedRate)
  5440  			if suggestedRange.End.X != expectedRangeHighX {
  5441  				t.Fatalf("%s: expected end of range on X to be: %v, but got %v", test.name, float64(expectedRate), suggestedRange.End.X)
  5442  			}
  5443  		}
  5444  		testPreAccelerate()
  5445  	}
  5446  }
  5447  
  5448  func TestGetTxFee(t *testing.T) {
  5449  	runRubric(t, testGetTxFee)
  5450  }
  5451  
  5452  func testGetTxFee(t *testing.T, segwit bool, walletType string) {
  5453  	w, node, shutdown := tNewWallet(segwit, walletType)
  5454  	defer shutdown()
  5455  
  5456  	inputTx := &wire.MsgTx{
  5457  		TxIn: []*wire.TxIn{{}},
  5458  		TxOut: []*wire.TxOut{
  5459  			{
  5460  				Value: 2e6,
  5461  			},
  5462  			{
  5463  				Value: 3e6,
  5464  			},
  5465  		},
  5466  	}
  5467  	txBytes, err := serializeMsgTx(inputTx)
  5468  	if err != nil {
  5469  		t.Fatalf("unexpected error: %v", err)
  5470  	}
  5471  
  5472  	node.getTransactionMap = map[string]*GetTransactionResult{
  5473  		"any": {
  5474  			Bytes: txBytes,
  5475  		},
  5476  	}
  5477  
  5478  	tests := []struct {
  5479  		name        string
  5480  		tx          *wire.MsgTx
  5481  		expectErr   bool
  5482  		expectedFee uint64
  5483  		getTxErr    bool
  5484  	}{
  5485  		{
  5486  			name: "ok",
  5487  			tx: &wire.MsgTx{
  5488  				TxIn: []*wire.TxIn{{
  5489  					PreviousOutPoint: wire.OutPoint{
  5490  						Hash:  inputTx.TxHash(),
  5491  						Index: 0,
  5492  					}},
  5493  					{PreviousOutPoint: wire.OutPoint{
  5494  						Hash:  inputTx.TxHash(),
  5495  						Index: 1,
  5496  					}},
  5497  				},
  5498  				TxOut: []*wire.TxOut{{
  5499  					Value: 4e6,
  5500  				}},
  5501  			},
  5502  			expectedFee: 1e6,
  5503  		},
  5504  		{
  5505  			name: "get transaction error",
  5506  			tx: &wire.MsgTx{
  5507  				TxIn: []*wire.TxIn{{
  5508  					PreviousOutPoint: wire.OutPoint{
  5509  						Hash:  inputTx.TxHash(),
  5510  						Index: 0,
  5511  					}},
  5512  					{PreviousOutPoint: wire.OutPoint{
  5513  						Hash:  inputTx.TxHash(),
  5514  						Index: 1,
  5515  					}},
  5516  				},
  5517  				TxOut: []*wire.TxOut{{
  5518  					Value: 4e6,
  5519  				}},
  5520  			},
  5521  			getTxErr:  true,
  5522  			expectErr: true,
  5523  		},
  5524  		{
  5525  			name: "invalid prev output index error",
  5526  			tx: &wire.MsgTx{
  5527  				TxIn: []*wire.TxIn{{
  5528  					PreviousOutPoint: wire.OutPoint{
  5529  						Hash:  inputTx.TxHash(),
  5530  						Index: 0,
  5531  					}},
  5532  					{PreviousOutPoint: wire.OutPoint{
  5533  						Hash:  inputTx.TxHash(),
  5534  						Index: 2,
  5535  					}},
  5536  				},
  5537  				TxOut: []*wire.TxOut{{
  5538  					Value: 4e6,
  5539  				}},
  5540  			},
  5541  			expectErr: true,
  5542  		},
  5543  		{
  5544  			name: "tx out > in error",
  5545  			tx: &wire.MsgTx{
  5546  				TxIn: []*wire.TxIn{{
  5547  					PreviousOutPoint: wire.OutPoint{
  5548  						Hash:  inputTx.TxHash(),
  5549  						Index: 0,
  5550  					}},
  5551  					{PreviousOutPoint: wire.OutPoint{
  5552  						Hash:  inputTx.TxHash(),
  5553  						Index: 2,
  5554  					}},
  5555  				},
  5556  				TxOut: []*wire.TxOut{{
  5557  					Value: 8e6,
  5558  				}},
  5559  			},
  5560  			expectErr: true,
  5561  		},
  5562  	}
  5563  
  5564  	for _, test := range tests {
  5565  		node.getTransactionErr = nil
  5566  		if test.getTxErr {
  5567  			node.getTransactionErr = errors.New("")
  5568  		}
  5569  
  5570  		fee, err := w.getTxFee(test.tx)
  5571  		if test.expectErr {
  5572  			if err == nil {
  5573  				t.Fatalf("%s: expected error but did not get", test.name)
  5574  			}
  5575  			continue
  5576  		}
  5577  		if err != nil {
  5578  			t.Fatalf("%s: unexpected error: %v", test.name, err)
  5579  		}
  5580  		if fee != test.expectedFee {
  5581  			t.Fatalf("%s: expected fee %d, but got %d", test.name, test.expectedFee, fee)
  5582  		}
  5583  	}
  5584  }
  5585  
  5586  func TestTooEarlyToAccelerate(t *testing.T) {
  5587  	type tx struct {
  5588  		secondsBeforeNow uint64
  5589  		confirmations    uint64
  5590  	}
  5591  	tests := []struct {
  5592  		name           string
  5593  		accelerations  []tx
  5594  		swaps          []tx
  5595  		expectedReturn *asset.EarlyAcceleration
  5596  		expectError    bool
  5597  	}{
  5598  		{
  5599  			name: "all confirmed",
  5600  			accelerations: []tx{
  5601  				{secondsBeforeNow: minTimeBeforeAcceleration + 800,
  5602  					confirmations: 2},
  5603  				{secondsBeforeNow: minTimeBeforeAcceleration + 300,
  5604  					confirmations: 1}},
  5605  			swaps: []tx{
  5606  				{secondsBeforeNow: minTimeBeforeAcceleration + 1000,
  5607  					confirmations: 2},
  5608  				{secondsBeforeNow: minTimeBeforeAcceleration + 500,
  5609  					confirmations: 1}},
  5610  			expectError: true,
  5611  		},
  5612  		{
  5613  			name:          "no accelerations, not too early",
  5614  			accelerations: []tx{},
  5615  			swaps: []tx{
  5616  				{secondsBeforeNow: minTimeBeforeAcceleration + 1000,
  5617  					confirmations: 2},
  5618  				{secondsBeforeNow: minTimeBeforeAcceleration + 500,
  5619  					confirmations: 0},
  5620  				{secondsBeforeNow: minTimeBeforeAcceleration + 300,
  5621  					confirmations: 0},
  5622  				{secondsBeforeNow: minTimeBeforeAcceleration + 800,
  5623  					confirmations: 2}},
  5624  		},
  5625  		{
  5626  			name: "acceleration after unconfirmed swap, not too early",
  5627  			accelerations: []tx{
  5628  				{secondsBeforeNow: minTimeBeforeAcceleration + 300,
  5629  					confirmations: 0}},
  5630  			swaps: []tx{
  5631  				{secondsBeforeNow: minTimeBeforeAcceleration + 1000,
  5632  					confirmations: 2},
  5633  				{secondsBeforeNow: minTimeBeforeAcceleration + 800,
  5634  					confirmations: 2},
  5635  				{secondsBeforeNow: minTimeBeforeAcceleration + 500,
  5636  					confirmations: 0}},
  5637  		},
  5638  		{
  5639  			name: "acceleration after unconfirmed swap, too early",
  5640  			accelerations: []tx{
  5641  				{secondsBeforeNow: minTimeBeforeAcceleration - 300,
  5642  					confirmations: 0}},
  5643  			swaps: []tx{
  5644  				{secondsBeforeNow: minTimeBeforeAcceleration + 1000,
  5645  					confirmations: 2},
  5646  				{secondsBeforeNow: minTimeBeforeAcceleration + 800,
  5647  					confirmations: 2},
  5648  				{secondsBeforeNow: minTimeBeforeAcceleration + 500,
  5649  					confirmations: 0}},
  5650  			expectedReturn: &asset.EarlyAcceleration{
  5651  				TimePast:       minTimeBeforeAcceleration - 300,
  5652  				WasAccelerated: true,
  5653  			},
  5654  		},
  5655  		{
  5656  			name:          "no accelerations, too early",
  5657  			accelerations: []tx{},
  5658  			swaps: []tx{
  5659  				{secondsBeforeNow: minTimeBeforeAcceleration + 1000,
  5660  					confirmations: 2},
  5661  				{secondsBeforeNow: minTimeBeforeAcceleration + 800,
  5662  					confirmations: 2},
  5663  				{secondsBeforeNow: minTimeBeforeAcceleration - 300,
  5664  					confirmations: 0},
  5665  				{secondsBeforeNow: minTimeBeforeAcceleration - 500,
  5666  					confirmations: 0}},
  5667  			expectedReturn: &asset.EarlyAcceleration{
  5668  				TimePast:       minTimeBeforeAcceleration - 300,
  5669  				WasAccelerated: false,
  5670  			},
  5671  		},
  5672  		{
  5673  			name: "only accelerations are unconfirmed, too early",
  5674  			accelerations: []tx{
  5675  				{secondsBeforeNow: minTimeBeforeAcceleration - 300,
  5676  					confirmations: 0}},
  5677  			swaps: []tx{
  5678  				{secondsBeforeNow: minTimeBeforeAcceleration + 1000,
  5679  					confirmations: 2},
  5680  				{secondsBeforeNow: minTimeBeforeAcceleration + 800,
  5681  					confirmations: 2},
  5682  			},
  5683  			expectedReturn: &asset.EarlyAcceleration{
  5684  				TimePast:       minTimeBeforeAcceleration - 300,
  5685  				WasAccelerated: true,
  5686  			},
  5687  		},
  5688  	}
  5689  
  5690  	for _, test := range tests {
  5691  		swapTxs := make([]*GetTransactionResult, 0, len(test.swaps))
  5692  		accelerationTxs := make([]*GetTransactionResult, 0, len(test.accelerations))
  5693  		now := time.Now().Unix()
  5694  
  5695  		for _, swap := range test.swaps {
  5696  			var txHash chainhash.Hash
  5697  			copy(txHash[:], encode.RandomBytes(32))
  5698  			swapTxs = append(swapTxs, &GetTransactionResult{
  5699  				TxID:          txHash.String(),
  5700  				Confirmations: swap.confirmations,
  5701  				Time:          uint64(now) - swap.secondsBeforeNow,
  5702  			})
  5703  		}
  5704  
  5705  		for _, acceleration := range test.accelerations {
  5706  			var txHash chainhash.Hash
  5707  			copy(txHash[:], encode.RandomBytes(32))
  5708  			accelerationTxs = append(accelerationTxs, &GetTransactionResult{
  5709  				TxID:          txHash.String(),
  5710  				Confirmations: acceleration.confirmations,
  5711  				Time:          uint64(now) - acceleration.secondsBeforeNow,
  5712  			})
  5713  		}
  5714  
  5715  		earlyAcceleration, err := tooEarlyToAccelerate(swapTxs, accelerationTxs)
  5716  		if test.expectError {
  5717  			if err == nil {
  5718  				t.Fatalf("%s: expected error but did not get", test.name)
  5719  			}
  5720  			continue
  5721  		}
  5722  		if err != nil {
  5723  			t.Fatalf("%s: unexpected error: %v", test.name, err)
  5724  		}
  5725  
  5726  		if test.expectedReturn == nil && earlyAcceleration == nil {
  5727  			continue
  5728  		}
  5729  		if test.expectedReturn == nil && earlyAcceleration != nil {
  5730  			t.Fatalf("%s: expected return to be nil, but got %+v", test.name, earlyAcceleration)
  5731  		}
  5732  		if test.expectedReturn != nil && earlyAcceleration == nil {
  5733  			t.Fatalf("%s: expected return to not be nil, but got nil", test.name)
  5734  		}
  5735  		if test.expectedReturn.TimePast != earlyAcceleration.TimePast ||
  5736  			test.expectedReturn.WasAccelerated != earlyAcceleration.WasAccelerated {
  5737  			t.Fatalf("%s: expected %+v, got %+v", test.name, test.expectedReturn, earlyAcceleration)
  5738  		}
  5739  	}
  5740  }
  5741  
  5742  type tReconfigurer struct {
  5743  	*rpcClient
  5744  	restart bool
  5745  	err     error
  5746  }
  5747  
  5748  func (r *tReconfigurer) reconfigure(walletCfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error) {
  5749  	return r.restart, r.err
  5750  }
  5751  
  5752  func TestReconfigure(t *testing.T) {
  5753  	wallet, _, shutdown := tNewWallet(false, walletTypeRPC)
  5754  	// We don't need the wallet running, and we need to write to the node field,
  5755  	// which is used in the block loop.
  5756  	shutdown()
  5757  
  5758  	reconfigurer := &tReconfigurer{rpcClient: wallet.node.(*rpcClient)}
  5759  	wallet.baseWallet.setNode(reconfigurer)
  5760  
  5761  	cfg := &asset.WalletConfig{
  5762  		Settings: map[string]string{
  5763  			"redeemconftarget": "3",
  5764  		},
  5765  	}
  5766  
  5767  	restart, err := wallet.Reconfigure(tCtx, cfg, "")
  5768  	if err != nil {
  5769  		t.Fatalf("initial Reconfigure error: %v", err)
  5770  	}
  5771  	if restart {
  5772  		t.Fatal("restart = false not propagated")
  5773  	}
  5774  	if wallet.redeemConfTarget() != 3 {
  5775  		t.Fatal("redeemconftarget not updated", wallet.redeemConfTarget())
  5776  	}
  5777  
  5778  	cfg.Settings["redeemconftarget"] = "2"
  5779  
  5780  	reconfigurer.err = tErr
  5781  	if _, err = wallet.Reconfigure(tCtx, cfg, ""); err == nil {
  5782  		t.Fatal("node reconfigure error not propagated")
  5783  	}
  5784  	reconfigurer.err = nil
  5785  	// Redeem target should be unchanged
  5786  	if wallet.redeemConfTarget() != 3 {
  5787  		t.Fatal("redeemconftarget updated for node reconfigure error", wallet.redeemConfTarget())
  5788  	}
  5789  
  5790  	reconfigurer.restart = true
  5791  	if restart, err := wallet.Reconfigure(tCtx, cfg, ""); err != nil {
  5792  		t.Fatal("Reconfigure error for restart = true")
  5793  	} else if !restart {
  5794  		t.Fatal("restart = true not propagated")
  5795  	}
  5796  	reconfigurer.restart = false
  5797  
  5798  	// One last success, and make sure baseWalletConfig is updated.
  5799  	if _, err := wallet.Reconfigure(tCtx, cfg, ""); err != nil {
  5800  		t.Fatalf("Reconfigure error for final success: %v", err)
  5801  	}
  5802  	// Redeem target should be changed now.
  5803  	if wallet.redeemConfTarget() != 2 {
  5804  		t.Fatal("redeemconftarget not updated in final success", wallet.redeemConfTarget())
  5805  	}
  5806  }
  5807  
  5808  func TestConfirmRedemption(t *testing.T) {
  5809  	segwit := true
  5810  	wallet, node, shutdown := tNewWallet(segwit, walletTypeRPC)
  5811  	defer shutdown()
  5812  
  5813  	swapVal := toSatoshi(5)
  5814  
  5815  	secret, _, _, contract, addr, _, lockTime := makeSwapContract(segwit, time.Hour*12)
  5816  
  5817  	coin := NewOutput(tTxHash, 0, swapVal)
  5818  	ci := &asset.AuditInfo{
  5819  		Coin:       coin,
  5820  		Contract:   contract,
  5821  		Recipient:  addr.String(),
  5822  		Expiration: lockTime,
  5823  	}
  5824  
  5825  	redemption := &asset.Redemption{
  5826  		Spends: ci,
  5827  		Secret: secret,
  5828  	}
  5829  
  5830  	coinID := coin.ID()
  5831  
  5832  	privBytes, _ := hex.DecodeString("b07209eec1a8fb6cfe5cb6ace36567406971a75c330db7101fb21bc679bc5330")
  5833  	privKey, _ := btcec.PrivKeyFromBytes(privBytes)
  5834  	wif, err := btcutil.NewWIF(privKey, &chaincfg.MainNetParams, true)
  5835  	if err != nil {
  5836  		t.Fatalf("error encoding wif: %v", err)
  5837  	}
  5838  
  5839  	node.newAddress = tP2WPKHAddr
  5840  	node.privKeyForAddr = wif
  5841  
  5842  	tests := []struct {
  5843  		name                 string
  5844  		redemption           *asset.Redemption
  5845  		coinID               []byte
  5846  		wantErr              bool
  5847  		wantConfs            uint64
  5848  		txOutRes             *btcjson.GetTxOutResult
  5849  		getTransactionResult *GetTransactionResult
  5850  		txOutErr             error
  5851  		getTransactionErr    error
  5852  	}{{
  5853  		name:                 "ok and found",
  5854  		coinID:               coinID,
  5855  		redemption:           redemption,
  5856  		getTransactionResult: new(GetTransactionResult),
  5857  	}, {
  5858  		name:       "ok spent by someone but not sure who",
  5859  		coinID:     coinID,
  5860  		redemption: redemption,
  5861  		wantConfs:  requiredRedeemConfirms,
  5862  	}, {
  5863  		name:       "ok but sending new tx",
  5864  		coinID:     coinID,
  5865  		redemption: redemption,
  5866  		txOutRes:   new(btcjson.GetTxOutResult),
  5867  	}, {
  5868  		name:       "decode coin error",
  5869  		redemption: redemption,
  5870  		wantErr:    true,
  5871  	}, {
  5872  		name:       "error finding contract output",
  5873  		coinID:     coinID,
  5874  		redemption: redemption,
  5875  		txOutErr:   errors.New(""),
  5876  		wantErr:    true,
  5877  	}, {
  5878  		name:              "error finding redeem tx",
  5879  		coinID:            coinID,
  5880  		redemption:        redemption,
  5881  		getTransactionErr: errors.New(""),
  5882  		wantErr:           true,
  5883  	}, {
  5884  		name:   "redemption error",
  5885  		coinID: coinID,
  5886  		redemption: func() *asset.Redemption {
  5887  			ci := &asset.AuditInfo{
  5888  				Coin: coin,
  5889  				// Contract:   contract,
  5890  				Recipient:  addr.String(),
  5891  				Expiration: lockTime,
  5892  			}
  5893  
  5894  			return &asset.Redemption{
  5895  				Spends: ci,
  5896  				Secret: secret,
  5897  			}
  5898  		}(),
  5899  		txOutRes: new(btcjson.GetTxOutResult),
  5900  		wantErr:  true,
  5901  	}}
  5902  	for _, test := range tests {
  5903  		node.txOutRes = test.txOutRes
  5904  		node.txOutErr = test.txOutErr
  5905  		node.getTransactionErr = test.getTransactionErr
  5906  		node.getTransactionMap[tTxID] = test.getTransactionResult
  5907  
  5908  		status, err := wallet.ConfirmRedemption(test.coinID, test.redemption, 0)
  5909  		if test.wantErr {
  5910  			if err == nil {
  5911  				t.Fatalf("%q: expected error", test.name)
  5912  			}
  5913  			continue
  5914  		}
  5915  		if err != nil {
  5916  			t.Fatalf("%q: unexpected error: %v", test.name, err)
  5917  		}
  5918  		if status.Confs != test.wantConfs {
  5919  			t.Fatalf("%q: wanted %d confs but got %d", test.name, test.wantConfs, status.Confs)
  5920  		}
  5921  	}
  5922  }
  5923  
  5924  func TestAddressRecycling(t *testing.T) {
  5925  	w, td, shutdown := tNewWallet(false, walletTypeSPV)
  5926  	defer shutdown()
  5927  
  5928  	compareAddrLists := func(tag string, exp, actual []string) {
  5929  		if len(exp) != len(actual) {
  5930  			t.Fatalf("%s: Wrong number of recycled addrs. Expected %d, got %d", tag, len(exp), len(actual))
  5931  		}
  5932  		unfound := make(map[string]bool, len(exp))
  5933  		for _, addr := range exp {
  5934  			unfound[addr] = true
  5935  		}
  5936  		for _, addr := range actual {
  5937  			delete(unfound, addr)
  5938  		}
  5939  		if len(unfound) > 0 {
  5940  			t.Fatalf("%s: Wrong addresses stored. Expected %+v, got %+v", tag, exp, actual)
  5941  		}
  5942  	}
  5943  
  5944  	checkAddrs := func(tag string, expAddrs ...string) {
  5945  		memList := make([]string, 0, len(w.ar.addrs))
  5946  		for addr := range w.ar.addrs {
  5947  			memList = append(memList, addr)
  5948  		}
  5949  		compareAddrLists(tag, expAddrs, memList)
  5950  	}
  5951  
  5952  	addr1, _ := btcutil.NewAddressPubKeyHash(encode.RandomBytes(20), &chaincfg.MainNetParams)
  5953  	addr2, _ := btcutil.NewAddressPubKeyHash(encode.RandomBytes(20), &chaincfg.MainNetParams)
  5954  
  5955  	w.ReturnRedemptionAddress(addr1.String())
  5956  
  5957  	checkAddrs("first single addr", addr1.String())
  5958  
  5959  	td.ownsAddress = true
  5960  	redemptionAddr, _ := w.RedemptionAddress()
  5961  	if redemptionAddr != addr1.String() {
  5962  		t.Fatalf("recycled address not returned for redemption address")
  5963  	}
  5964  
  5965  	contracts := make([][]byte, 2)
  5966  	contracts[0], _ = dexbtc.MakeContract(addr1, addr2, encode.RandomBytes(32), time.Now().Unix(), false, &chaincfg.MainNetParams)
  5967  	contracts[1], _ = dexbtc.MakeContract(addr2, addr1, encode.RandomBytes(32), time.Now().Unix(), false, &chaincfg.MainNetParams)
  5968  
  5969  	w.ReturnRefundContracts(contracts)
  5970  
  5971  	checkAddrs("two addrs", addr1.String(), addr2.String())
  5972  
  5973  	// Check that unowned addresses are not returned
  5974  	td.ownsAddress = false
  5975  	td.newAddress = "1JoiKZz2QRd47ARtcYgvgxC9jhnre9aphv"
  5976  	priv, _ := btcec.NewPrivateKey()
  5977  	td.privKeyForAddr, _ = btcutil.NewWIF(priv, &chaincfg.MainNetParams, true)
  5978  	redemptionAddr, err := w.RedemptionAddress()
  5979  	if err != nil {
  5980  		t.Fatalf("RedemptionAddress error: %v", err)
  5981  	}
  5982  	if redemptionAddr == addr1.String() || redemptionAddr == addr2.String() {
  5983  		t.Fatalf("unowned address returned (1)")
  5984  	}
  5985  	redemptionAddr, _ = w.RedemptionAddress()
  5986  	if redemptionAddr == addr1.String() || redemptionAddr == addr2.String() {
  5987  		t.Fatalf("unowned address returned (2)")
  5988  	}
  5989  
  5990  	checkAddrs("after unowned")
  5991  
  5992  	// Check address loading.
  5993  	w.ReturnRefundContracts(contracts)
  5994  
  5995  	w.ar.WriteRecycledAddrsToFile()
  5996  	b, _ := os.ReadFile(w.ar.recyclePath)
  5997  	var fileAddrs []string
  5998  	for _, addr := range strings.Split(string(b), "\n") {
  5999  		if addr == "" {
  6000  			continue
  6001  		}
  6002  		fileAddrs = append(fileAddrs, addr)
  6003  	}
  6004  	compareAddrLists("filecheck", []string{addr1.String(), addr2.String()}, fileAddrs)
  6005  
  6006  	otherW, _ := newUnconnectedWallet(w.cloneParams, &WalletConfig{})
  6007  	if len(otherW.ar.addrs) != 2 {
  6008  		t.Fatalf("newly opened wallet didn't load recycled addrs")
  6009  	}
  6010  
  6011  }
  6012  
  6013  func TestFindBond(t *testing.T) {
  6014  	wallet, node, shutdown := tNewWallet(false, walletTypeRPC)
  6015  	defer shutdown()
  6016  
  6017  	node.signFunc = func(tx *wire.MsgTx) {
  6018  		signFunc(tx, 0, wallet.segwit)
  6019  	}
  6020  
  6021  	privBytes, _ := hex.DecodeString("b07209eec1a8fb6cfe5cb6ace36567406971a75c330db7101fb21bc679bc5330")
  6022  	bondKey, _ := btcec.PrivKeyFromBytes(privBytes)
  6023  
  6024  	amt := uint64(500_000)
  6025  	acctID := [32]byte{}
  6026  	lockTime := time.Now().Add(time.Hour * 12)
  6027  	utxo := &ListUnspentResult{
  6028  		TxID:          tTxID,
  6029  		Address:       tP2PKHAddr,
  6030  		Amount:        1.0,
  6031  		Confirmations: 1,
  6032  		Spendable:     true,
  6033  		ScriptPubKey:  decodeString("76a914e114d5bb20cdbd75f3726f27c10423eb1332576288ac"),
  6034  	}
  6035  	node.listUnspent = []*ListUnspentResult{utxo}
  6036  	node.changeAddr = tP2PKHAddr
  6037  	node.newAddress = tP2PKHAddr
  6038  
  6039  	bond, _, err := wallet.MakeBondTx(0, amt, 200, lockTime, bondKey, acctID[:])
  6040  	if err != nil {
  6041  		t.Fatal(err)
  6042  	}
  6043  
  6044  	txRes := func(tx []byte) *GetTransactionResult {
  6045  		return &GetTransactionResult{
  6046  			BlockHash: hex.EncodeToString(randBytes(32)),
  6047  			Bytes:     tx,
  6048  		}
  6049  	}
  6050  
  6051  	newBondTx := func() *wire.MsgTx {
  6052  		msgTx, err := msgTxFromBytes(bond.SignedTx)
  6053  		if err != nil {
  6054  			t.Fatal(err)
  6055  		}
  6056  		return msgTx
  6057  	}
  6058  	tooFewOutputs := newBondTx()
  6059  	tooFewOutputs.TxOut = tooFewOutputs.TxOut[2:]
  6060  	tooFewOutputsBytes, err := serializeMsgTx(tooFewOutputs)
  6061  	if err != nil {
  6062  		t.Fatal(err)
  6063  	}
  6064  
  6065  	badBondScript := newBondTx()
  6066  	badBondScript.TxOut[1].PkScript = badBondScript.TxOut[1].PkScript[1:]
  6067  	badBondScriptBytes, err := serializeMsgTx(badBondScript)
  6068  	if err != nil {
  6069  		t.Fatal(err)
  6070  	}
  6071  
  6072  	noBondMatch := newBondTx()
  6073  	noBondMatch.TxOut[0].PkScript = noBondMatch.TxOut[0].PkScript[1:]
  6074  	noBondMatchBytes, err := serializeMsgTx(noBondMatch)
  6075  	if err != nil {
  6076  		t.Fatal(err)
  6077  	}
  6078  
  6079  	node.addRawTx(1, newBondTx())
  6080  	verboseBlocks := node.verboseBlocks
  6081  	for _, blk := range verboseBlocks {
  6082  		blk.msgBlock.Header.Timestamp = time.Now()
  6083  	}
  6084  
  6085  	tests := []struct {
  6086  		name              string
  6087  		coinID            []byte
  6088  		txRes             *GetTransactionResult
  6089  		bestBlockErr      error
  6090  		getTransactionErr error
  6091  		verboseBlocks     map[chainhash.Hash]*msgBlockWithHeight
  6092  		searchUntil       time.Time
  6093  		wantErr           bool
  6094  	}{{
  6095  		name:   "ok",
  6096  		coinID: bond.CoinID,
  6097  		txRes:  txRes(bond.SignedTx),
  6098  	}, {
  6099  		name:              "ok with find blocks",
  6100  		coinID:            bond.CoinID,
  6101  		getTransactionErr: asset.CoinNotFoundError,
  6102  	}, {
  6103  		name:    "bad coin id",
  6104  		coinID:  make([]byte, 0),
  6105  		txRes:   txRes(bond.SignedTx),
  6106  		wantErr: true,
  6107  	}, {
  6108  		name:    "missing an output",
  6109  		coinID:  bond.CoinID,
  6110  		txRes:   txRes(tooFewOutputsBytes),
  6111  		wantErr: true,
  6112  	}, {
  6113  		name:    "bad bond commitment script",
  6114  		coinID:  bond.CoinID,
  6115  		txRes:   txRes(badBondScriptBytes),
  6116  		wantErr: true,
  6117  	}, {
  6118  		name:    "bond script does not match commitment",
  6119  		coinID:  bond.CoinID,
  6120  		txRes:   txRes(noBondMatchBytes),
  6121  		wantErr: true,
  6122  	}, {
  6123  		name:    "bad msgtx",
  6124  		coinID:  bond.CoinID,
  6125  		txRes:   txRes(bond.SignedTx[1:]),
  6126  		wantErr: true,
  6127  	}, {
  6128  		name:              "get best block error",
  6129  		coinID:            bond.CoinID,
  6130  		getTransactionErr: asset.CoinNotFoundError,
  6131  		bestBlockErr:      errors.New("some error"),
  6132  		wantErr:           true,
  6133  	}, {
  6134  		name:              "block not found",
  6135  		coinID:            bond.CoinID,
  6136  		getTransactionErr: asset.CoinNotFoundError,
  6137  		verboseBlocks:     map[chainhash.Hash]*msgBlockWithHeight{},
  6138  		wantErr:           true,
  6139  	}, {
  6140  		name:              "did not find by search until time",
  6141  		coinID:            bond.CoinID,
  6142  		getTransactionErr: asset.CoinNotFoundError,
  6143  		searchUntil:       time.Now().Add(time.Hour),
  6144  		wantErr:           true,
  6145  	}}
  6146  
  6147  	for _, test := range tests {
  6148  		t.Run(test.name, func(t *testing.T) {
  6149  			node.getTransactionMap["any"] = test.txRes
  6150  			node.getBestBlockHashErr = test.bestBlockErr
  6151  			node.verboseBlocks = verboseBlocks
  6152  			if test.verboseBlocks != nil {
  6153  				node.verboseBlocks = test.verboseBlocks
  6154  			}
  6155  			bd, err := wallet.FindBond(tCtx, test.coinID, test.searchUntil)
  6156  			if test.wantErr {
  6157  				if err == nil {
  6158  					t.Fatal("expected error")
  6159  				}
  6160  				return
  6161  			}
  6162  			if err != nil {
  6163  				t.Fatalf("unexpected error: %v", err)
  6164  			}
  6165  			if !bd.CheckPrivKey(bondKey) {
  6166  				t.Fatal("pkh not equal")
  6167  			}
  6168  		})
  6169  	}
  6170  }
  6171  
  6172  func TestIDUnknownTx(t *testing.T) {
  6173  	t.Run("non-segwit", func(t *testing.T) {
  6174  		testIDUnknownTx(t, false)
  6175  	})
  6176  	t.Run("segwit", func(t *testing.T) {
  6177  		testIDUnknownTx(t, true)
  6178  	})
  6179  }
  6180  
  6181  func testIDUnknownTx(t *testing.T, segwit bool) {
  6182  	// Swap Tx - any tx with p2sh outputs that is not a bond.
  6183  	_, _, swapPKScript, _, _, _, _ := makeSwapContract(true, time.Hour*12)
  6184  	swapTx := &wire.MsgTx{
  6185  		TxIn:  []*wire.TxIn{wire.NewTxIn(&wire.OutPoint{}, nil, nil)},
  6186  		TxOut: []*wire.TxOut{wire.NewTxOut(int64(toSatoshi(1)), swapPKScript)},
  6187  	}
  6188  
  6189  	// Redeem Tx
  6190  	addrStr := tP2PKHAddr
  6191  	if segwit {
  6192  		addrStr = tP2WPKHAddr
  6193  	}
  6194  	addr, _ := decodeAddress(addrStr, &chaincfg.MainNetParams)
  6195  	swapContract, _ := dexbtc.MakeContract(addr, addr, randBytes(32), time.Now().Unix(), segwit, &chaincfg.MainNetParams)
  6196  	txIn := wire.NewTxIn(&wire.OutPoint{}, nil, nil)
  6197  	if segwit {
  6198  		txIn.Witness = dexbtc.RedeemP2WSHContract(swapContract, randBytes(73), randBytes(33), randBytes(32))
  6199  	} else {
  6200  		txIn.SignatureScript, _ = dexbtc.RedeemP2SHContract(swapContract, randBytes(73), randBytes(33), randBytes(32))
  6201  	}
  6202  	redeemFee := 0.0000143
  6203  	redemptionTx := &wire.MsgTx{
  6204  		TxIn:  []*wire.TxIn{txIn},
  6205  		TxOut: []*wire.TxOut{wire.NewTxOut(int64(toSatoshi(5-redeemFee)), tP2PKH)},
  6206  	}
  6207  
  6208  	h2b := func(h string) []byte {
  6209  		b, _ := hex.DecodeString(h)
  6210  		return b
  6211  	}
  6212  
  6213  	// Create Bond Tx
  6214  	bondLockTime := 1711637410
  6215  	bondID := h2b("0e39bbb09592fd00b7d770cc832ddf4d625ae3a0")
  6216  	accountID := h2b("a0836b39b5ceb84f422b8a8cd5940117087a8522457c6d81d200557652fbe6ea")
  6217  	bondContract, _ := dexbtc.MakeBondScript(0, uint32(bondLockTime), bondID)
  6218  	contractAddr, _ := scriptHashAddress(segwit, bondContract, &chaincfg.MainNetParams)
  6219  	bondPkScript, _ := txscript.PayToAddrScript(contractAddr)
  6220  	bondOutput := wire.NewTxOut(int64(toSatoshi(2)), bondPkScript)
  6221  	bondCommitPkScript, _ := bondPushDataScript(0, accountID, int64(bondLockTime), bondID)
  6222  	bondCommitmentOutput := wire.NewTxOut(0, bondCommitPkScript)
  6223  	createBondTx := &wire.MsgTx{
  6224  		TxIn:  []*wire.TxIn{wire.NewTxIn(&wire.OutPoint{}, nil, nil)},
  6225  		TxOut: []*wire.TxOut{bondOutput, bondCommitmentOutput},
  6226  	}
  6227  
  6228  	// Redeem Bond Tx
  6229  	txIn = wire.NewTxIn(&wire.OutPoint{}, nil, nil)
  6230  	if segwit {
  6231  		txIn.Witness = dexbtc.RefundBondScriptSegwit(bondContract, randBytes(73), randBytes(33))
  6232  	} else {
  6233  		txIn.SignatureScript, _ = dexbtc.RefundBondScript(bondContract, randBytes(73), randBytes(33))
  6234  	}
  6235  	redeemBondTx := &wire.MsgTx{
  6236  		TxIn:  []*wire.TxIn{txIn},
  6237  		TxOut: []*wire.TxOut{wire.NewTxOut(int64(toSatoshi(5)), tP2PKH)},
  6238  	}
  6239  
  6240  	// Acceleration Tx
  6241  	accelerationTx := &wire.MsgTx{
  6242  		TxIn:  []*wire.TxIn{wire.NewTxIn(&wire.OutPoint{}, nil, nil)},
  6243  		TxOut: []*wire.TxOut{wire.NewTxOut(0, tP2PKH)},
  6244  	}
  6245  
  6246  	// Split Tx
  6247  	splitTx := &wire.MsgTx{
  6248  		TxIn:  []*wire.TxIn{wire.NewTxIn(&wire.OutPoint{}, nil, nil)},
  6249  		TxOut: []*wire.TxOut{wire.NewTxOut(0, tP2PKH), wire.NewTxOut(0, tP2PKH)},
  6250  	}
  6251  
  6252  	// Send Tx
  6253  	ourPkScript, cpPkScript := tP2PKH, tP2WPKH
  6254  	if segwit {
  6255  		ourPkScript, cpPkScript = tP2WPKH, tP2PKH
  6256  	}
  6257  
  6258  	_, ourAddr, _, _ := txscript.ExtractPkScriptAddrs(ourPkScript, &chaincfg.MainNetParams)
  6259  	ourAddrStr := ourAddr[0].String()
  6260  
  6261  	_, cpAddr, _, _ := txscript.ExtractPkScriptAddrs(cpPkScript, &chaincfg.MainNetParams)
  6262  	cpAddrStr := cpAddr[0].String()
  6263  
  6264  	sendTx := &wire.MsgTx{
  6265  		TxIn:  []*wire.TxIn{wire.NewTxIn(&wire.OutPoint{}, nil, nil)},
  6266  		TxOut: []*wire.TxOut{wire.NewTxOut(int64(toSatoshi(0.001)), ourPkScript), wire.NewTxOut(int64(toSatoshi(4)), cpPkScript)},
  6267  	}
  6268  
  6269  	// Receive Tx
  6270  	receiveTx := &wire.MsgTx{
  6271  		TxIn:  []*wire.TxIn{wire.NewTxIn(&wire.OutPoint{}, nil, nil)},
  6272  		TxOut: []*wire.TxOut{wire.NewTxOut(int64(toSatoshi(4)), ourPkScript), wire.NewTxOut(int64(toSatoshi(0.001)), cpPkScript)},
  6273  	}
  6274  
  6275  	floatPtr := func(f float64) *float64 {
  6276  		return &f
  6277  	}
  6278  
  6279  	type test struct {
  6280  		name           string
  6281  		ltr            *ListTransactionsResult
  6282  		tx             *wire.MsgTx
  6283  		ownedAddresses map[string]bool
  6284  		ownsAddress    bool
  6285  		exp            *asset.WalletTransaction
  6286  	}
  6287  
  6288  	tests := []*test{
  6289  		{
  6290  			name: "swap",
  6291  			ltr: &ListTransactionsResult{
  6292  				Send: true,
  6293  			},
  6294  			tx: swapTx,
  6295  			exp: &asset.WalletTransaction{
  6296  				Type:   asset.SwapOrSend,
  6297  				ID:     swapTx.TxHash().String(),
  6298  				Amount: toSatoshi(1),
  6299  			},
  6300  		},
  6301  		{
  6302  			name: "redeem",
  6303  			ltr: &ListTransactionsResult{
  6304  				Send: false,
  6305  				Fee:  &redeemFee,
  6306  			},
  6307  			tx: redemptionTx,
  6308  			exp: &asset.WalletTransaction{
  6309  				Type:   asset.Redeem,
  6310  				ID:     redemptionTx.TxHash().String(),
  6311  				Amount: toSatoshi(5),
  6312  				Fees:   toSatoshi(redeemFee),
  6313  			},
  6314  		},
  6315  		{
  6316  			name: "create bond",
  6317  			ltr: &ListTransactionsResult{
  6318  				Send: true,
  6319  				Fee:  floatPtr(0.0000222),
  6320  			},
  6321  			tx: createBondTx,
  6322  			exp: &asset.WalletTransaction{
  6323  				Type:   asset.CreateBond,
  6324  				ID:     createBondTx.TxHash().String(),
  6325  				Amount: toSatoshi(2),
  6326  				Fees:   toSatoshi(0.0000222),
  6327  				BondInfo: &asset.BondTxInfo{
  6328  					AccountID: accountID,
  6329  					BondID:    bondID,
  6330  					LockTime:  uint64(bondLockTime),
  6331  				},
  6332  			},
  6333  		},
  6334  		{
  6335  			name: "redeem bond",
  6336  			ltr: &ListTransactionsResult{
  6337  				Send: false,
  6338  			},
  6339  			tx: redeemBondTx,
  6340  			exp: &asset.WalletTransaction{
  6341  				Type:   asset.RedeemBond,
  6342  				ID:     redeemBondTx.TxHash().String(),
  6343  				Amount: toSatoshi(5),
  6344  				BondInfo: &asset.BondTxInfo{
  6345  					AccountID: []byte{},
  6346  					BondID:    bondID,
  6347  					LockTime:  uint64(bondLockTime),
  6348  				},
  6349  			},
  6350  		},
  6351  		{
  6352  			name: "acceleration",
  6353  			ltr: &ListTransactionsResult{
  6354  				Send: true,
  6355  			},
  6356  			tx: accelerationTx,
  6357  			exp: &asset.WalletTransaction{
  6358  				Type: asset.Acceleration,
  6359  				ID:   accelerationTx.TxHash().String(),
  6360  			},
  6361  			ownsAddress: true,
  6362  		},
  6363  		{
  6364  			name: "split",
  6365  			ltr: &ListTransactionsResult{
  6366  				Send: true,
  6367  			},
  6368  			tx: splitTx,
  6369  			exp: &asset.WalletTransaction{
  6370  				Type: asset.Split,
  6371  				ID:   splitTx.TxHash().String(),
  6372  			},
  6373  			ownsAddress: true,
  6374  		},
  6375  		{
  6376  			name: "send",
  6377  			ltr: &ListTransactionsResult{
  6378  				Send: true,
  6379  			},
  6380  			tx: sendTx,
  6381  			exp: &asset.WalletTransaction{
  6382  				Type:      asset.Send,
  6383  				ID:        sendTx.TxHash().String(),
  6384  				Amount:    toSatoshi(4),
  6385  				Recipient: &cpAddrStr,
  6386  			},
  6387  			ownedAddresses: map[string]bool{ourAddrStr: true},
  6388  		},
  6389  		{
  6390  			name: "receive",
  6391  			ltr:  &ListTransactionsResult{},
  6392  			tx:   receiveTx,
  6393  			exp: &asset.WalletTransaction{
  6394  				Type:      asset.Receive,
  6395  				ID:        receiveTx.TxHash().String(),
  6396  				Amount:    toSatoshi(4),
  6397  				Recipient: &ourAddrStr,
  6398  			},
  6399  			ownedAddresses: map[string]bool{ourAddrStr: true},
  6400  		},
  6401  	}
  6402  
  6403  	runTest := func(tt *test) {
  6404  		t.Run(tt.name, func(t *testing.T) {
  6405  			wallet, node, shutdown := tNewWallet(true, walletTypeRPC)
  6406  			defer shutdown()
  6407  			txID := tt.tx.TxHash().String()
  6408  			tt.ltr.TxID = txID
  6409  
  6410  			buf := new(bytes.Buffer)
  6411  			err := tt.tx.Serialize(buf)
  6412  			if err != nil {
  6413  				t.Fatalf("%s: error serializing tx: %v", tt.name, err)
  6414  			}
  6415  
  6416  			node.getTransactionMap[txID] = &GetTransactionResult{Bytes: buf.Bytes()}
  6417  			node.ownedAddresses = tt.ownedAddresses
  6418  			node.ownsAddress = tt.ownsAddress
  6419  			wt, err := wallet.idUnknownTx(tt.ltr)
  6420  			if err != nil {
  6421  				t.Fatalf("%s: unexpected error: %v", tt.name, err)
  6422  			}
  6423  			if !reflect.DeepEqual(wt, tt.exp) {
  6424  				t.Fatalf("%s: expected %+v, got %+v", tt.name, tt.exp, wt)
  6425  			}
  6426  		})
  6427  	}
  6428  
  6429  	for _, tt := range tests {
  6430  		runTest(tt)
  6431  	}
  6432  }
  6433  
  6434  func TestFeeRateCache(t *testing.T) {
  6435  	const okRate = 1
  6436  	var n int
  6437  	feeRateOK := func(context.Context, dex.Network) (uint64, error) {
  6438  		n++
  6439  		return okRate, nil
  6440  	}
  6441  	err1 := errors.New("test error 1")
  6442  	feeRateBad := func(context.Context, dex.Network) (uint64, error) {
  6443  		n++
  6444  		return 0, err1
  6445  	}
  6446  	c := &feeRateCache{f: feeRateOK}
  6447  	r, err := c.rate(context.Background(), dex.Mainnet)
  6448  	if err != nil {
  6449  		t.Fatalf("rate error: %v", err)
  6450  	}
  6451  	if r != okRate {
  6452  		t.Fatalf("expected rate %d, got %d", okRate, r)
  6453  	}
  6454  	if n != 1 {
  6455  		t.Fatal("counter not incremented")
  6456  	}
  6457  
  6458  	// Again should hit cache
  6459  	r, err = c.rate(context.Background(), dex.Mainnet)
  6460  	if err != nil {
  6461  		t.Fatalf("cached rate error: %v", err)
  6462  	}
  6463  	if r != okRate {
  6464  		t.Fatalf("expected cached rate %d, got %d", okRate, r)
  6465  	}
  6466  	if n != 1 {
  6467  		t.Fatal("counter incremented")
  6468  	}
  6469  
  6470  	// Expire cache
  6471  	c.fetchStamp = time.Now().Add(-time.Minute * 5) // const defaultShelfLife
  6472  	r, err = c.rate(context.Background(), dex.Mainnet)
  6473  	if err != nil {
  6474  		t.Fatalf("fresh rate error: %v", err)
  6475  	}
  6476  	if r != okRate {
  6477  		t.Fatalf("expected fresh rate %d, got %d", okRate, r)
  6478  	}
  6479  	if n != 2 {
  6480  		t.Fatal("counter not incremented for fresh rate")
  6481  	}
  6482  
  6483  	c.fetchStamp = time.Time{}
  6484  	c.f = feeRateBad
  6485  	_, err = c.rate(context.Background(), dex.Mainnet)
  6486  	if err == nil {
  6487  		t.Fatal("error not propagated")
  6488  	}
  6489  	if n != 3 {
  6490  		t.Fatal("counter not incremented for error")
  6491  	}
  6492  
  6493  	// Again should hit error cache.
  6494  	c.f = feeRateOK
  6495  	_, err = c.rate(context.Background(), dex.Mainnet)
  6496  	if err == nil {
  6497  		t.Fatal("error not cached")
  6498  	}
  6499  	if n != 3 {
  6500  		t.Fatal("counter incremented for cached error")
  6501  	}
  6502  
  6503  	// expire error
  6504  	c.errorStamp = time.Now().Add(-time.Minute) // const errorDelay
  6505  	r, err = c.rate(context.Background(), dex.Mainnet)
  6506  	if err != nil {
  6507  		t.Fatalf("recovered rate error: %v", err)
  6508  	}
  6509  	if r != okRate {
  6510  		t.Fatalf("expected recovered rate %d, got %d", okRate, r)
  6511  	}
  6512  	if n != 4 {
  6513  		t.Fatal("counter not incremented for recovered rate")
  6514  	}
  6515  }