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

     1  //go:build !harness && !vspd
     2  
     3  package dcr
     4  
     5  import (
     6  	"context"
     7  	"errors"
     8  	"fmt"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  	"unsafe"
    14  
    15  	"decred.org/dcrdex/client/asset"
    16  	"decred.org/dcrdex/dex"
    17  	"decred.org/dcrdex/dex/encode"
    18  	walleterrors "decred.org/dcrwallet/v4/errors"
    19  	"decred.org/dcrwallet/v4/p2p"
    20  	walletjson "decred.org/dcrwallet/v4/rpc/jsonrpc/types"
    21  	"decred.org/dcrwallet/v4/wallet"
    22  	"decred.org/dcrwallet/v4/wallet/udb"
    23  	"github.com/decred/dcrd/blockchain/stake/v5"
    24  	"github.com/decred/dcrd/chaincfg/chainhash"
    25  	"github.com/decred/dcrd/chaincfg/v3"
    26  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    27  	"github.com/decred/dcrd/dcrutil/v4"
    28  	"github.com/decred/dcrd/gcs/v4"
    29  	chainjson "github.com/decred/dcrd/rpc/jsonrpc/types/v4"
    30  	"github.com/decred/dcrd/txscript/v4"
    31  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    32  	"github.com/decred/dcrd/wire"
    33  )
    34  
    35  type tDcrWallet struct {
    36  	spvSyncer
    37  	knownAddr      wallet.KnownAddress
    38  	knownAddrErr   error
    39  	txsByHash      []*wire.MsgTx
    40  	txsByHashErr   error
    41  	acctBal        wallet.Balances
    42  	acctBalErr     error
    43  	lockedPts      []chainjson.TransactionInput
    44  	lockedPtsErr   error
    45  	unspents       []*walletjson.ListUnspentResult
    46  	listUnspentErr error
    47  	listTxs        []walletjson.ListTransactionsResult
    48  	listTxsErr     error
    49  	tip            struct {
    50  		hash   chainhash.Hash
    51  		height int32
    52  	}
    53  	extAddr              stdaddr.Address
    54  	extAddrErr           error
    55  	intAddr              stdaddr.Address
    56  	intAddrErr           error
    57  	sigErrs              []wallet.SignatureError
    58  	signTxErr            error
    59  	publishTxErr         error
    60  	blockHeader          map[chainhash.Hash]*wire.BlockHeader
    61  	blockHeaderErr       map[chainhash.Hash]error
    62  	mainchainDontHave    bool
    63  	mainchainInvalidated bool
    64  	mainchainErr         error
    65  	filterKey            [gcs.KeySize]byte
    66  	filter               *gcs.FilterV2
    67  	filterErr            error
    68  	blockInfo            map[int32]*wallet.BlockInfo
    69  	blockInfoErr         error
    70  	// walletLocked         bool
    71  	acctLocked       bool
    72  	acctUnlockedErr  error
    73  	lockAcctErr      error
    74  	unlockAcctErr    error
    75  	priv             *secp256k1.PrivateKey
    76  	privKeyErr       error
    77  	txDetails        *udb.TxDetails
    78  	txDetailsErr     error
    79  	remotePeers      map[string]*p2p.RemotePeer
    80  	spvBlocks        []*wire.MsgBlock
    81  	spvBlocksErr     error
    82  	unlockedOutpoint *wire.OutPoint
    83  	lockedOutpoint   *wire.OutPoint
    84  	stakeInfo        wallet.StakeInfoData
    85  	rescanUpdates    []wallet.RescanProgress
    86  }
    87  
    88  func (w *tDcrWallet) KnownAddress(ctx context.Context, a stdaddr.Address) (wallet.KnownAddress, error) {
    89  	return w.knownAddr, w.knownAddrErr
    90  }
    91  
    92  func (w *tDcrWallet) AccountNumber(ctx context.Context, accountName string) (uint32, error) {
    93  	return 0, nil
    94  }
    95  
    96  func (w *tDcrWallet) NextAccount(ctx context.Context, name string) (uint32, error) {
    97  	return 0, fmt.Errorf("not stubbed")
    98  }
    99  
   100  func (w *tDcrWallet) AccountBalance(ctx context.Context, account uint32, confirms int32) (wallet.Balances, error) {
   101  	return w.acctBal, w.acctBalErr
   102  }
   103  
   104  func (w *tDcrWallet) LockedOutpoints(ctx context.Context, accountName string) ([]chainjson.TransactionInput, error) {
   105  	return w.lockedPts, w.lockedPtsErr
   106  }
   107  
   108  func (w *tDcrWallet) ListUnspent(ctx context.Context, minconf, maxconf int32, addresses map[string]struct{}, accountName string) ([]*walletjson.ListUnspentResult, error) {
   109  	return w.unspents, w.listUnspentErr
   110  }
   111  
   112  func (w *tDcrWallet) UnlockOutpoint(txHash *chainhash.Hash, index uint32) {
   113  	w.unlockedOutpoint = &wire.OutPoint{
   114  		Hash:  *txHash,
   115  		Index: index,
   116  	}
   117  }
   118  
   119  func (w *tDcrWallet) LockOutpoint(txHash *chainhash.Hash, index uint32) {
   120  	w.lockedOutpoint = &wire.OutPoint{
   121  		Hash:  *txHash,
   122  		Index: index,
   123  	}
   124  }
   125  
   126  func (w *tDcrWallet) ListTransactionDetails(ctx context.Context, txHash *chainhash.Hash) ([]walletjson.ListTransactionsResult, error) {
   127  	return w.listTxs, w.listTxsErr
   128  }
   129  
   130  func (w *tDcrWallet) MixAccount(ctx context.Context, changeAccount, mixAccount, mixBranch uint32) error {
   131  	return fmt.Errorf("not stubbed")
   132  }
   133  
   134  func (w *tDcrWallet) MainChainTip(ctx context.Context) (hash chainhash.Hash, height int32) {
   135  	return w.tip.hash, w.tip.height
   136  }
   137  
   138  func (w *tDcrWallet) MainTipChangedNotifications() (chan *wallet.MainTipChangedNotification, func()) {
   139  	return nil, nil
   140  }
   141  
   142  func (w *tDcrWallet) NewExternalAddress(ctx context.Context, account uint32, callOpts ...wallet.NextAddressCallOption) (stdaddr.Address, error) {
   143  	return w.extAddr, w.extAddrErr
   144  }
   145  
   146  func (w *tDcrWallet) NewInternalAddress(ctx context.Context, account uint32, callOpts ...wallet.NextAddressCallOption) (stdaddr.Address, error) {
   147  	return w.intAddr, w.intAddrErr
   148  }
   149  
   150  func (w *tDcrWallet) SignTransaction(ctx context.Context, tx *wire.MsgTx, hashType txscript.SigHashType,
   151  	additionalPrevScripts map[wire.OutPoint][]byte, additionalKeysByAddress map[string]*dcrutil.WIF,
   152  	p2shRedeemScriptsByAddress map[string][]byte) ([]wallet.SignatureError, error) {
   153  
   154  	return w.sigErrs, w.signTxErr
   155  }
   156  
   157  func (w *tDcrWallet) PublishTransaction(ctx context.Context, tx *wire.MsgTx, n wallet.NetworkBackend) (*chainhash.Hash, error) {
   158  	if w.publishTxErr != nil {
   159  		return nil, w.publishTxErr
   160  	}
   161  	h := tx.TxHash()
   162  	return &h, nil
   163  }
   164  
   165  func (w *tDcrWallet) BlockHeader(ctx context.Context, blockHash *chainhash.Hash) (*wire.BlockHeader, error) {
   166  	return w.blockHeader[*blockHash], w.blockHeaderErr[*blockHash]
   167  }
   168  
   169  func (w *tDcrWallet) BlockInMainChain(ctx context.Context, hash *chainhash.Hash) (haveBlock, invalidated bool, err error) {
   170  	return !w.mainchainDontHave, w.mainchainInvalidated, w.mainchainErr
   171  }
   172  
   173  func (w *tDcrWallet) CFilterV2(ctx context.Context, blockHash *chainhash.Hash) ([gcs.KeySize]byte, *gcs.FilterV2, error) {
   174  	return w.filterKey, w.filter, w.filterErr
   175  }
   176  
   177  func blockIDHeight(blockID *wallet.BlockIdentifier) int32 {
   178  	const fieldIndex = 0
   179  	rf := reflect.ValueOf(blockID).Elem().Field(fieldIndex)
   180  	return reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem().Interface().(int32)
   181  }
   182  
   183  func (w *tDcrWallet) makeBlocks(from, to int32) {
   184  	var prevHash chainhash.Hash
   185  	for i := from; i <= to; i++ {
   186  		hdr := &wire.BlockHeader{
   187  			Height:    uint32(i),
   188  			PrevBlock: prevHash,
   189  			Timestamp: time.Unix(int64(i), 0),
   190  		}
   191  		h := hdr.BlockHash()
   192  		w.blockHeader[h] = hdr
   193  		w.blockInfo[i] = &wallet.BlockInfo{
   194  			Hash: h,
   195  		}
   196  		prevHash = h
   197  	}
   198  }
   199  
   200  func (w *tDcrWallet) BlockInfo(ctx context.Context, blockID *wallet.BlockIdentifier) (*wallet.BlockInfo, error) {
   201  	return w.blockInfo[blockIDHeight(blockID)], w.blockInfoErr
   202  }
   203  
   204  func (w *tDcrWallet) AccountHasPassphrase(ctx context.Context, account uint32) (bool, error) {
   205  	return false, fmt.Errorf("not stubbed")
   206  }
   207  
   208  func (w *tDcrWallet) SetAccountPassphrase(ctx context.Context, account uint32, passphrase []byte) error {
   209  	return fmt.Errorf("not stubbed")
   210  }
   211  
   212  func (w *tDcrWallet) AccountUnlocked(ctx context.Context, account uint32) (bool, error) {
   213  	return !w.acctLocked, w.acctUnlockedErr
   214  }
   215  
   216  func (w *tDcrWallet) LockAccount(ctx context.Context, account uint32) error {
   217  	return w.lockAcctErr
   218  }
   219  
   220  func (w *tDcrWallet) UnlockAccount(ctx context.Context, account uint32, passphrase []byte) error {
   221  	return w.unlockAcctErr
   222  }
   223  
   224  func (w *tDcrWallet) Unlock(ctx context.Context, passphrase []byte, timeout <-chan time.Time) error {
   225  	return fmt.Errorf("not stubbed")
   226  }
   227  
   228  func (w *tDcrWallet) LoadPrivateKey(ctx context.Context, addr stdaddr.Address) (key *secp256k1.PrivateKey, zero func(), err error) {
   229  	return w.priv, func() {}, w.privKeyErr
   230  }
   231  
   232  func (w *tDcrWallet) TxDetails(ctx context.Context, txHash *chainhash.Hash) (*udb.TxDetails, error) {
   233  	return w.txDetails, w.txDetailsErr
   234  }
   235  
   236  func (w *tDcrWallet) Synced(context.Context) (bool, int32) {
   237  	return true, 0
   238  }
   239  
   240  func (w *tDcrWallet) GetRemotePeers() map[string]*p2p.RemotePeer {
   241  	return w.remotePeers
   242  }
   243  
   244  func (w *tDcrWallet) Blocks(ctx context.Context, blockHashes []*chainhash.Hash) ([]*wire.MsgBlock, error) {
   245  	return w.spvBlocks, w.spvBlocksErr
   246  }
   247  
   248  func (w *tDcrWallet) GetTransactionsByHashes(ctx context.Context, txHashes []*chainhash.Hash) (
   249  	txs []*wire.MsgTx, notFound []*wire.InvVect, err error) {
   250  
   251  	return w.txsByHash, nil, w.txsByHashErr
   252  }
   253  
   254  func (w *tDcrWallet) StakeInfo(ctx context.Context) (*wallet.StakeInfoData, error) {
   255  	return &w.stakeInfo, nil
   256  }
   257  
   258  func (w *tDcrWallet) PurchaseTickets(context.Context, wallet.NetworkBackend, *wallet.PurchaseTicketsRequest) (*wallet.PurchaseTicketsResponse, error) {
   259  	return nil, nil
   260  }
   261  
   262  func (w *tDcrWallet) ForUnspentUnexpiredTickets(ctx context.Context, f func(hash *chainhash.Hash) error) error {
   263  	return nil
   264  }
   265  
   266  func (w *tDcrWallet) GetTickets(ctx context.Context, f func([]*wallet.TicketSummary, *wire.BlockHeader) (bool, error), startBlock, endBlock *wallet.BlockIdentifier) error {
   267  	return nil
   268  }
   269  
   270  func (w *tDcrWallet) AgendaChoices(ctx context.Context, ticketHash *chainhash.Hash) (choices map[string]string, voteBits uint16, err error) {
   271  	return nil, 0, nil
   272  }
   273  
   274  func (w *tDcrWallet) TreasuryKeyPolicies() []wallet.TreasuryKeyPolicy {
   275  	return nil
   276  }
   277  
   278  func (w *tDcrWallet) GetAllTSpends(ctx context.Context) []*wire.MsgTx {
   279  	return nil
   280  }
   281  
   282  func (w *tDcrWallet) TSpendPolicy(tspendHash, ticketHash *chainhash.Hash) stake.TreasuryVoteT {
   283  	return 0
   284  }
   285  
   286  func (w *tDcrWallet) VSPHostForTicket(ctx context.Context, ticketHash *chainhash.Hash) (string, error) {
   287  	return "", nil
   288  }
   289  
   290  func (w *tDcrWallet) SetAgendaChoices(ctx context.Context, ticketHash *chainhash.Hash, choices map[string]string) (voteBits uint16, err error) {
   291  	return 0, nil
   292  }
   293  
   294  func (w *tDcrWallet) SetTSpendPolicy(ctx context.Context, tspendHash *chainhash.Hash, policy stake.TreasuryVoteT, ticketHash *chainhash.Hash) error {
   295  	return nil
   296  }
   297  
   298  func (w *tDcrWallet) SetTreasuryKeyPolicy(ctx context.Context, pikey []byte, policy stake.TreasuryVoteT, ticketHash *chainhash.Hash) error {
   299  	return nil
   300  }
   301  
   302  func (w *tDcrWallet) Spender(ctx context.Context, out *wire.OutPoint) (*wire.MsgTx, uint32, error) {
   303  	return nil, 0, nil
   304  }
   305  
   306  func (w *tDcrWallet) ChainParams() *chaincfg.Params {
   307  	return nil
   308  }
   309  
   310  func (w *tDcrWallet) TxBlock(ctx context.Context, hash *chainhash.Hash) (chainhash.Hash, int32, error) {
   311  	return chainhash.Hash{}, 0, nil
   312  }
   313  
   314  func (w *tDcrWallet) DumpWIFPrivateKey(ctx context.Context, addr stdaddr.Address) (string, error) {
   315  	return "", nil
   316  }
   317  
   318  func (w *tDcrWallet) VSPFeeHashForTicket(ctx context.Context, ticketHash *chainhash.Hash) (chainhash.Hash, error) {
   319  	return chainhash.Hash{}, nil
   320  }
   321  
   322  func (w *tDcrWallet) UpdateVspTicketFeeToStarted(ctx context.Context, ticketHash, feeHash *chainhash.Hash, host string, pubkey []byte) error {
   323  	return nil
   324  }
   325  
   326  func (w *tDcrWallet) ReserveOutputsForAmount(ctx context.Context, account uint32, amount dcrutil.Amount, minconf int32) ([]wallet.Input, error) {
   327  	return nil, nil
   328  }
   329  
   330  func (w *tDcrWallet) NewChangeAddress(ctx context.Context, account uint32) (stdaddr.Address, error) {
   331  	return nil, nil
   332  }
   333  
   334  func (w *tDcrWallet) RelayFee() dcrutil.Amount {
   335  	return 0
   336  }
   337  
   338  func (w *tDcrWallet) SetPublished(ctx context.Context, hash *chainhash.Hash, published bool) error {
   339  	return nil
   340  }
   341  
   342  func (w *tDcrWallet) AddTransaction(ctx context.Context, tx *wire.MsgTx, blockHash *chainhash.Hash) error {
   343  	return nil
   344  }
   345  
   346  func (w *tDcrWallet) UpdateVspTicketFeeToPaid(ctx context.Context, ticketHash, feeHash *chainhash.Hash, host string, pubkey []byte) error {
   347  	return nil
   348  }
   349  
   350  func (w *tDcrWallet) NetworkBackend() (wallet.NetworkBackend, error) {
   351  	return nil, nil
   352  }
   353  
   354  func (w *tDcrWallet) RevokeTickets(ctx context.Context, rpcCaller wallet.Caller) error {
   355  	return nil
   356  }
   357  
   358  func (w *tDcrWallet) UpdateVspTicketFeeToErrored(ctx context.Context, ticketHash *chainhash.Hash, host string, pubkey []byte) error {
   359  	return nil
   360  }
   361  
   362  func (w *tDcrWallet) TSpendPolicyForTicket(ticketHash *chainhash.Hash) map[string]string {
   363  	return nil
   364  }
   365  
   366  func (w *tDcrWallet) TreasuryKeyPolicyForTicket(ticketHash *chainhash.Hash) map[string]string {
   367  	return nil
   368  }
   369  
   370  func (w *tDcrWallet) AbandonTransaction(ctx context.Context, hash *chainhash.Hash) error {
   371  	return nil
   372  }
   373  
   374  func (w *tDcrWallet) TxConfirms(ctx context.Context, hash *chainhash.Hash) (int32, error) {
   375  	return 0, nil
   376  }
   377  
   378  func (w *tDcrWallet) IsVSPTicketConfirmed(ctx context.Context, ticketHash *chainhash.Hash) (bool, error) {
   379  	return false, nil
   380  }
   381  
   382  func (w *tDcrWallet) UpdateVspTicketFeeToConfirmed(ctx context.Context, ticketHash, feeHash *chainhash.Hash, host string, pubkey []byte) error {
   383  	return nil
   384  }
   385  
   386  func (w *tDcrWallet) VSPTicketInfo(ctx context.Context, ticketHash *chainhash.Hash) (*wallet.VSPTicket, error) {
   387  	return nil, nil
   388  }
   389  
   390  func (w *tDcrWallet) SignMessage(ctx context.Context, msg string, addr stdaddr.Address) (sig []byte, err error) {
   391  	return nil, nil
   392  }
   393  
   394  func (w *tDcrWallet) SetRelayFee(relayFee dcrutil.Amount) {}
   395  
   396  func (w *tDcrWallet) GetTicketInfo(ctx context.Context, hash *chainhash.Hash) (*wallet.TicketSummary, *wire.BlockHeader, error) {
   397  	return nil, nil, nil
   398  }
   399  
   400  func (w *tDcrWallet) ListSinceBlock(ctx context.Context, start, end, syncHeight int32) ([]walletjson.ListTransactionsResult, error) {
   401  	return nil, nil
   402  }
   403  
   404  func (w *tDcrWallet) AddressAtIdx(ctx context.Context, account, branch, childIdx uint32) (stdaddr.Address, error) {
   405  	return nil, nil
   406  }
   407  func (w *tDcrWallet) CreateVspPayment(ctx context.Context, tx *wire.MsgTx, fee dcrutil.Amount,
   408  	feeAddr stdaddr.Address, feeAcct uint32, changeAcct uint32) error {
   409  	return nil
   410  }
   411  
   412  func (w *tDcrWallet) NewVSPTicket(ctx context.Context, hash *chainhash.Hash) (*wallet.VSPTicket, error) {
   413  	return nil, nil
   414  }
   415  
   416  func (w *tDcrWallet) GetTransactions(ctx context.Context, f func(*wallet.Block) (bool, error), startBlock, endBlock *wallet.BlockIdentifier) error {
   417  	return nil
   418  }
   419  
   420  func (w *tDcrWallet) RescanProgressFromHeight(ctx context.Context, n wallet.NetworkBackend, startHeight int32, p chan<- wallet.RescanProgress) {
   421  	go func() {
   422  		defer close(p)
   423  		for _, u := range w.rescanUpdates {
   424  			p <- u
   425  		}
   426  	}()
   427  }
   428  
   429  func (w *tDcrWallet) RescanPoint(ctx context.Context) (*chainhash.Hash, error) {
   430  	return nil, nil
   431  }
   432  
   433  func tNewSpvWallet() (*spvWallet, *tDcrWallet) {
   434  	dcrw := &tDcrWallet{
   435  		blockInfo:      make(map[int32]*wallet.BlockInfo),
   436  		blockHeader:    make(map[chainhash.Hash]*wire.BlockHeader),
   437  		blockHeaderErr: make(map[chainhash.Hash]error),
   438  	}
   439  	return &spvWallet{
   440  		dcrWallet: dcrw,
   441  		spv:       dcrw,
   442  		log:       dex.StdOutLogger("T", dex.LevelTrace),
   443  		blockCache: blockCache{
   444  			blocks: make(map[chainhash.Hash]*cachedBlock),
   445  		},
   446  	}, dcrw
   447  }
   448  
   449  type tKnownAddress struct {
   450  	stdaddr.Address
   451  	acctName string
   452  	acctType wallet.AccountKind // 0-value is AccountKindBIP0044
   453  }
   454  
   455  func (a *tKnownAddress) AccountName() string {
   456  	return a.acctName
   457  }
   458  
   459  func (a *tKnownAddress) AccountKind() wallet.AccountKind {
   460  	return a.acctType
   461  }
   462  
   463  func (a *tKnownAddress) ScriptLen() int { return 1 }
   464  
   465  var _ wallet.KnownAddress = (*tKnownAddress)(nil)
   466  
   467  func TestAccountOwnsAddress(t *testing.T) {
   468  	w, dcrw := tNewSpvWallet()
   469  
   470  	kaddr := &tKnownAddress{
   471  		Address:  tPKHAddr,
   472  		acctName: tAcctName,
   473  	}
   474  	dcrw.knownAddr = kaddr
   475  
   476  	// Initial success
   477  	if have, err := w.AccountOwnsAddress(tCtx, tPKHAddr, tAcctName); err != nil {
   478  		t.Fatalf("initial success trial failed: %v", err)
   479  	} else if !have {
   480  		t.Fatal("failed initial success. have = false")
   481  	}
   482  
   483  	// Foreign address
   484  	dcrw.knownAddrErr = walleterrors.NotExist
   485  	if have, err := w.AccountOwnsAddress(tCtx, tPKHAddr, tAcctName); err != nil {
   486  		t.Fatalf("unexpected error when should just be have = false for foreign address: %v", err)
   487  	} else if have {
   488  		t.Fatalf("shouldn't have, but have for foreign address")
   489  	}
   490  
   491  	// Other KnownAddress error
   492  	dcrw.knownAddrErr = tErr
   493  	if _, err := w.AccountOwnsAddress(tCtx, tPKHAddr, tAcctName); err == nil {
   494  		t.Fatal("no error for KnownAddress error")
   495  	}
   496  	dcrw.knownAddrErr = nil
   497  
   498  	// Wrong account
   499  	kaddr.acctName = "not the right name"
   500  	if have, err := w.AccountOwnsAddress(tCtx, tPKHAddr, tAcctName); err != nil {
   501  		t.Fatalf("unexpected error when should just be have = false for wrong account: %v", err)
   502  	} else if have {
   503  		t.Fatalf("shouldn't have, but have for wrong account")
   504  	}
   505  	kaddr.acctName = tAcctName
   506  
   507  	// Wrong type
   508  	kaddr.acctType = wallet.AccountKindImportedXpub
   509  	if have, err := w.AccountOwnsAddress(tCtx, tPKHAddr, tAcctName); err != nil {
   510  		t.Fatalf("don't have trial failed: %v", err)
   511  	} else if have {
   512  		t.Fatal("have, but shouldn't")
   513  	}
   514  	kaddr.acctType = wallet.AccountKindBIP0044
   515  }
   516  
   517  func TestAccountBalance(t *testing.T) {
   518  	w, dcrw := tNewSpvWallet()
   519  	const amt = 1e8
   520  
   521  	dcrw.acctBal = wallet.Balances{
   522  		Spendable: amt,
   523  	}
   524  
   525  	// Initial success
   526  	if bal, err := w.AccountBalance(tCtx, 1, ""); err != nil {
   527  		t.Fatalf("AccountBalance during initial success test: %v", err)
   528  	} else if bal.Spendable != amt/1e8 {
   529  		t.Fatalf("wrong amount. wanted %.0f, got %.0f", amt, bal.Spendable)
   530  	}
   531  
   532  	// AccountBalance error
   533  	dcrw.acctBalErr = tErr
   534  	if _, err := w.AccountBalance(tCtx, 1, ""); err == nil {
   535  		t.Fatal("no error for AccountBalance error")
   536  	}
   537  }
   538  
   539  func TestSimpleErrorPropagation(t *testing.T) {
   540  	w, dcrw := tNewSpvWallet()
   541  	var err error
   542  	tests := map[string]func(){
   543  		"LockedOutputs": func() {
   544  			dcrw.lockedPtsErr = tErr
   545  			_, err = w.LockedOutputs(tCtx, "")
   546  		},
   547  		"Unspents": func() {
   548  			dcrw.listUnspentErr = tErr
   549  			_, err = w.Unspents(tCtx, "")
   550  		},
   551  		"SignRawTransaction.err": func() {
   552  			dcrw.signTxErr = tErr
   553  			_, err = w.SignRawTransaction(tCtx, new(wire.MsgTx))
   554  			dcrw.signTxErr = nil
   555  		},
   556  		"SignRawTransaction.sigErrs": func() {
   557  			dcrw.sigErrs = []wallet.SignatureError{{}}
   558  			_, err = w.SignRawTransaction(tCtx, new(wire.MsgTx))
   559  		},
   560  		"SendRawTransaction": func() {
   561  			dcrw.publishTxErr = tErr
   562  			_, err = w.SendRawTransaction(tCtx, nil, false)
   563  		},
   564  		"ExternalAddress": func() {
   565  			dcrw.extAddrErr = tErr
   566  			_, err = w.ExternalAddress(tCtx, "")
   567  		},
   568  		"InternalAddress": func() {
   569  			dcrw.intAddrErr = tErr
   570  			_, err = w.InternalAddress(tCtx, "")
   571  		},
   572  		"AccountUnlocked": func() {
   573  			dcrw.acctUnlockedErr = tErr
   574  			_, err = w.AccountUnlocked(tCtx, "")
   575  		},
   576  		"LockAccount": func() {
   577  			dcrw.lockAcctErr = tErr
   578  			err = w.LockAccount(tCtx, "")
   579  		},
   580  		"UnlockAccount": func() {
   581  			dcrw.unlockAcctErr = tErr
   582  			err = w.UnlockAccount(tCtx, []byte("abc"), "")
   583  		},
   584  		"AddressPrivKey": func() {
   585  			dcrw.privKeyErr = tErr
   586  			_, err = w.AddressPrivKey(tCtx, tPKHAddr)
   587  		},
   588  	}
   589  
   590  	for name, f := range tests {
   591  		if f(); err == nil {
   592  			t.Fatalf("%q error did not propagate", name)
   593  		}
   594  	}
   595  }
   596  
   597  func TestLockUnlockOutpoints(t *testing.T) {
   598  	w, dcrw := tNewSpvWallet()
   599  	lock := &wire.OutPoint{
   600  		Hash:  chainhash.Hash{0x1},
   601  		Index: 55,
   602  	}
   603  
   604  	w.LockUnspent(nil, false, []*wire.OutPoint{lock})
   605  	if *dcrw.lockedOutpoint != *lock {
   606  		t.Fatalf("outpoint not locked")
   607  	}
   608  
   609  	unlock := &wire.OutPoint{
   610  		Hash:  chainhash.Hash{0x2},
   611  		Index: 555,
   612  	}
   613  	w.LockUnspent(nil, true, []*wire.OutPoint{unlock})
   614  	if *dcrw.unlockedOutpoint != *unlock {
   615  		t.Fatalf("outpoint not unlocked")
   616  	}
   617  }
   618  
   619  func TestUnspentOutput(t *testing.T) {
   620  	w, dcrw := tNewSpvWallet()
   621  
   622  	const txOutIdx = 1
   623  
   624  	dcrw.txDetails = &udb.TxDetails{
   625  		TxRecord: udb.TxRecord{
   626  			MsgTx: wire.MsgTx{
   627  				TxOut: []*wire.TxOut{
   628  					{},
   629  					{},
   630  				},
   631  			},
   632  			TxType: stake.TxTypeRegular,
   633  		},
   634  		Credits: []udb.CreditRecord{
   635  			{
   636  				Index: txOutIdx,
   637  			},
   638  		},
   639  	}
   640  
   641  	dcrw.listTxs = []walletjson.ListTransactionsResult{
   642  		{
   643  			Vout:    txOutIdx,
   644  			Address: tPKHAddr.String(),
   645  		},
   646  	}
   647  
   648  	// Initial success
   649  	if _, err := w.UnspentOutput(tCtx, nil, 1, 0); err != nil {
   650  		t.Fatalf("failed initial success test: %v", err)
   651  	}
   652  
   653  	// TxDetails NotExist error
   654  	dcrw.txDetailsErr = walleterrors.NotExist
   655  	if _, err := w.UnspentOutput(tCtx, nil, 1, 0); !errors.Is(err, asset.CoinNotFoundError) {
   656  		t.Fatalf("expected asset.CoinNotFoundError, got %v", err)
   657  	}
   658  
   659  	// TxDetails generic error
   660  	dcrw.txDetailsErr = tErr
   661  	if _, err := w.UnspentOutput(tCtx, nil, 1, 0); err == nil {
   662  		t.Fatalf("expected TxDetail generic error to propagate")
   663  	}
   664  	dcrw.txDetailsErr = nil
   665  
   666  	// ListTransactionDetails error
   667  	dcrw.listTxsErr = tErr
   668  	if _, err := w.UnspentOutput(tCtx, nil, 1, 0); err == nil {
   669  		t.Fatalf("expected ListTransactionDetails error to propagate")
   670  	}
   671  	dcrw.listTxsErr = nil
   672  
   673  	// output not found
   674  	dcrw.listTxs[0].Vout = txOutIdx + 1
   675  	if _, err := w.UnspentOutput(tCtx, nil, 1, 0); err == nil {
   676  		t.Fatalf("expected ListTransactionDetails output not found")
   677  	}
   678  	dcrw.listTxs[0].Vout = txOutIdx
   679  
   680  	// Not enough outputs
   681  	dcrw.txDetails.MsgTx.TxOut = dcrw.txDetails.MsgTx.TxOut[:1]
   682  	if _, err := w.UnspentOutput(tCtx, nil, 1, 0); err == nil {
   683  		t.Fatalf("expected error for too few TxDetails outputs")
   684  	}
   685  	dcrw.txDetails.MsgTx.AddTxOut(new(wire.TxOut))
   686  
   687  	// Credit spent
   688  	dcrw.txDetails.Credits[0].Spent = true
   689  	if _, err := w.UnspentOutput(tCtx, nil, 1, 0); err == nil {
   690  		t.Fatalf("expected error TxDetail output spent")
   691  	}
   692  	dcrw.txDetails.Credits[0].Spent = false
   693  
   694  	// Output not found
   695  	dcrw.txDetails.Credits[0].Index = txOutIdx + 1
   696  	if _, err := w.UnspentOutput(tCtx, nil, 1, 0); !errors.Is(err, asset.CoinNotFoundError) {
   697  		t.Fatalf("expected asset.CoinNotFoundError for not our output, got %v", err)
   698  	}
   699  	dcrw.txDetails.Credits[0].Index = txOutIdx
   700  
   701  	// ensure we can recover success
   702  	if _, err := w.UnspentOutput(tCtx, nil, 1, 0); err != nil {
   703  		t.Fatalf("failed final success test: %v", err)
   704  	}
   705  }
   706  
   707  func TestGetBlockHeader(t *testing.T) {
   708  	w, dcrw := tNewSpvWallet()
   709  
   710  	const tipHeight = 12
   711  	const blockHeight = 11 // 2 confirmations
   712  	dcrw.makeBlocks(0, tipHeight)
   713  	blockHash := dcrw.blockInfo[blockHeight].Hash
   714  	blockHash5 := dcrw.blockInfo[5].Hash
   715  	tipHash := dcrw.blockInfo[tipHeight].Hash
   716  
   717  	dcrw.tip.hash = tipHash
   718  	dcrw.tip.height = tipHeight
   719  
   720  	hdr, err := w.GetBlockHeader(tCtx, &blockHash)
   721  	if err != nil {
   722  		t.Fatalf("initial success error: %v", err)
   723  	}
   724  
   725  	if hdr.BlockHash() != blockHash {
   726  		t.Fatal("wrong header?")
   727  	}
   728  
   729  	if hdr.MedianTime != tipHeight/2 {
   730  		t.Fatalf("wrong median time. wanted %d, got %d", tipHeight/2, hdr.MedianTime)
   731  	}
   732  
   733  	if hdr.Confirmations != 2 {
   734  		t.Fatalf("expected 2 confs, got %d", hdr.Confirmations)
   735  	}
   736  
   737  	if *hdr.NextHash != tipHash {
   738  		t.Fatal("wrong next hash")
   739  	}
   740  
   741  	dcrw.mainchainDontHave = true
   742  	hdr, err = w.GetBlockHeader(tCtx, &blockHash)
   743  	if err != nil {
   744  		t.Fatalf("initial success error: %v", err)
   745  	}
   746  	if hdr.Confirmations != -1 {
   747  		t.Fatalf("expected -1 confs for side chain block, got %d", hdr.Confirmations)
   748  	}
   749  	dcrw.mainchainDontHave = false
   750  
   751  	// BlockHeader error
   752  	dcrw.blockHeaderErr[blockHash] = tErr
   753  	if _, err := w.GetBlockHeader(tCtx, &blockHash); err == nil {
   754  		t.Fatalf("BlockHeader error not propagated")
   755  	}
   756  	dcrw.blockHeaderErr[blockHash] = nil
   757  
   758  	// medianTime error
   759  	dcrw.blockHeaderErr[blockHash5] = tErr
   760  	if _, err := w.GetBlockHeader(tCtx, &blockHash); err == nil {
   761  		t.Fatalf("medianTime error not propagated")
   762  	}
   763  	dcrw.blockHeaderErr[blockHash5] = nil
   764  
   765  	// MainChainTip error
   766  	dcrw.tip.height = 3
   767  	if hdr, err := w.GetBlockHeader(tCtx, &blockHash); err != nil {
   768  		t.Fatalf("invalid tip height not noticed")
   769  	} else if hdr.Confirmations != 0 {
   770  		t.Fatalf("confirmations not zero for lower tip height")
   771  	}
   772  	dcrw.tip.height = tipHeight
   773  
   774  	// GetBlockHash error
   775  	dcrw.blockInfoErr = tErr
   776  	if _, err := w.GetBlockHeader(tCtx, &blockHash); err == nil {
   777  		t.Fatalf("BlockInfo error not propagated")
   778  	}
   779  	dcrw.blockInfoErr = nil
   780  
   781  	_, err = w.GetBlockHeader(tCtx, &blockHash)
   782  	if err != nil {
   783  		t.Fatalf("final success error: %v", err)
   784  	}
   785  }
   786  
   787  func TestGetBlock(t *testing.T) {
   788  	// 2 success routes, cached and uncached.
   789  	w, dcrw := tNewSpvWallet()
   790  
   791  	hdr := wire.BlockHeader{Height: 5}
   792  	blockHash := hdr.BlockHash()
   793  	msgBlock := &wire.MsgBlock{Header: hdr}
   794  	dcrw.spvBlocks = []*wire.MsgBlock{msgBlock}
   795  
   796  	// uncached
   797  	blk, err := w.GetBlock(tCtx, &blockHash)
   798  	if err != nil {
   799  		t.Fatalf("initial success (uncached) error: %v", err)
   800  	}
   801  	delete(w.blockCache.blocks, blockHash)
   802  
   803  	if blk.BlockHash() != blockHash {
   804  		t.Fatalf("wrong hash")
   805  	}
   806  
   807  	// Blocks error
   808  	dcrw.spvBlocksErr = tErr
   809  	if _, err := w.GetBlock(tCtx, &blockHash); err == nil {
   810  		t.Fatalf("Blocks error not propagated")
   811  	}
   812  	dcrw.spvBlocksErr = nil
   813  
   814  	// No blocks
   815  	dcrw.spvBlocks = []*wire.MsgBlock{}
   816  	if _, err := w.GetBlock(tCtx, &blockHash); err == nil {
   817  		t.Fatalf("empty Blocks didn't generate an error")
   818  	}
   819  	dcrw.spvBlocks = []*wire.MsgBlock{msgBlock}
   820  
   821  	if _, err = w.GetBlock(tCtx, &blockHash); err != nil {
   822  		t.Fatalf("final success (uncached) error: %v", err)
   823  	}
   824  
   825  	// The block should be cached
   826  	if w.blockCache.blocks[blockHash] == nil {
   827  		t.Fatalf("block not cached")
   828  	}
   829  
   830  	// Zero the time. Then check to make sure it was updated.
   831  	// We can also add back our Blocks error, because with a cached block,
   832  	// we'll never get there.
   833  	dcrw.spvBlocksErr = tErr
   834  	w.blockCache.blocks[blockHash].lastAccess = time.Time{}
   835  	if _, err = w.GetBlock(tCtx, &blockHash); err != nil {
   836  		t.Fatalf("final success (cached) error: %v", err)
   837  	}
   838  	if w.blockCache.blocks[blockHash].lastAccess.IsZero() {
   839  		t.Fatalf("lastAccess stamp not updated")
   840  	}
   841  }
   842  
   843  func TestGetTransaction(t *testing.T) {
   844  	w, dcrw := tNewSpvWallet()
   845  
   846  	const txOutIdx = 1
   847  
   848  	dcrw.txDetails = &udb.TxDetails{
   849  		TxRecord: udb.TxRecord{
   850  			MsgTx: wire.MsgTx{
   851  				TxOut: []*wire.TxOut{
   852  					{},
   853  					{},
   854  				},
   855  			},
   856  			TxType: stake.TxTypeRegular,
   857  		},
   858  		Credits: []udb.CreditRecord{
   859  			{
   860  				Index: txOutIdx,
   861  			},
   862  		},
   863  		Block: udb.BlockMeta{
   864  			Block: udb.Block{
   865  				Height: 2,
   866  			},
   867  		},
   868  	}
   869  
   870  	dcrw.tip.height = 3 // 2 confirmations
   871  
   872  	dcrw.listTxs = []walletjson.ListTransactionsResult{
   873  		{
   874  			Vout:    txOutIdx,
   875  			Address: tPKHAddr.String(),
   876  		},
   877  	}
   878  
   879  	txHash := &chainhash.Hash{0x12}
   880  	tx, err := w.GetTransaction(tCtx, txHash)
   881  	if err != nil {
   882  		t.Fatalf("initial success error: %v", err)
   883  	}
   884  
   885  	if tx.Confirmations != 2 {
   886  		t.Fatalf("expected 2 confirmations, got %d", tx.Confirmations)
   887  	}
   888  
   889  	// TxDetails NotExist error
   890  	dcrw.txDetailsErr = walleterrors.NotExist
   891  	if _, err := w.GetTransaction(tCtx, txHash); !errors.Is(err, asset.CoinNotFoundError) {
   892  		t.Fatalf("expected asset.CoinNotFoundError, got %v", err)
   893  	}
   894  
   895  	// TxDetails generic error
   896  	dcrw.txDetailsErr = tErr
   897  	if _, err := w.GetTransaction(tCtx, txHash); err == nil {
   898  		t.Fatalf("expected TxDetail generic error to propagate")
   899  	}
   900  	dcrw.txDetailsErr = nil
   901  
   902  	// ListTransactionDetails error
   903  	dcrw.listTxsErr = tErr
   904  	if _, err := w.UnspentOutput(tCtx, nil, 1, 0); err == nil {
   905  		t.Fatalf("expected ListTransactionDetails error to propagate")
   906  	}
   907  	dcrw.listTxsErr = nil
   908  
   909  	if _, err := w.GetTransaction(tCtx, txHash); err != nil {
   910  		t.Fatalf("final success error: %v", err)
   911  	}
   912  }
   913  
   914  func TestMatchAnyScript(t *testing.T) {
   915  	w, dcrw := tNewSpvWallet()
   916  
   917  	const (
   918  		// dcrd/gcs/blockcf2/blockcf.go
   919  		B = 19
   920  		M = 784931
   921  	)
   922  
   923  	// w.filterKey, w.filter, w.filterErr
   924  
   925  	key := [gcs.KeySize]byte{0xb2}
   926  	var blockHash chainhash.Hash
   927  	copy(blockHash[:], key[:])
   928  
   929  	pkScript := encode.RandomBytes(25)
   930  	filter, err := gcs.NewFilterV2(B, M, key, [][]byte{pkScript})
   931  	if err != nil {
   932  		t.Fatalf("NewFilterV2 error: %v", err)
   933  	}
   934  	dcrw.filterKey = key
   935  	dcrw.filter = filter
   936  
   937  	match, err := w.MatchAnyScript(tCtx, &blockHash, [][]byte{pkScript})
   938  	if err != nil {
   939  		t.Fatalf("MatchAnyScript error: %v", err)
   940  	}
   941  
   942  	if !match {
   943  		t.Fatalf("no match reported")
   944  	}
   945  
   946  	dcrw.filterErr = tErr
   947  	if _, err := w.MatchAnyScript(tCtx, &blockHash, [][]byte{pkScript}); err == nil {
   948  		t.Fatalf("CFilterV2 error did not propagate")
   949  	}
   950  }
   951  
   952  func TestBirthdayBlockHeight(t *testing.T) {
   953  	spvw, dcrw := tNewSpvWallet()
   954  
   955  	const tipHeight = 10
   956  	dcrw.makeBlocks(0, tipHeight)
   957  	w := &NativeWallet{
   958  		ExchangeWallet: &ExchangeWallet{
   959  			wallet: spvw,
   960  			log:    dex.StdOutLogger("T", dex.LevelInfo),
   961  		},
   962  		spvw: spvw,
   963  	}
   964  	w.currentTip.Store(&block{height: tipHeight})
   965  
   966  	if h := w.birthdayBlockHeight(tCtx, 5); h != 4 {
   967  		t.Fatalf("expected block 4, got %d", h)
   968  	}
   969  
   970  	if h := w.birthdayBlockHeight(tCtx, tipHeight); h != 0 {
   971  		t.Fatalf("expected zero for birthday from the future, got %d", h)
   972  	}
   973  
   974  	if h := w.birthdayBlockHeight(tCtx, 0); h != 0 {
   975  		t.Fatalf("expected zero for genesis birthday, got %d", h)
   976  	}
   977  }
   978  
   979  func TestRescan(t *testing.T) {
   980  	const tipHeight = 20
   981  
   982  	spvw, dcrw := tNewSpvWallet()
   983  	w := &NativeWallet{
   984  		ExchangeWallet: &ExchangeWallet{
   985  			wallet: spvw,
   986  			log:    dex.StdOutLogger("T", dex.LevelInfo),
   987  		},
   988  		spvw: spvw,
   989  	}
   990  	w.currentTip.Store(&block{height: tipHeight})
   991  
   992  	dcrw.makeBlocks(0, tipHeight)
   993  
   994  	ctx, cancel := context.WithCancel(context.Background())
   995  	defer cancel()
   996  
   997  	ensureErr := func(errStr string, us []wallet.RescanProgress) {
   998  		t.Helper()
   999  		defer w.wg.Wait()
  1000  		dcrw.rescanUpdates = us
  1001  		err := w.Rescan(ctx, 0)
  1002  		if err == nil {
  1003  			if errStr == "" {
  1004  				return
  1005  			}
  1006  			t.Fatalf("No error. Expected %q", errStr)
  1007  		}
  1008  		if !strings.Contains(err.Error(), errStr) {
  1009  			t.Fatalf("Wrong error %q. Expected %q", err, errStr)
  1010  		}
  1011  	}
  1012  
  1013  	// No updates is an error.
  1014  	ensureErr("rescan finished without a progress update", nil)
  1015  
  1016  	// Initial error comes straight through.
  1017  	tErr := errors.New("test error")
  1018  	ensureErr("test error", []wallet.RescanProgress{{Err: tErr}})
  1019  
  1020  	// Any progress update = no error.
  1021  	ensureErr("", []wallet.RescanProgress{{}, {Err: tErr}})
  1022  
  1023  	// Rescan in progress error
  1024  	w.rescan.Lock()
  1025  	w.rescan.progress = &rescanProgress{}
  1026  	w.rescan.Unlock()
  1027  	ensureErr("rescan already in progress", []wallet.RescanProgress{{}})
  1028  }