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 }