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