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