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 }