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

     1  //go:build !spvlive && !harness
     2  
     3  // This code is available on the terms of the project LICENSE.md file,
     4  // also available online at https://blueoakcouncil.org/license/1.0.0.
     5  
     6  package btc
     7  
     8  import (
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"testing"
    13  	"time"
    14  
    15  	"decred.org/dcrdex/client/asset"
    16  	"decred.org/dcrdex/dex"
    17  	"decred.org/dcrdex/dex/encode"
    18  	"github.com/btcsuite/btcd/btcec/v2"
    19  	"github.com/btcsuite/btcd/btcjson"
    20  	"github.com/btcsuite/btcd/btcutil"
    21  	"github.com/btcsuite/btcd/btcutil/gcs"
    22  	"github.com/btcsuite/btcd/btcutil/gcs/builder"
    23  	"github.com/btcsuite/btcd/btcutil/psbt"
    24  	"github.com/btcsuite/btcd/chaincfg"
    25  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    26  	"github.com/btcsuite/btcd/peer"
    27  	"github.com/btcsuite/btcd/txscript"
    28  	"github.com/btcsuite/btcd/wire"
    29  	"github.com/btcsuite/btcwallet/chain"
    30  	"github.com/btcsuite/btcwallet/waddrmgr"
    31  	"github.com/btcsuite/btcwallet/wallet"
    32  	"github.com/btcsuite/btcwallet/walletdb"
    33  	"github.com/btcsuite/btcwallet/wtxmgr"
    34  	"github.com/lightninglabs/neutrino"
    35  	"github.com/lightninglabs/neutrino/headerfs"
    36  )
    37  
    38  type tBtcWallet struct {
    39  	*testData
    40  }
    41  
    42  func (c *tBtcWallet) AccountInfo() XCWalletAccount {
    43  	return XCWalletAccount{
    44  		AccountName:   defaultAcctName,
    45  		AccountNumber: defaultAcctNum,
    46  	}
    47  }
    48  
    49  func (c *tBtcWallet) GetTransactions(startBlock, endBlock int32, accountName string, cancel <-chan struct{}) (*wallet.GetTransactionsResult, error) {
    50  	return nil, nil
    51  }
    52  
    53  func (c *tBtcWallet) PublishTransaction(tx *wire.MsgTx, label string) error {
    54  	c.sentRawTx = tx
    55  	if c.sendErr != nil {
    56  		return c.sendErr
    57  	}
    58  	if c.sendToAddressErr != nil {
    59  		return c.sendToAddressErr
    60  	}
    61  	if c.badSendHash != nil {
    62  		return errors.New("bad hash")
    63  	}
    64  	return c.sendErr
    65  }
    66  
    67  func (c *tBtcWallet) CalculateAccountBalances(account uint32, confirms int32) (wallet.Balances, error) {
    68  	if c.getBalancesErr != nil {
    69  		return wallet.Balances{}, c.getBalancesErr
    70  	}
    71  	bal := &c.getBalances.Mine
    72  	mustAmount := func(v float64) btcutil.Amount {
    73  		amt, _ := btcutil.NewAmount(v)
    74  		return amt
    75  	}
    76  	return wallet.Balances{
    77  		Total:          mustAmount(bal.Trusted + bal.Untrusted),
    78  		Spendable:      mustAmount(bal.Trusted + bal.Untrusted),
    79  		ImmatureReward: mustAmount(bal.Immature),
    80  	}, nil
    81  }
    82  
    83  func (c *tBtcWallet) ListUnspent(minconf, maxconf int32, acctName string) ([]*btcjson.ListUnspentResult, error) {
    84  	if c.listUnspentErr != nil {
    85  		return nil, c.listUnspentErr
    86  	}
    87  	unspents := make([]*btcjson.ListUnspentResult, 0, len(c.listUnspent))
    88  	for _, u := range c.listUnspent {
    89  		unspents = append(unspents, &btcjson.ListUnspentResult{
    90  			TxID:    u.TxID,
    91  			Vout:    u.Vout,
    92  			Address: u.Address,
    93  			// Account: ,
    94  			ScriptPubKey:  u.ScriptPubKey.String(),
    95  			RedeemScript:  u.RedeemScript.String(),
    96  			Amount:        u.Amount,
    97  			Confirmations: int64(u.Confirmations),
    98  			Spendable:     u.Spendable,
    99  		})
   100  	}
   101  
   102  	return unspents, nil
   103  }
   104  
   105  func (c *tBtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*wire.MsgTx, *wire.TxOut, *psbt.Bip32Derivation, int64, error) {
   106  	return c.fetchInputInfoTx, nil, nil, 0, nil
   107  }
   108  
   109  func (c *tBtcWallet) ResetLockedOutpoints() {}
   110  
   111  func (c *tBtcWallet) LockOutpoint(op wire.OutPoint) {
   112  	if c.lockedCoins != nil {
   113  		// check if already locked
   114  		for _, l := range c.lockedCoins {
   115  			if l.TxID == op.Hash.String() && l.Vout == op.Index {
   116  				return
   117  			}
   118  		}
   119  
   120  		c.lockedCoins = append(c.lockedCoins, &RPCOutpoint{
   121  			TxID: op.Hash.String(),
   122  			Vout: op.Index,
   123  		})
   124  		return
   125  	}
   126  
   127  	c.lockedCoins = []*RPCOutpoint{{
   128  		TxID: op.Hash.String(),
   129  		Vout: op.Index,
   130  	}}
   131  }
   132  
   133  func (c *tBtcWallet) UnlockOutpoint(op wire.OutPoint) {}
   134  
   135  func (c *tBtcWallet) LockedOutpoints() []btcjson.TransactionInput {
   136  	unspents := make([]btcjson.TransactionInput, 0)
   137  	for _, u := range c.listLockUnspent {
   138  		unspents = append(unspents, btcjson.TransactionInput{
   139  			Txid: u.TxID,
   140  			Vout: u.Vout,
   141  		})
   142  	}
   143  
   144  	return unspents
   145  }
   146  
   147  func (c *tBtcWallet) NewChangeAddress(account uint32, scope waddrmgr.KeyScope) (btcutil.Address, error) {
   148  	if c.changeAddrErr != nil {
   149  		return nil, c.changeAddrErr
   150  	}
   151  	return btcutil.DecodeAddress(c.changeAddr, &chaincfg.MainNetParams)
   152  }
   153  
   154  func (c *tBtcWallet) NewAddress(account uint32, scope waddrmgr.KeyScope) (btcutil.Address, error) {
   155  	if c.newAddressErr != nil {
   156  		return nil, c.newAddressErr
   157  	}
   158  	return btcutil.DecodeAddress(c.newAddress, &chaincfg.MainNetParams)
   159  }
   160  
   161  func (c *tBtcWallet) SignTransaction(tx *wire.MsgTx, hashType txscript.SigHashType, additionalPrevScriptsadditionalPrevScripts map[wire.OutPoint][]byte,
   162  	additionalKeysByAddress map[string]*btcutil.WIF, p2shRedeemScriptsByAddress map[string][]byte) ([]wallet.SignatureError, error) {
   163  
   164  	if c.signTxErr != nil {
   165  		return nil, c.signTxErr
   166  	}
   167  	c.signFunc(tx)
   168  	if c.sigIncomplete {
   169  		return []wallet.SignatureError{{Error: errors.New("tBtcWallet SignTransaction error")}}, nil
   170  	}
   171  	return nil, nil
   172  }
   173  
   174  func (c *tBtcWallet) PrivKeyForAddress(a btcutil.Address) (*btcec.PrivateKey, error) {
   175  	if c.privKeyForAddrErr != nil {
   176  		return nil, c.privKeyForAddrErr
   177  	}
   178  	return c.privKeyForAddr.PrivKey, nil
   179  }
   180  
   181  func (c *tBtcWallet) Database() walletdb.DB {
   182  	return nil
   183  }
   184  
   185  func (c *tBtcWallet) Unlock(passphrase []byte, lock <-chan time.Time) error {
   186  	return c.unlockErr
   187  }
   188  
   189  func (c *tBtcWallet) Lock() {}
   190  
   191  func (c *tBtcWallet) Locked() bool {
   192  	return c.locked
   193  }
   194  
   195  func (c *tBtcWallet) SendOutputs(outputs []*wire.TxOut, keyScope *waddrmgr.KeyScope,
   196  	account uint32, minconf int32, satPerKb btcutil.Amount,
   197  	coinSelectionStrategy wallet.CoinSelectionStrategy, label string) (*wire.MsgTx, error) {
   198  	if c.sendToAddressErr != nil {
   199  		return nil, c.sendToAddressErr
   200  	}
   201  	tx := wire.NewMsgTx(wire.TxVersion)
   202  	for _, txOut := range outputs {
   203  		tx.AddTxOut(txOut)
   204  	}
   205  	tx.AddTxIn(dummyInput())
   206  
   207  	return tx, nil
   208  }
   209  
   210  func (c *tBtcWallet) HaveAddress(a btcutil.Address) (bool, error) {
   211  	if c.ownedAddresses != nil && c.ownedAddresses[a.String()] {
   212  		return true, nil
   213  	}
   214  	return c.ownsAddress, nil
   215  }
   216  
   217  func (c *tBtcWallet) Stop() {}
   218  
   219  func (c *tBtcWallet) Start() (SPVService, error) {
   220  	return nil, nil
   221  }
   222  
   223  func (c *tBtcWallet) WaitForShutdown() {}
   224  
   225  func (c *tBtcWallet) ChainSynced() bool {
   226  	c.blockchainMtx.RLock()
   227  	defer c.blockchainMtx.RUnlock()
   228  	if c.getBlockchainInfo == nil {
   229  		return false
   230  	}
   231  	return c.getBlockchainInfo.Blocks >= c.getBlockchainInfo.Headers // -1 ok for chain sync ?
   232  }
   233  
   234  func (c *tBtcWallet) SynchronizeRPC(chainClient chain.Interface) {}
   235  
   236  func (c *tBtcWallet) WalletTransaction(txHash *chainhash.Hash) (*wtxmgr.TxDetails, error) {
   237  	if c.getTransactionErr != nil {
   238  		return nil, c.getTransactionErr
   239  	}
   240  	var txData *GetTransactionResult
   241  	if c.getTransactionMap != nil {
   242  		if txData = c.getTransactionMap["any"]; txData == nil {
   243  			txData = c.getTransactionMap[txHash.String()]
   244  		}
   245  	}
   246  	if txData == nil {
   247  		return nil, WalletTransactionNotFound
   248  	}
   249  
   250  	tx, _ := msgTxFromBytes(txData.Bytes)
   251  	blockHash, _ := chainhash.NewHashFromStr(txData.BlockHash)
   252  
   253  	blk := c.getBlock(*blockHash)
   254  	var blockHeight int32
   255  	if blk != nil {
   256  		blockHeight = int32(blk.height)
   257  	} else {
   258  		blockHeight = -1
   259  	}
   260  
   261  	credits := make([]wtxmgr.CreditRecord, 0, len(tx.TxIn))
   262  	debits := make([]wtxmgr.DebitRecord, 0, len(tx.TxIn))
   263  	for i, in := range tx.TxIn {
   264  		credits = append(credits, wtxmgr.CreditRecord{
   265  			// Amount:,
   266  			Index: uint32(i),
   267  			Spent: c.walletTxSpent,
   268  			// Change: ,
   269  		})
   270  
   271  		var debitAmount int64
   272  		// The sources of transaction inputs all need to be added to getTransactionMap
   273  		// in order to get accurate Fees and Amounts when calling GetWalletTransaction
   274  		// when using the SPV wallet.
   275  		if gtr := c.getTransactionMap[in.PreviousOutPoint.Hash.String()]; gtr != nil {
   276  			tx, _ := msgTxFromBytes(gtr.Bytes)
   277  			debitAmount = tx.TxOut[in.PreviousOutPoint.Index].Value
   278  		}
   279  
   280  		debits = append(debits, wtxmgr.DebitRecord{
   281  			Amount: btcutil.Amount(debitAmount),
   282  		})
   283  
   284  	}
   285  	return &wtxmgr.TxDetails{
   286  		TxRecord: wtxmgr.TxRecord{
   287  			MsgTx:    *tx,
   288  			Received: time.Unix(int64(txData.Time), 0),
   289  		},
   290  		Block: wtxmgr.BlockMeta{
   291  			Block: wtxmgr.Block{
   292  				Hash:   *blockHash,
   293  				Height: blockHeight,
   294  			},
   295  		},
   296  		Credits: credits,
   297  		Debits:  debits,
   298  	}, nil
   299  }
   300  
   301  func (c *tBtcWallet) getTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error) {
   302  	if c.getTransactionErr != nil {
   303  		return nil, c.getTransactionErr
   304  	}
   305  	var txData *GetTransactionResult
   306  	if c.getTransactionMap != nil {
   307  		if txData = c.getTransactionMap["any"]; txData == nil {
   308  			txData = c.getTransactionMap[txHash.String()]
   309  		}
   310  	}
   311  	if txData == nil {
   312  		return nil, WalletTransactionNotFound
   313  	}
   314  	return txData, nil
   315  }
   316  
   317  func (c *tBtcWallet) SyncedTo() waddrmgr.BlockStamp {
   318  	bestHash, bestHeight := c.bestBlock() // NOTE: in reality this may be lower than the chain service's best block
   319  	blk := c.getBlock(*bestHash)
   320  	return waddrmgr.BlockStamp{
   321  		Height:    int32(bestHeight),
   322  		Hash:      *bestHash,
   323  		Timestamp: blk.msgBlock.Header.Timestamp,
   324  	}
   325  }
   326  
   327  func (c *tBtcWallet) SignTx(tx *wire.MsgTx) error {
   328  	if c.signTxErr != nil {
   329  		return c.signTxErr
   330  	}
   331  	c.signFunc(tx)
   332  	if c.sigIncomplete {
   333  		return errors.New("tBtcWallet SignTransaction error")
   334  	}
   335  	return nil
   336  }
   337  
   338  func (c *tBtcWallet) AccountProperties(scope waddrmgr.KeyScope, acct uint32) (*waddrmgr.AccountProperties, error) {
   339  	return nil, nil
   340  }
   341  
   342  func (c *tBtcWallet) BlockNotifications(ctx context.Context) <-chan *BlockNotification {
   343  	return nil
   344  }
   345  
   346  func (c *tBtcWallet) ForceRescan() {}
   347  
   348  func (c *tBtcWallet) RescanAsync() error { return nil }
   349  
   350  func (c *tBtcWallet) Birthday() time.Time {
   351  	return time.Time{}
   352  }
   353  
   354  func (c *tBtcWallet) Reconfigure(*asset.WalletConfig, string) (bool, error) {
   355  	return false, nil
   356  }
   357  
   358  func (c *tBtcWallet) Peers() ([]*asset.WalletPeer, error) {
   359  	return nil, nil
   360  }
   361  func (c *tBtcWallet) AddPeer(string) error {
   362  	return nil
   363  }
   364  func (c *tBtcWallet) RemovePeer(string) error {
   365  	return nil
   366  }
   367  
   368  type tNeutrinoClient struct {
   369  	*testData
   370  }
   371  
   372  func (c *tNeutrinoClient) Stop() error { return nil }
   373  
   374  func (c *tNeutrinoClient) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
   375  	c.blockchainMtx.RLock()
   376  	defer c.blockchainMtx.RUnlock()
   377  	for height, blockHash := range c.mainchain {
   378  		if height == blockHeight {
   379  			return blockHash, nil
   380  		}
   381  	}
   382  	return nil, fmt.Errorf("no (test) block at height %d", blockHeight)
   383  }
   384  
   385  func (c *tNeutrinoClient) BestBlock() (*headerfs.BlockStamp, error) {
   386  	c.blockchainMtx.RLock()
   387  	if c.getBestBlockHashErr != nil {
   388  		defer c.blockchainMtx.RUnlock() // reading c.getBestBlockHashErr below
   389  		return nil, c.getBestBlockHashErr
   390  	}
   391  	c.blockchainMtx.RUnlock()
   392  	bestHash, bestHeight := c.bestBlock()
   393  	blk := c.getBlock(*bestHash)
   394  	return &headerfs.BlockStamp{
   395  		Height:    int32(bestHeight),
   396  		Hash:      *bestHash,
   397  		Timestamp: blk.msgBlock.Header.Timestamp, // neutrino sets this as of 88c025e
   398  	}, nil
   399  }
   400  
   401  func (c *tNeutrinoClient) Peers() []SPVPeer {
   402  	c.blockchainMtx.RLock()
   403  	defer c.blockchainMtx.RUnlock()
   404  	peer := &neutrino.ServerPeer{Peer: &peer.Peer{}}
   405  	if c.getBlockchainInfo != nil {
   406  		peer.UpdateLastBlockHeight(int32(c.getBlockchainInfo.Headers))
   407  	}
   408  	return []SPVPeer{peer}
   409  }
   410  
   411  func (c *tNeutrinoClient) AddPeer(string) error {
   412  	return nil
   413  }
   414  
   415  func (c *tNeutrinoClient) GetBlockHeight(hash *chainhash.Hash) (int32, error) {
   416  	block := c.getBlock(*hash)
   417  	if block == nil {
   418  		return 0, fmt.Errorf("(test) block not found for block hash %s", hash)
   419  	}
   420  	return int32(block.height), nil
   421  }
   422  
   423  func (c *tNeutrinoClient) GetBlockHeader(blkHash *chainhash.Hash) (*wire.BlockHeader, error) {
   424  	block := c.getBlock(*blkHash)
   425  	if block == nil {
   426  		return nil, errors.New("no block verbose found")
   427  	}
   428  	return &block.msgBlock.Header, nil
   429  }
   430  
   431  func (c *tNeutrinoClient) GetCFilter(blockHash chainhash.Hash, filterType wire.FilterType, options ...neutrino.QueryOption) (*gcs.Filter, error) {
   432  	var key [gcs.KeySize]byte
   433  	copy(key[:], blockHash.CloneBytes()[:])
   434  	scripts := c.getCFilterScripts[blockHash]
   435  	scripts = append(scripts, encode.RandomBytes(10))
   436  	return gcs.BuildGCSFilter(builder.DefaultP, builder.DefaultM, key, scripts)
   437  }
   438  
   439  func (c *tNeutrinoClient) GetBlock(blockHash chainhash.Hash, options ...neutrino.QueryOption) (*btcutil.Block, error) {
   440  	blk := c.getBlock(blockHash)
   441  	if blk == nil {
   442  		return nil, fmt.Errorf("no (test) block %s", blockHash)
   443  	}
   444  	return btcutil.NewBlock(blk.msgBlock), nil
   445  }
   446  
   447  type tSPVPeer struct {
   448  	startHeight, lastHeight int32
   449  }
   450  
   451  func (p *tSPVPeer) StartingHeight() int32 {
   452  	return p.startHeight
   453  }
   454  
   455  func (p *tSPVPeer) LastBlock() int32 {
   456  	return p.lastHeight
   457  }
   458  
   459  func TestSwapConfirmations(t *testing.T) {
   460  	wallet, node, shutdown := tNewWallet(true, walletTypeSPV)
   461  	defer shutdown()
   462  
   463  	spv := wallet.node.(*spvWallet)
   464  	node.txOutRes = nil
   465  
   466  	const tipHeight = 10
   467  	const swapHeight = 1
   468  	const spendHeight = 3
   469  	const swapConfs = tipHeight - swapHeight + 1
   470  
   471  	for i := int64(0); i <= tipHeight; i++ {
   472  		node.addRawTx(i, dummyTx())
   473  	}
   474  
   475  	_, _, pkScript, _, _, _, _ := makeSwapContract(true, time.Hour*12)
   476  
   477  	swapTx := makeRawTx([]dex.Bytes{pkScript}, []*wire.TxIn{dummyInput()})
   478  	swapTxHash := swapTx.TxHash()
   479  	const vout = 0
   480  	swapOutPt := NewOutPoint(&swapTxHash, vout)
   481  	swapBlockHash, _ := node.addRawTx(swapHeight, swapTx)
   482  
   483  	spendTx := dummyTx()
   484  	spendTx.TxIn[0].PreviousOutPoint.Hash = swapTxHash
   485  	spendBlockHash, _ := node.addRawTx(spendHeight, spendTx)
   486  	spendTxHash := spendTx.TxHash()
   487  
   488  	matchTime := time.Unix(1, 0)
   489  
   490  	checkSuccess := func(tag string, expConfs uint32, expSpent bool) {
   491  		t.Helper()
   492  		confs, spent, err := spv.swapConfirmations(&swapTxHash, vout, pkScript, matchTime)
   493  		if err != nil {
   494  			t.Fatalf("%s error: %v", tag, err)
   495  		}
   496  		if confs != expConfs {
   497  			t.Fatalf("wrong number of %s confs. wanted %d, got %d", tag, expConfs, confs)
   498  		}
   499  		if spent != expSpent {
   500  			t.Fatalf("%s path not expected spent status. wanted %t, got %t", tag, expSpent, spent)
   501  		}
   502  	}
   503  
   504  	checkFailure := func(tag string) {
   505  		t.Helper()
   506  		_, _, err := spv.swapConfirmations(&swapTxHash, vout, pkScript, matchTime)
   507  		if err == nil {
   508  			t.Fatalf("no error for %q test", tag)
   509  		}
   510  	}
   511  
   512  	// confirmations() path
   513  	node.confsErr = tErr
   514  	checkFailure("confirmations")
   515  
   516  	node.confsErr = nil
   517  	node.confs = 10
   518  	node.confsSpent = true
   519  	txB, _ := serializeMsgTx(swapTx)
   520  	node.getTransactionMap = map[string]*GetTransactionResult{
   521  		"any": {
   522  			BlockHash:  swapBlockHash.String(),
   523  			BlockIndex: swapHeight,
   524  			Bytes:      txB,
   525  		}}
   526  	node.walletTxSpent = true
   527  	checkSuccess("confirmations", swapConfs, true)
   528  	node.getTransactionMap = nil
   529  	node.walletTxSpent = false
   530  	node.confsErr = WalletTransactionNotFound
   531  
   532  	// DB path.
   533  	node.dbBlockForTx[swapTxHash] = &hashEntry{hash: *swapBlockHash}
   534  	node.dbBlockForTx[spendTxHash] = &hashEntry{hash: *spendBlockHash}
   535  	node.checkpoints[swapOutPt] = &scanCheckpoint{res: &filterScanResult{
   536  		blockHash:   swapBlockHash,
   537  		blockHeight: swapHeight,
   538  		spend:       &spendingInput{},
   539  		checkpoint:  *spendBlockHash,
   540  	}}
   541  	checkSuccess("GetSpend", swapConfs, true)
   542  	delete(node.checkpoints, swapOutPt)
   543  	delete(node.dbBlockForTx, swapTxHash)
   544  
   545  	// Neutrino scan
   546  
   547  	// Fail cuz no match time provided and block not known.
   548  	matchTime = time.Time{}
   549  	checkFailure("no match time")
   550  	matchTime = time.Unix(1, 0)
   551  
   552  	// spent
   553  	node.getCFilterScripts[*spendBlockHash] = [][]byte{pkScript}
   554  	delete(node.checkpoints, swapOutPt)
   555  	checkSuccess("spend", 0, true)
   556  	node.getCFilterScripts[*spendBlockHash] = nil
   557  
   558  	// Not found
   559  	delete(node.checkpoints, swapOutPt)
   560  	checkFailure("no utxo")
   561  
   562  	// Found.
   563  	node.getCFilterScripts[*swapBlockHash] = [][]byte{pkScript}
   564  	delete(node.checkpoints, swapOutPt)
   565  	checkSuccess("scan find", swapConfs, false)
   566  }
   567  
   568  func TestFindBlockForTime(t *testing.T) {
   569  	wallet, node, shutdown := tNewWallet(true, walletTypeSPV)
   570  	defer shutdown()
   571  	spv := wallet.node.(*spvWallet)
   572  
   573  	const tipHeight = 40
   574  
   575  	for i := 0; i <= tipHeight; i++ {
   576  		node.addRawTx(int64(i), dummyTx())
   577  	}
   578  
   579  	const searchBlock = 35
   580  	matchTime := generateTestBlockTime(searchBlock)
   581  	const offsetBlock = searchBlock - testBlocksPerBlockTimeOffset
   582  	const startBlock = offsetBlock - medianTimeBlocks
   583  	height, err := spv.findBlockForTime(matchTime)
   584  	if err != nil {
   585  		t.Fatalf("findBlockForTime error: %v", err)
   586  	}
   587  	if height != startBlock {
   588  		t.Fatalf("wrong height. wanted %d, got %d", startBlock, height)
   589  	}
   590  
   591  	// But if we shift the startBlock time to > offsetBlock time, the window
   592  	// will continue down 11 more.
   593  	_, blk := node.getBlockAtHeight(startBlock)
   594  	blk.msgBlock.Header.Timestamp = generateTestBlockTime(offsetBlock)
   595  	height, err = spv.findBlockForTime(matchTime)
   596  	if err != nil {
   597  		t.Fatalf("findBlockForTime error for shifted start block: %v", err)
   598  	}
   599  	if height != startBlock-medianTimeBlocks {
   600  		t.Fatalf("wrong height. wanted %d, got %d", startBlock-11, height)
   601  	}
   602  
   603  	// And doing an early enough block just returns genesis
   604  	height, err = spv.findBlockForTime(generateTestBlockTime(10))
   605  	if err != nil {
   606  		t.Fatalf("findBlockForTime error for genesis test: %v", err)
   607  	}
   608  	if height != 0 {
   609  		t.Fatalf("not genesis: height = %d", height)
   610  	}
   611  
   612  	// A time way in the future still returns at least the last 11 blocks.
   613  	height, err = spv.findBlockForTime(generateTestBlockTime(100))
   614  	if err != nil {
   615  		t.Fatalf("findBlockForTime error for future test: %v", err)
   616  	}
   617  	// +1 because tip block is included here, as opposed to the shifted start
   618  	// block, where the shifted block wasn't included.
   619  	if height != tipHeight-medianTimeBlocks+1 {
   620  		t.Fatalf("didn't get tip - 11. wanted %d, got %d", tipHeight-medianTimeBlocks, height)
   621  	}
   622  }
   623  
   624  func TestGetTxOut(t *testing.T) {
   625  	wallet, node, shutdown := tNewWallet(true, walletTypeSPV)
   626  	defer shutdown()
   627  	spv := wallet.node.(*spvWallet)
   628  
   629  	_, _, pkScript, _, _, _, _ := makeSwapContract(true, time.Hour*12)
   630  	const vout = 0
   631  	const blockHeight = 10
   632  	const tipHeight = 20
   633  	tx := makeRawTx([]dex.Bytes{pkScript}, []*wire.TxIn{dummyInput()})
   634  	txHash := tx.TxHash()
   635  	outPt := NewOutPoint(&txHash, vout)
   636  	blockHash, _ := node.addRawTx(blockHeight, tx)
   637  	txB, _ := serializeMsgTx(tx)
   638  	node.addRawTx(tipHeight, dummyTx())
   639  	spendingTx := dummyTx()
   640  	spendingTx.TxIn[0].PreviousOutPoint.Hash = txHash
   641  	spendBlockHash, _ := node.addRawTx(tipHeight-1, spendingTx)
   642  
   643  	// Prime the db
   644  	for h := int64(1); h <= tipHeight; h++ {
   645  		node.addRawTx(h, dummyTx())
   646  	}
   647  
   648  	// Abnormal error
   649  	node.getTransactionErr = tErr
   650  	_, _, err := spv.getTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight))
   651  	if err == nil {
   652  		t.Fatalf("no error for getWalletTransaction error")
   653  	}
   654  
   655  	// Wallet transaction found
   656  	node.getTransactionErr = nil
   657  	node.getTransactionMap = map[string]*GetTransactionResult{"any": {
   658  		BlockHash: blockHash.String(),
   659  		Bytes:     txB,
   660  	}}
   661  
   662  	_, confs, err := spv.getTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight))
   663  	if err != nil {
   664  		t.Fatalf("error for wallet transaction found: %v", err)
   665  	}
   666  	if confs != tipHeight-blockHeight+1 {
   667  		t.Fatalf("wrong confs for wallet transaction. wanted %d, got %d", tipHeight-blockHeight+1, confs)
   668  	}
   669  
   670  	// No wallet transaction, but we have a spend recorded.
   671  	node.getTransactionErr = WalletTransactionNotFound
   672  	node.getTransactionMap = nil
   673  	node.checkpoints[outPt] = &scanCheckpoint{res: &filterScanResult{
   674  		blockHash:  blockHash,
   675  		spend:      &spendingInput{},
   676  		checkpoint: *spendBlockHash,
   677  	}}
   678  	op, confs, err := spv.getTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight))
   679  	if op != nil || confs != 0 || err != nil {
   680  		t.Fatal("wrong result for spent txout", op != nil, confs, err)
   681  	}
   682  	delete(node.checkpoints, outPt)
   683  
   684  	// no spend record. gotta scan
   685  
   686  	// case 1: we have a block hash in the database
   687  	node.dbBlockForTx[txHash] = &hashEntry{hash: *blockHash}
   688  	node.getCFilterScripts[*blockHash] = [][]byte{pkScript}
   689  	_, _, err = spv.getTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight))
   690  	if err != nil {
   691  		t.Fatalf("error for GetUtxo with cached hash: %v", err)
   692  	}
   693  
   694  	// case 2: no block hash in db. Will do scan and store them.
   695  	delete(node.dbBlockForTx, txHash)
   696  	delete(node.checkpoints, outPt)
   697  	_, _, err = spv.getTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight))
   698  	if err != nil {
   699  		t.Fatalf("error for GetUtxo with no cached hash: %v", err)
   700  	}
   701  	if _, inserted := node.dbBlockForTx[txHash]; !inserted {
   702  		t.Fatalf("db not updated after GetUtxo scan success")
   703  	}
   704  
   705  	// case 3: spending tx found first
   706  	delete(node.checkpoints, outPt)
   707  	node.getCFilterScripts[*spendBlockHash] = [][]byte{pkScript}
   708  	txOut, _, err := spv.getTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight))
   709  	if err != nil {
   710  		t.Fatalf("error for spent tx: %v", err)
   711  	}
   712  	if txOut != nil {
   713  		t.Fatalf("spend output returned from getTxOut")
   714  	}
   715  
   716  	// Make sure we can find it with the checkpoint.
   717  	node.checkpoints[outPt].res.spend = nil
   718  	node.getCFilterScripts[*spendBlockHash] = nil
   719  	// We won't actually scan for the output itself, so nil'ing these should
   720  	// have no effect.
   721  	node.getCFilterScripts[*blockHash] = nil
   722  	_, _, err = spv.getTxOut(&txHash, vout, pkScript, generateTestBlockTime(blockHeight))
   723  	if err != nil {
   724  		t.Fatalf("error for checkpointed output: %v", err)
   725  	}
   726  }
   727  
   728  func TestTryBlocksWithNotifier(t *testing.T) {
   729  	defaultWalletBlockAllowance := walletBlockAllowance
   730  	defaultBlockTicker := blockTicker
   731  
   732  	walletBlockAllowance = 50 * time.Millisecond
   733  	blockTicker = 20 * time.Millisecond
   734  
   735  	defer func() {
   736  		walletBlockAllowance = defaultWalletBlockAllowance
   737  		blockTicker = defaultBlockTicker
   738  	}()
   739  
   740  	wallet, node, shutdown := tNewWallet(true, walletTypeSPV)
   741  	defer shutdown()
   742  
   743  	spv := wallet.node.(*spvWallet)
   744  
   745  	getNote := func(timeout time.Duration) bool {
   746  		select {
   747  		case <-node.tipChanged:
   748  			return true
   749  		case <-time.After(timeout):
   750  			return false
   751  		}
   752  	}
   753  
   754  	if getNote(walletBlockAllowance * 2) {
   755  		t.Fatalf("got a first block")
   756  	}
   757  
   758  	// Feed (*baseWallet).watchBlocks:
   759  	// (*spvWallet).getBestBlockHeader -> (*spvWallet).getBestBlockHash -> (*tBtcWallet).syncedTo -> (*testData).bestBlock [mainchain]
   760  	// (*spvWallet).getBestBlockHeader -> (*spvWallet).getBlockHeader -> (*testData).getBlock [verboseBlocks]
   761  
   762  	var tipHeight int64
   763  	addBlock := func() *BlockVector { // update the mainchain and verboseBlocks testData fields
   764  		tipHeight++
   765  		h, _ := node.addRawTx(tipHeight, dummyTx())
   766  		return &BlockVector{tipHeight, *h}
   767  	}
   768  
   769  	// Start with no blocks so that we're not synced.
   770  	node.blockchainMtx.Lock()
   771  	node.getBlockchainInfo = &GetBlockchainInfoResult{
   772  		Headers: 3,
   773  		Blocks:  0,
   774  	}
   775  	node.blockchainMtx.Unlock()
   776  
   777  	addBlock()
   778  
   779  	if !getNote(blockTicker * 2) {
   780  		t.Fatalf("0th block didn't send tip change update")
   781  	}
   782  
   783  	addBlock()
   784  
   785  	// It should not come through on the block tick, since it will be queued.
   786  	if getNote(blockTicker * 2) {
   787  		t.Fatalf("got non-0th block that should've been queued")
   788  	}
   789  
   790  	// And it won't come through after a single block allowance, because we're
   791  	// not synced.
   792  	if getNote(walletBlockAllowance * 2) {
   793  		t.Fatal("block didn't wait for the syncing mode allowance")
   794  	}
   795  
   796  	// But it will come through after the sync timeout = 10 * normal timeout.
   797  	if !getNote(walletBlockAllowance * 9) {
   798  		t.Fatal("block didn't time out in syncing mode")
   799  	}
   800  
   801  	// But if we're synced, it should come through after the normal block
   802  	// allowance.
   803  	addBlock()
   804  	node.blockchainMtx.Lock()
   805  	node.getBlockchainInfo = &GetBlockchainInfoResult{
   806  		Headers: tipHeight,
   807  		Blocks:  tipHeight,
   808  	}
   809  	node.blockchainMtx.Unlock()
   810  	if !getNote(walletBlockAllowance * 2) {
   811  		t.Fatal("block didn't time out in normal mode")
   812  	}
   813  
   814  	// On the other hand, a wallet block should come through immediately. Not
   815  	// even waiting on the block tick.
   816  	spv.tipChan <- addBlock()
   817  	if !getNote(blockTicker / 2) {
   818  		t.Fatal("wallet block wasn't sent through")
   819  	}
   820  
   821  	// If we do the same thing but make sure that a polled block is queued
   822  	// first, we should still see the block right away, and the queued block
   823  	// should be canceled.
   824  	blk := addBlock()
   825  	time.Sleep(blockTicker * 2)
   826  	spv.tipChan <- blk
   827  	if !getNote(blockTicker / 2) {
   828  		t.Fatal("wallet block wasn't sent through with polled block queued")
   829  	}
   830  
   831  	if getNote(walletBlockAllowance * 2) {
   832  		t.Fatal("queued polled block that should have been canceled came through")
   833  	}
   834  }
   835  
   836  func TestGetBlockHeader(t *testing.T) {
   837  	wallet, node, shutdown := tNewWallet(true, walletTypeSPV)
   838  	defer shutdown()
   839  
   840  	const tipHeight = 12
   841  	const blockHeight = 11 // 2 confirmations
   842  	var blockHash, prevHash, h chainhash.Hash
   843  	var blockHdr msgBlockWithHeight
   844  	for height := 0; height <= tipHeight; height++ {
   845  		hdr := &msgBlockWithHeight{
   846  			msgBlock: &wire.MsgBlock{
   847  				Header: wire.BlockHeader{
   848  					PrevBlock: h,
   849  					Timestamp: time.Unix(int64(height), 0),
   850  				},
   851  				Transactions: nil,
   852  			},
   853  			height: int64(height),
   854  		}
   855  
   856  		h = hdr.msgBlock.BlockHash()
   857  
   858  		switch height {
   859  		case blockHeight - 1:
   860  			prevHash = h
   861  			blockHdr = *hdr
   862  		case blockHeight:
   863  			blockHash = h
   864  		}
   865  
   866  		node.verboseBlocks[h] = hdr
   867  		hh := h // just because we are storing pointers in mainchain
   868  		node.mainchain[int64(height)] = &hh
   869  	}
   870  
   871  	hdr, mainchain, err := wallet.tipRedeemer.getBlockHeader(&blockHash)
   872  	if err != nil {
   873  		t.Fatalf("initial success error: %v", err)
   874  	}
   875  	if !mainchain {
   876  		t.Fatalf("expected block %s to be in mainchain", blockHash)
   877  	}
   878  	if hdr.Hash != blockHash.String() {
   879  		t.Fatal("wrong header?")
   880  	}
   881  	if hdr.Height != blockHeight {
   882  		t.Fatal("wrong height?")
   883  	}
   884  	if hdr.Time != blockHeight {
   885  		t.Fatalf("wrong block time stamp, wanted %d, got %d", blockHeight, hdr.Time)
   886  	}
   887  	if hdr.Confirmations != 2 {
   888  		t.Fatalf("expected 2 confs, got %d", hdr.Confirmations)
   889  	}
   890  	if hdr.PreviousBlockHash != prevHash.String() {
   891  		t.Fatalf("wrong previous hash, want: %s got: %s", prevHash.String(), hdr.PreviousBlockHash)
   892  	}
   893  
   894  	node.mainchain[int64(blockHeight)] = &chainhash.Hash{0x01} // mainchain ended up with different block
   895  	hdr, mainchain, err = wallet.tipRedeemer.getBlockHeader(&blockHash)
   896  	if err != nil {
   897  		t.Fatalf("initial success error: %v", err)
   898  	}
   899  	if mainchain {
   900  		t.Fatalf("expected block %s to be classified as oprhan", blockHash)
   901  	}
   902  	if hdr.Confirmations != -1 {
   903  		t.Fatalf("expected -1 confs for side chain block, got %d", hdr.Confirmations)
   904  	}
   905  	node.mainchain[int64(blockHeight)] = &blockHash // clean up
   906  
   907  	// Can't fetch header error.
   908  	delete(node.verboseBlocks, blockHash) // can't find block by hash
   909  	if _, _, err := wallet.tipRedeemer.getBlockHeader(&blockHash); err == nil {
   910  		t.Fatalf("Can't fetch header error not propagated")
   911  	}
   912  	node.verboseBlocks[blockHash] = &blockHdr // clean up
   913  
   914  	// Main chain is shorter than requested block.
   915  	prevMainchain := node.mainchain
   916  	node.mainchain = map[int64]*chainhash.Hash{
   917  		0: node.mainchain[0],
   918  		1: node.mainchain[1],
   919  	}
   920  	hdr, mainchain, err = wallet.tipRedeemer.getBlockHeader(&blockHash)
   921  	if err != nil {
   922  		t.Fatalf("invalid tip height not noticed")
   923  	}
   924  	if mainchain {
   925  		t.Fatalf("expected block %s to be classified as orphan", blockHash)
   926  	}
   927  	if hdr.Confirmations != -1 {
   928  		t.Fatalf("confirmations not zero for lower mainchain tip height, got: %d", hdr.Confirmations)
   929  	}
   930  	node.mainchain = prevMainchain // clean up
   931  }