decred.org/dcrwallet/v3@v3.1.0/wallet/wallet.go (about) 1 // Copyright (c) 2013-2016 The btcsuite developers 2 // Copyright (c) 2015-2021 The Decred developers 3 // Use of this source code is governed by an ISC 4 // license that can be found in the LICENSE file. 5 6 package wallet 7 8 import ( 9 "bytes" 10 "context" 11 "encoding/binary" 12 "encoding/hex" 13 "fmt" 14 "math/big" 15 "runtime" 16 "sort" 17 "strconv" 18 "strings" 19 "sync" 20 "sync/atomic" 21 "time" 22 23 "decred.org/dcrwallet/v3/deployments" 24 "decred.org/dcrwallet/v3/errors" 25 "decred.org/dcrwallet/v3/internal/compat" 26 "decred.org/dcrwallet/v3/rpc/client/dcrd" 27 "decred.org/dcrwallet/v3/rpc/jsonrpc/types" 28 "decred.org/dcrwallet/v3/validate" 29 "decred.org/dcrwallet/v3/wallet/txrules" 30 "decred.org/dcrwallet/v3/wallet/txsizes" 31 "decred.org/dcrwallet/v3/wallet/udb" 32 "decred.org/dcrwallet/v3/wallet/walletdb" 33 34 "github.com/decred/dcrd/blockchain/stake/v5" 35 blockchain "github.com/decred/dcrd/blockchain/standalone/v2" 36 "github.com/decred/dcrd/chaincfg/chainhash" 37 "github.com/decred/dcrd/chaincfg/v3" 38 "github.com/decred/dcrd/crypto/blake256" 39 "github.com/decred/dcrd/dcrec" 40 "github.com/decred/dcrd/dcrec/secp256k1/v4" 41 "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" 42 "github.com/decred/dcrd/dcrutil/v4" 43 gcs2 "github.com/decred/dcrd/gcs/v4" 44 "github.com/decred/dcrd/hdkeychain/v3" 45 dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v4" 46 "github.com/decred/dcrd/txscript/v4" 47 "github.com/decred/dcrd/txscript/v4/sign" 48 "github.com/decred/dcrd/txscript/v4/stdaddr" 49 "github.com/decred/dcrd/txscript/v4/stdscript" 50 "github.com/decred/dcrd/wire" 51 "golang.org/x/sync/errgroup" 52 ) 53 54 const ( 55 // InsecurePubPassphrase is the default outer encryption passphrase used 56 // for public data (everything but private keys). Using a non-default 57 // public passphrase can prevent an attacker without the public 58 // passphrase from discovering all past and future wallet addresses if 59 // they gain access to the wallet database. 60 // 61 // NOTE: at time of writing, public encryption only applies to public 62 // data in the waddrmgr namespace. Transactions are not yet encrypted. 63 InsecurePubPassphrase = "public" 64 ) 65 66 var ( 67 // SimulationPassphrase is the password for a wallet created for simnet 68 // with --createtemp. 69 SimulationPassphrase = []byte("password") 70 ) 71 72 // Namespace bucket keys. 73 var ( 74 waddrmgrNamespaceKey = []byte("waddrmgr") 75 wtxmgrNamespaceKey = []byte("wtxmgr") 76 wstakemgrNamespaceKey = []byte("wstakemgr") 77 ) 78 79 // The assumed output script version is defined to assist with refactoring to 80 // use actual script versions. 81 const scriptVersionAssumed = 0 82 83 // StakeDifficultyInfo is a container for stake difficulty information updates. 84 type StakeDifficultyInfo struct { 85 BlockHash *chainhash.Hash 86 BlockHeight int64 87 StakeDifficulty int64 88 } 89 90 // outpoint is a wire.OutPoint without specifying a tree. It is used as the key 91 // for the lockedOutpoints map. 92 type outpoint struct { 93 hash chainhash.Hash 94 index uint32 95 } 96 97 // Wallet is a structure containing all the components for a 98 // complete wallet. It contains the Armory-style key store 99 // addresses and keys), 100 type Wallet struct { 101 // disapprovePercent is an atomic. It sets the percentage of blocks to 102 // disapprove on simnet or testnet. 103 disapprovePercent uint32 104 105 // Data stores 106 db walletdb.DB 107 manager *udb.Manager 108 txStore *udb.Store 109 stakeMgr *udb.StakeStore 110 111 // Handlers for stake system. 112 stakeSettingsLock sync.Mutex 113 defaultVoteBits stake.VoteBits 114 votingEnabled bool 115 poolAddress stdaddr.StakeAddress 116 poolFees float64 117 manualTickets bool 118 stakePoolEnabled bool 119 stakePoolColdAddrs map[string]struct{} 120 subsidyCache *blockchain.SubsidyCache 121 tspends map[chainhash.Hash]wire.MsgTx 122 tspendPolicy map[chainhash.Hash]stake.TreasuryVoteT 123 tspendKeyPolicy map[string]stake.TreasuryVoteT // keyed by politeia key 124 vspTSpendPolicy map[udb.VSPTSpend]stake.TreasuryVoteT 125 vspTSpendKeyPolicy map[udb.VSPTreasuryKey]stake.TreasuryVoteT 126 127 // Start up flags/settings 128 gapLimit uint32 129 watchLast uint32 130 accountGapLimit int 131 132 // initialHeight is the wallet's tip height prior to syncing with the 133 // network. Useful for calculating or estimating headers fetch progress 134 // during sync if the target header height is known or can be estimated. 135 initialHeight int32 136 137 networkBackend NetworkBackend 138 networkBackendMu sync.Mutex 139 140 lockedOutpoints map[outpoint]struct{} 141 lockedOutpointMu sync.Mutex 142 143 relayFee dcrutil.Amount 144 relayFeeMu sync.Mutex 145 allowHighFees bool 146 disableCoinTypeUpgrades bool 147 recentlyPublished map[chainhash.Hash]struct{} 148 recentlyPublishedMu sync.Mutex 149 150 // Internal address handling. 151 addressReuse bool 152 ticketAddress stdaddr.StakeAddress 153 addressBuffers map[uint32]*bip0044AccountData 154 addressBuffersMu sync.Mutex 155 156 // Passphrase unlock 157 passphraseUsedMu sync.RWMutex 158 passphraseTimeoutMu sync.Mutex 159 passphraseTimeoutCancel chan struct{} 160 161 // Mix rate limiting 162 mixSems mixSemaphores 163 164 // Cached Blake3 anchor candidate 165 cachedBlake3WorkDiffCandidateAnchor *wire.BlockHeader 166 cachedBlake3WorkDiffCandidateAnchorMu sync.Mutex 167 168 NtfnServer *NotificationServer 169 170 chainParams *chaincfg.Params 171 deploymentsByID map[string]*chaincfg.ConsensusDeployment 172 minTestNetTarget *big.Int 173 minTestNetDiffBits uint32 174 } 175 176 // Config represents the configuration options needed to initialize a wallet. 177 type Config struct { 178 DB DB 179 180 PubPassphrase []byte 181 182 VotingEnabled bool 183 AddressReuse bool 184 VotingAddress stdaddr.StakeAddress 185 PoolAddress stdaddr.StakeAddress 186 PoolFees float64 187 188 GapLimit uint32 189 WatchLast uint32 190 AccountGapLimit int 191 MixSplitLimit int 192 DisableCoinTypeUpgrades bool 193 194 StakePoolColdExtKey string 195 ManualTickets bool 196 AllowHighFees bool 197 RelayFee dcrutil.Amount 198 Params *chaincfg.Params 199 } 200 201 // DisapprovePercent returns the wallet's block disapproval percentage. 202 func (w *Wallet) DisapprovePercent() uint32 { 203 return atomic.LoadUint32(&w.disapprovePercent) 204 } 205 206 // SetDisapprovePercent sets the wallet's block disapproval percentage. Do not 207 // set on mainnet. 208 func (w *Wallet) SetDisapprovePercent(percent uint32) { 209 atomic.StoreUint32(&w.disapprovePercent, percent) 210 } 211 212 // FetchOutput fetches the associated transaction output given an outpoint. 213 // It cannot be used to fetch multi-signature outputs. 214 func (w *Wallet) FetchOutput(ctx context.Context, outPoint *wire.OutPoint) (*wire.TxOut, error) { 215 const op errors.Op = "wallet.FetchOutput" 216 217 var out *wire.TxOut 218 err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 219 txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) 220 outTx, err := w.txStore.Tx(txmgrNs, &outPoint.Hash) 221 if err != nil { 222 return err 223 } 224 225 out = outTx.TxOut[outPoint.Index] 226 return err 227 }) 228 if err != nil { 229 return nil, errors.E(op, err) 230 } 231 232 return out, nil 233 } 234 235 // VotingEnabled returns whether the wallet is configured to vote tickets. 236 func (w *Wallet) VotingEnabled() bool { 237 w.stakeSettingsLock.Lock() 238 enabled := w.votingEnabled 239 w.stakeSettingsLock.Unlock() 240 return enabled 241 } 242 243 // IsTSpendCached returns whether the given hash is already cached. 244 func (w *Wallet) IsTSpendCached(hash *chainhash.Hash) bool { 245 if _, ok := w.tspends[*hash]; ok { 246 return true 247 } 248 return false 249 } 250 251 // AddTSpend adds a tspend to the cache. 252 func (w *Wallet) AddTSpend(tx wire.MsgTx) error { 253 hash := tx.TxHash() 254 log.Infof("TSpend arrived: %v", hash) 255 w.stakeSettingsLock.Lock() 256 defer w.stakeSettingsLock.Unlock() 257 258 if _, ok := w.tspends[hash]; ok { 259 return fmt.Errorf("tspend already cached") 260 } 261 w.tspends[hash] = tx 262 return nil 263 } 264 265 // GetAllTSpends returns all tspends currently in the cache. 266 // Note: currently the tspend list does not get culled. 267 func (w *Wallet) GetAllTSpends(ctx context.Context) []*wire.MsgTx { 268 _, height := w.MainChainTip(ctx) 269 w.stakeSettingsLock.Lock() 270 defer w.stakeSettingsLock.Unlock() 271 272 txs := make([]*wire.MsgTx, 0, len(w.tspends)) 273 for k := range w.tspends { 274 v := w.tspends[k] 275 if uint32(height) > v.Expiry { 276 delete(w.tspends, k) 277 continue 278 } 279 txs = append(txs, &v) 280 } 281 return txs 282 } 283 284 func voteVersion(params *chaincfg.Params) uint32 { 285 switch params.Net { 286 case wire.MainNet: 287 return 10 288 case 0x48e7a065: // TestNet2 289 return 6 290 case wire.TestNet3: 291 return 11 292 case wire.SimNet: 293 return 11 294 default: 295 return 1 296 } 297 } 298 299 // CurrentAgendas returns the current stake version for the active network and 300 // this version of the software, and all agendas defined by it. 301 func CurrentAgendas(params *chaincfg.Params) (version uint32, agendas []chaincfg.ConsensusDeployment) { 302 version = voteVersion(params) 303 if params.Deployments == nil { 304 return version, nil 305 } 306 return version, params.Deployments[version] 307 } 308 309 func (w *Wallet) readDBVoteBits(dbtx walletdb.ReadTx) stake.VoteBits { 310 version, deployments := CurrentAgendas(w.chainParams) 311 vb := stake.VoteBits{ 312 Bits: 0x0001, 313 ExtendedBits: make([]byte, 4), 314 } 315 binary.LittleEndian.PutUint32(vb.ExtendedBits, version) 316 317 if len(deployments) == 0 { 318 return vb 319 } 320 321 for i := range deployments { 322 d := &deployments[i] 323 choiceID := udb.DefaultAgendaPreference(dbtx, version, d.Vote.Id) 324 if choiceID == "" { 325 continue 326 } 327 for j := range d.Vote.Choices { 328 choice := &d.Vote.Choices[j] 329 if choiceID == choice.Id { 330 vb.Bits |= choice.Bits 331 break 332 } 333 } 334 } 335 336 return vb 337 } 338 339 func (w *Wallet) readDBTicketVoteBits(dbtx walletdb.ReadTx, ticketHash *chainhash.Hash) (stake.VoteBits, bool) { 340 version, deployments := CurrentAgendas(w.chainParams) 341 tvb := stake.VoteBits{ 342 Bits: 0x0001, 343 ExtendedBits: make([]byte, 4), 344 } 345 binary.LittleEndian.PutUint32(tvb.ExtendedBits, version) 346 347 if len(deployments) == 0 { 348 return tvb, false 349 } 350 351 var hasSavedPrefs bool 352 for i := range deployments { 353 d := &deployments[i] 354 choiceID := udb.TicketAgendaPreference(dbtx, ticketHash, version, d.Vote.Id) 355 if choiceID == "" { 356 continue 357 } 358 hasSavedPrefs = true 359 for j := range d.Vote.Choices { 360 choice := &d.Vote.Choices[j] 361 if choiceID == choice.Id { 362 tvb.Bits |= choice.Bits 363 break 364 } 365 } 366 } 367 return tvb, hasSavedPrefs 368 } 369 370 func (w *Wallet) readDBTreasuryPolicies(dbtx walletdb.ReadTx) ( 371 map[chainhash.Hash]stake.TreasuryVoteT, map[udb.VSPTSpend]stake.TreasuryVoteT, error) { 372 a, err := udb.TSpendPolicies(dbtx) 373 if err != nil { 374 return nil, nil, err 375 } 376 b, err := udb.VSPTSpendPolicies(dbtx) 377 return a, b, err 378 } 379 380 func (w *Wallet) readDBTreasuryKeyPolicies(dbtx walletdb.ReadTx) ( 381 map[string]stake.TreasuryVoteT, map[udb.VSPTreasuryKey]stake.TreasuryVoteT, error) { 382 a, err := udb.TreasuryKeyPolicies(dbtx) 383 if err != nil { 384 return nil, nil, err 385 } 386 b, err := udb.VSPTreasuryKeyPolicies(dbtx) 387 return a, b, err 388 } 389 390 // VoteBits returns the vote bits that are described by the currently set agenda 391 // preferences. The previous block valid bit is always set, and must be unset 392 // elsewhere if the previous block's regular transactions should be voted 393 // against. 394 func (w *Wallet) VoteBits() stake.VoteBits { 395 w.stakeSettingsLock.Lock() 396 vb := w.defaultVoteBits 397 w.stakeSettingsLock.Unlock() 398 return vb 399 } 400 401 // AgendaChoice describes a user's choice for a consensus deployment agenda. 402 type AgendaChoice struct { 403 AgendaID string 404 ChoiceID string 405 } 406 407 type AgendaChoices []AgendaChoice 408 409 // Map returns the agenda choices formatted as map["AgendaID"] = "ChoiceID". 410 func (a AgendaChoices) Map() map[string]string { 411 choices := make(map[string]string, len(a)) 412 413 for _, c := range a { 414 choices[c.AgendaID] = c.ChoiceID 415 } 416 return choices 417 } 418 419 // AgendaChoices returns the choice IDs for every agenda of the supported stake 420 // version. Abstains are included. Returns choice IDs set for the specified 421 // non-nil ticket hash, or the default choice IDs if the ticket hash is nil or 422 // there are no choices set for the ticket. 423 func (w *Wallet) AgendaChoices(ctx context.Context, ticketHash *chainhash.Hash) (choices AgendaChoices, voteBits uint16, err error) { 424 const op errors.Op = "wallet.AgendaChoices" 425 version, deployments := CurrentAgendas(w.chainParams) 426 if len(deployments) == 0 { 427 return nil, 0, nil 428 } 429 430 choices = make(AgendaChoices, len(deployments)) 431 for i := range choices { 432 choices[i].AgendaID = deployments[i].Vote.Id 433 choices[i].ChoiceID = "abstain" 434 } 435 436 var ownTicket bool 437 var hasSavedPrefs bool 438 439 voteBits = 1 440 err = walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 441 if ticketHash != nil { 442 ownTicket = w.txStore.OwnTicket(tx, ticketHash) 443 ownTicket = ownTicket || w.stakeMgr.OwnTicket(ticketHash) 444 if !ownTicket { 445 return nil 446 } 447 } 448 449 for i := range deployments { 450 agenda := &deployments[i].Vote 451 var choice string 452 if ticketHash == nil { 453 choice = udb.DefaultAgendaPreference(tx, version, agenda.Id) 454 } else { 455 choice = udb.TicketAgendaPreference(tx, ticketHash, version, agenda.Id) 456 } 457 if choice == "" { 458 continue 459 } 460 hasSavedPrefs = true 461 choices[i].ChoiceID = choice 462 for j := range agenda.Choices { 463 if agenda.Choices[j].Id == choice { 464 voteBits |= agenda.Choices[j].Bits 465 break 466 } 467 } 468 } 469 return nil 470 }) 471 if err != nil { 472 return nil, 0, errors.E(op, err) 473 } 474 if ticketHash != nil && !ownTicket { 475 return nil, 0, errors.E(errors.NotExist, "ticket not found") 476 } 477 if ticketHash != nil && !hasSavedPrefs { 478 // no choices set for ticket hash, return default choices. 479 return w.AgendaChoices(ctx, nil) 480 } 481 return choices, voteBits, nil 482 } 483 484 // SetAgendaChoices sets the choices for agendas defined by the supported stake 485 // version. If a choice is set multiple times, the last takes preference. The 486 // new votebits after each change is made are returned. 487 // If a ticketHash is provided, agenda choices are only set for that ticket and 488 // the new votebits for that ticket is returned. 489 func (w *Wallet) SetAgendaChoices(ctx context.Context, ticketHash *chainhash.Hash, choices ...AgendaChoice) (voteBits uint16, err error) { 490 const op errors.Op = "wallet.SetAgendaChoices" 491 version, deployments := CurrentAgendas(w.chainParams) 492 if len(deployments) == 0 { 493 return 0, errors.E("no agendas to set for this network") 494 } 495 496 if ticketHash != nil { 497 // validate ticket ownership 498 var ownTicket bool 499 err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 500 ownTicket = w.txStore.OwnTicket(dbtx, ticketHash) || w.stakeMgr.OwnTicket(ticketHash) 501 return nil 502 }) 503 if err != nil { 504 return 0, errors.E(op, err) 505 } 506 if !ownTicket { 507 return 0, errors.E(errors.NotExist, "ticket not found") 508 } 509 } 510 511 type maskChoice struct { 512 mask uint16 513 bits uint16 514 } 515 var appliedChoices []maskChoice 516 517 err = walletdb.Update(ctx, w.db, func(tx walletdb.ReadWriteTx) error { 518 for _, c := range choices { 519 var matchingAgenda *chaincfg.Vote 520 for i := range deployments { 521 if deployments[i].Vote.Id == c.AgendaID { 522 matchingAgenda = &deployments[i].Vote 523 break 524 } 525 } 526 if matchingAgenda == nil { 527 return errors.E(errors.Invalid, errors.Errorf("no agenda with ID %q", c.AgendaID)) 528 } 529 530 var matchingChoice *chaincfg.Choice 531 for i := range matchingAgenda.Choices { 532 if matchingAgenda.Choices[i].Id == c.ChoiceID { 533 matchingChoice = &matchingAgenda.Choices[i] 534 break 535 } 536 } 537 if matchingChoice == nil { 538 return errors.E(errors.Invalid, errors.Errorf("agenda %q has no choice ID %q", c.AgendaID, c.ChoiceID)) 539 } 540 541 var err error 542 if ticketHash == nil { 543 err = udb.SetDefaultAgendaPreference(tx, version, c.AgendaID, c.ChoiceID) 544 } else { 545 err = udb.SetTicketAgendaPreference(tx, ticketHash, version, c.AgendaID, c.ChoiceID) 546 } 547 if err != nil { 548 return err 549 } 550 appliedChoices = append(appliedChoices, maskChoice{ 551 mask: matchingAgenda.Mask, 552 bits: matchingChoice.Bits, 553 }) 554 if ticketHash != nil { 555 // No need to check that this ticket has prefs set, 556 // we just saved the per-ticket vote bits. 557 ticketVoteBits, _ := w.readDBTicketVoteBits(tx, ticketHash) 558 voteBits = ticketVoteBits.Bits 559 } 560 } 561 return nil 562 }) 563 if err != nil { 564 return 0, errors.E(op, err) 565 } 566 567 // With the DB update successful, modify the default votebits cached by the 568 // wallet structure. Per-ticket votebits are not cached. 569 if ticketHash == nil { 570 w.stakeSettingsLock.Lock() 571 for _, c := range appliedChoices { 572 w.defaultVoteBits.Bits &^= c.mask // Clear all bits from this agenda 573 w.defaultVoteBits.Bits |= c.bits // Set bits for this choice 574 } 575 voteBits = w.defaultVoteBits.Bits 576 w.stakeSettingsLock.Unlock() 577 } 578 579 return voteBits, nil 580 } 581 582 // TreasuryKeyPolicyForTicket returns all of the treasury key policies set for a 583 // single ticket. It does not consider the global wallet setting. 584 func (w *Wallet) TreasuryKeyPolicyForTicket(ticketHash *chainhash.Hash) map[string]string { 585 w.stakeSettingsLock.Lock() 586 defer w.stakeSettingsLock.Unlock() 587 588 policies := make(map[string]string) 589 for key, value := range w.vspTSpendKeyPolicy { 590 if key.Ticket.IsEqual(ticketHash) { 591 var choice string 592 switch value { 593 case stake.TreasuryVoteYes: 594 choice = "yes" 595 case stake.TreasuryVoteNo: 596 choice = "no" 597 default: 598 choice = "abstain" 599 } 600 policies[key.TreasuryKey] = choice 601 } 602 } 603 return policies 604 } 605 606 // TreasuryKeyPolicy returns a vote policy for provided Pi key. If there is 607 // no policy this method returns TreasuryVoteInvalid. 608 // A non-nil ticket hash may be used by a VSP to return per-ticket policies. 609 func (w *Wallet) TreasuryKeyPolicy(pikey []byte, ticket *chainhash.Hash) stake.TreasuryVoteT { 610 w.stakeSettingsLock.Lock() 611 defer w.stakeSettingsLock.Unlock() 612 613 // Zero value is abstain/invalid, just return as is. 614 if ticket != nil { 615 return w.vspTSpendKeyPolicy[udb.VSPTreasuryKey{ 616 Ticket: *ticket, 617 TreasuryKey: string(pikey), 618 }] 619 } 620 return w.tspendKeyPolicy[string(pikey)] 621 } 622 623 // TSpendPolicyForTicket returns all of the tspend policies set for a single 624 // ticket. It does not consider the global wallet setting. 625 func (w *Wallet) TSpendPolicyForTicket(ticketHash *chainhash.Hash) map[string]string { 626 w.stakeSettingsLock.Lock() 627 defer w.stakeSettingsLock.Unlock() 628 629 policies := make(map[string]string) 630 for key, value := range w.vspTSpendPolicy { 631 if key.Ticket.IsEqual(ticketHash) { 632 var choice string 633 switch value { 634 case stake.TreasuryVoteYes: 635 choice = "yes" 636 case stake.TreasuryVoteNo: 637 choice = "no" 638 default: 639 choice = "abstain" 640 } 641 policies[key.TSpend.String()] = choice 642 } 643 } 644 return policies 645 } 646 647 // TSpendPolicy returns a vote policy for a tspend. If a policy is set for a 648 // particular tspend transaction, that policy is returned. Otherwise, if the 649 // tspend is known, any policy for the treasury key which signs the tspend is 650 // returned. 651 // A non-nil ticket hash may be used by a VSP to return per-ticket policies. 652 func (w *Wallet) TSpendPolicy(tspendHash, ticketHash *chainhash.Hash) stake.TreasuryVoteT { 653 w.stakeSettingsLock.Lock() 654 defer w.stakeSettingsLock.Unlock() 655 656 // Policy preferences for specific tspends override key policies. 657 if ticketHash != nil { 658 policy, ok := w.vspTSpendPolicy[udb.VSPTSpend{ 659 Ticket: *ticketHash, 660 TSpend: *tspendHash, 661 }] 662 if ok { 663 return policy 664 } 665 } 666 if policy, ok := w.tspendPolicy[*tspendHash]; ok { 667 return policy 668 } 669 670 // If this tspend is known, the pi key can be extracted from it and its 671 // policy is returned. 672 tspend, ok := w.tspends[*tspendHash] 673 if !ok { 674 return 0 // invalid/abstain 675 } 676 pikey := tspend.TxIn[0].SignatureScript[66 : 66+secp256k1.PubKeyBytesLenCompressed] 677 678 // Zero value means abstain, just return as is. 679 if ticketHash != nil { 680 policy, ok := w.vspTSpendKeyPolicy[udb.VSPTreasuryKey{ 681 Ticket: *ticketHash, 682 TreasuryKey: string(pikey), 683 }] 684 if ok { 685 return policy 686 } 687 } 688 return w.tspendKeyPolicy[string(pikey)] 689 } 690 691 // TreasuryKeyPolicy records the voting policy for treasury spend transactions 692 // by a particular key, and possibly for a particular ticket being voted on by a 693 // VSP. 694 type TreasuryKeyPolicy struct { 695 PiKey []byte 696 Ticket *chainhash.Hash // nil unless for per-ticket VSP policies 697 Policy stake.TreasuryVoteT 698 } 699 700 // TreasuryKeyPolicies returns all configured policies for treasury keys. 701 func (w *Wallet) TreasuryKeyPolicies() []TreasuryKeyPolicy { 702 w.stakeSettingsLock.Lock() 703 defer w.stakeSettingsLock.Unlock() 704 705 policies := make([]TreasuryKeyPolicy, 0, len(w.tspendKeyPolicy)) 706 for pikey, policy := range w.tspendKeyPolicy { 707 policies = append(policies, TreasuryKeyPolicy{ 708 PiKey: []byte(pikey), 709 Policy: policy, 710 }) 711 } 712 for tuple, policy := range w.vspTSpendKeyPolicy { 713 ticketHash := tuple.Ticket // copy 714 pikey := []byte(tuple.TreasuryKey) 715 policies = append(policies, TreasuryKeyPolicy{ 716 PiKey: pikey, 717 Ticket: &ticketHash, 718 Policy: policy, 719 }) 720 } 721 return policies 722 } 723 724 // SetTreasuryKeyPolicy sets a tspend vote policy for a specific Politeia 725 // instance key. 726 // A non-nil ticket hash may be used by a VSP to set per-ticket policies. 727 func (w *Wallet) SetTreasuryKeyPolicy(ctx context.Context, pikey []byte, 728 policy stake.TreasuryVoteT, ticketHash *chainhash.Hash) error { 729 730 switch policy { 731 case stake.TreasuryVoteInvalid, stake.TreasuryVoteNo, stake.TreasuryVoteYes: 732 default: 733 err := errors.Errorf("invalid treasury vote policy %#x", policy) 734 return errors.E(errors.Invalid, err) 735 } 736 737 defer w.stakeSettingsLock.Unlock() 738 w.stakeSettingsLock.Lock() 739 740 err := walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 741 if ticketHash != nil { 742 return udb.SetVSPTreasuryKeyPolicy(dbtx, ticketHash, 743 pikey, policy) 744 } 745 return udb.SetTreasuryKeyPolicy(dbtx, pikey, policy) 746 }) 747 if err != nil { 748 return err 749 } 750 751 if ticketHash != nil { 752 k := udb.VSPTreasuryKey{ 753 Ticket: *ticketHash, 754 TreasuryKey: string(pikey), 755 } 756 if policy == stake.TreasuryVoteInvalid { 757 delete(w.vspTSpendKeyPolicy, k) 758 return nil 759 } 760 761 w.vspTSpendKeyPolicy[k] = policy 762 return nil 763 } 764 765 if policy == stake.TreasuryVoteInvalid { 766 delete(w.tspendKeyPolicy, string(pikey)) 767 return nil 768 } 769 770 w.tspendKeyPolicy[string(pikey)] = policy 771 return nil 772 } 773 774 // SetTSpendPolicy sets a tspend vote policy for a specific tspend transaction 775 // hash. 776 // A non-nil ticket hash may be used by a VSP to set per-ticket policies. 777 func (w *Wallet) SetTSpendPolicy(ctx context.Context, tspendHash *chainhash.Hash, 778 policy stake.TreasuryVoteT, ticketHash *chainhash.Hash) error { 779 780 switch policy { 781 case stake.TreasuryVoteInvalid, stake.TreasuryVoteNo, stake.TreasuryVoteYes: 782 default: 783 err := errors.Errorf("invalid treasury vote policy %#x", policy) 784 return errors.E(errors.Invalid, err) 785 } 786 787 defer w.stakeSettingsLock.Unlock() 788 w.stakeSettingsLock.Lock() 789 790 err := walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 791 if ticketHash != nil { 792 return udb.SetVSPTSpendPolicy(dbtx, ticketHash, 793 tspendHash, policy) 794 } 795 return udb.SetTSpendPolicy(dbtx, tspendHash, policy) 796 }) 797 if err != nil { 798 return err 799 } 800 801 if ticketHash != nil { 802 k := udb.VSPTSpend{ 803 Ticket: *ticketHash, 804 TSpend: *tspendHash, 805 } 806 if policy == stake.TreasuryVoteInvalid { 807 delete(w.vspTSpendPolicy, k) 808 return nil 809 } 810 811 w.vspTSpendPolicy[k] = policy 812 return nil 813 } 814 815 if policy == stake.TreasuryVoteInvalid { 816 delete(w.tspendPolicy, *tspendHash) 817 return nil 818 } 819 820 w.tspendPolicy[*tspendHash] = policy 821 return nil 822 } 823 824 // RelayFee returns the current minimum relay fee (per kB of serialized 825 // transaction) used when constructing transactions. 826 func (w *Wallet) RelayFee() dcrutil.Amount { 827 w.relayFeeMu.Lock() 828 relayFee := w.relayFee 829 w.relayFeeMu.Unlock() 830 return relayFee 831 } 832 833 // SetRelayFee sets a new minimum relay fee (per kB of serialized 834 // transaction) used when constructing transactions. 835 func (w *Wallet) SetRelayFee(relayFee dcrutil.Amount) { 836 w.relayFeeMu.Lock() 837 w.relayFee = relayFee 838 w.relayFeeMu.Unlock() 839 } 840 841 // InitialHeight is the wallet's tip height prior to syncing with the network. 842 func (w *Wallet) InitialHeight() int32 { 843 return w.initialHeight 844 } 845 846 // MainChainTip returns the hash and height of the tip-most block in the main 847 // chain that the wallet is synchronized to. 848 func (w *Wallet) MainChainTip(ctx context.Context) (hash chainhash.Hash, height int32) { 849 // TODO: after the main chain tip is successfully updated in the db, it 850 // should be saved in memory. This will speed up access to it, and means 851 // there won't need to be an ignored error here for ergonomic access to the 852 // hash and height. 853 walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 854 hash, height = w.txStore.MainChainTip(dbtx) 855 return nil 856 }) 857 return 858 } 859 860 // BlockInMainChain returns whether hash is a block hash of any block in the 861 // wallet's main chain. If the block is in the main chain, invalidated reports 862 // whether a child block in the main chain stake invalidates the queried block. 863 func (w *Wallet) BlockInMainChain(ctx context.Context, hash *chainhash.Hash) (haveBlock, invalidated bool, err error) { 864 const op errors.Op = "wallet.BlockInMainChain" 865 err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 866 haveBlock, invalidated = w.txStore.BlockInMainChain(dbtx, hash) 867 return nil 868 }) 869 if err != nil { 870 return false, false, errors.E(op, err) 871 } 872 return haveBlock, invalidated, nil 873 } 874 875 // BlockHeader returns the block header for a block by it's identifying hash, if 876 // it is recorded by the wallet. 877 func (w *Wallet) BlockHeader(ctx context.Context, blockHash *chainhash.Hash) (*wire.BlockHeader, error) { 878 var header *wire.BlockHeader 879 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 880 var err error 881 header, err = w.txStore.GetBlockHeader(dbtx, blockHash) 882 return err 883 }) 884 return header, err 885 } 886 887 // CFilterV2 returns the version 2 regular compact filter for a block along 888 // with the key required to query it for matches against committed scripts. 889 func (w *Wallet) CFilterV2(ctx context.Context, blockHash *chainhash.Hash) ([gcs2.KeySize]byte, *gcs2.FilterV2, error) { 890 var f *gcs2.FilterV2 891 var key [gcs2.KeySize]byte 892 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 893 var err error 894 key, f, err = w.txStore.CFilterV2(dbtx, blockHash) 895 return err 896 }) 897 return key, f, err 898 } 899 900 // RangeCFiltersV2 calls the function `f` for the set of version 2 committed 901 // filters for the main chain within the specificed block range. 902 // 903 // The default behavior for an unspecified range is to loop over the entire 904 // main chain. 905 // 906 // The function `f` may return true for the first argument to indicate no more 907 // items should be fetched. Any returned errors by `f` also cause the loop to 908 // fail. 909 // 910 // Note that the filter passed to `f` is safe for use after `f` returns. 911 func (w *Wallet) RangeCFiltersV2(ctx context.Context, startBlock, endBlock *BlockIdentifier, f func(chainhash.Hash, [gcs2.KeySize]byte, *gcs2.FilterV2) (bool, error)) error { 912 const op errors.Op = "wallet.RangeCFiltersV2" 913 914 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 915 start, end, err := w.blockRange(dbtx, startBlock, endBlock) 916 if err != nil { 917 return err 918 } 919 920 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 921 922 rangeFn := func(block *udb.Block) (bool, error) { 923 key, filter, err := w.txStore.CFilterV2(dbtx, &block.Hash) 924 if err != nil { 925 return false, err 926 } 927 928 return f(block.Hash, key, filter) 929 } 930 931 return w.txStore.RangeBlocks(txmgrNs, start, end, rangeFn) 932 }) 933 if err != nil { 934 return errors.E(op, err) 935 } 936 return nil 937 } 938 939 // watchHDAddrs loads the network backend's transaction filter with HD addresses 940 // for transaction notifications. 941 // 942 // This method does nothing if the wallet's rescan point is behind the main 943 // chain tip block and firstWatch is false. That is, it does not watch any 944 // addresses if the wallet's transactions are not synced with the best known 945 // block. There is no reason to watch addresses if there is a known possibility 946 // of not having all relevant transactions. 947 func (w *Wallet) watchHDAddrs(ctx context.Context, firstWatch bool, n NetworkBackend) (count uint64, err error) { 948 if !firstWatch { 949 rp, err := w.RescanPoint(ctx) 950 if err != nil { 951 return 0, err 952 } 953 if rp != nil { 954 return 0, nil 955 } 956 } 957 958 // Read branch keys and child counts for all derived and imported 959 // HD accounts. 960 type hdAccount struct { 961 externalKey, internalKey *hdkeychain.ExtendedKey 962 externalCount, internalCount uint32 963 lastWatchedExternal, lastWatchedInternal uint32 964 lastReturnedExternal, lastReturnedInternal uint32 965 lastUsedExternal, lastUsedInternal uint32 966 } 967 hdAccounts := make(map[uint32]hdAccount) 968 err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 969 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) 970 971 var err error 972 lastAcct, err := w.manager.LastAccount(addrmgrNs) 973 if err != nil { 974 return err 975 } 976 lastImportedAcct, err := w.manager.LastImportedAccount(dbtx) 977 if err != nil { 978 return err 979 } 980 981 loadAccount := func(acct uint32) error { 982 props, err := w.manager.AccountProperties(addrmgrNs, acct) 983 if err != nil { 984 return err 985 } 986 hdAccounts[acct] = hdAccount{ 987 externalCount: minUint32(props.LastReturnedExternalIndex+w.gapLimit, hdkeychain.HardenedKeyStart-1), 988 internalCount: minUint32(props.LastReturnedInternalIndex+w.gapLimit, hdkeychain.HardenedKeyStart-1), 989 lastReturnedExternal: props.LastReturnedExternalIndex, 990 lastReturnedInternal: props.LastReturnedInternalIndex, 991 lastUsedExternal: props.LastUsedExternalIndex, 992 lastUsedInternal: props.LastUsedInternalIndex, 993 } 994 return nil 995 } 996 for acct := uint32(0); acct <= lastAcct; acct++ { 997 err := loadAccount(acct) 998 if err != nil { 999 return err 1000 } 1001 } 1002 for acct := uint32(udb.ImportedAddrAccount + 1); acct <= lastImportedAcct; acct++ { 1003 err := loadAccount(acct) 1004 if err != nil { 1005 return err 1006 } 1007 } 1008 return nil 1009 }) 1010 if err != nil { 1011 return 0, err 1012 } 1013 w.addressBuffersMu.Lock() 1014 for acct, ad := range w.addressBuffers { 1015 hd := hdAccounts[acct] 1016 1017 // Update the in-memory address tracking with the latest last 1018 // used index retreived from the db. 1019 // Because the cursor may be advanced ahead of what the database 1020 // would otherwise record as the last returned address, due to 1021 // delayed db updates during some operations, a delta is 1022 // calculated between the in-memory and db last returned 1023 // indexes, and this delta is added back to the new cursor. 1024 // 1025 // This is calculated as: 1026 // delta := ad.albExternal.lastUsed + ad.albExternal.cursor - hd.lastReturnedExternal 1027 // ad.albExternal.cursor = hd.lastReturnedExternal - hd.lastUsedExternal + delta 1028 // which simplifies to the calculation below. An additional clamp 1029 // is added to prevent the cursors from going negative. 1030 if hd.lastUsedExternal+1 > ad.albExternal.lastUsed+1 { 1031 ad.albExternal.cursor += ad.albExternal.lastUsed - hd.lastUsedExternal 1032 if ad.albExternal.cursor > ^uint32(0)>>1 { 1033 ad.albExternal.cursor = 0 1034 } 1035 ad.albExternal.lastUsed = hd.lastUsedExternal 1036 } 1037 if hd.lastUsedInternal+1 > ad.albInternal.lastUsed+1 { 1038 ad.albInternal.cursor += ad.albInternal.lastUsed - hd.lastUsedInternal 1039 if ad.albInternal.cursor > ^uint32(0)>>1 { 1040 ad.albInternal.cursor = 0 1041 } 1042 ad.albInternal.lastUsed = hd.lastUsedInternal 1043 } 1044 1045 hd.externalKey = ad.albExternal.branchXpub 1046 hd.internalKey = ad.albInternal.branchXpub 1047 if firstWatch { 1048 ad.albExternal.lastWatched = hd.externalCount 1049 ad.albInternal.lastWatched = hd.internalCount 1050 } else { 1051 hd.lastWatchedExternal = ad.albExternal.lastWatched 1052 hd.lastWatchedInternal = ad.albInternal.lastWatched 1053 } 1054 hdAccounts[acct] = hd 1055 } 1056 w.addressBuffersMu.Unlock() 1057 1058 ctx, cancel := context.WithCancel(ctx) 1059 defer cancel() 1060 watchAddrs := make(chan []stdaddr.Address, runtime.NumCPU()) 1061 watchError := make(chan error) 1062 go func() { 1063 for addrs := range watchAddrs { 1064 count += uint64(len(addrs)) 1065 err := n.LoadTxFilter(ctx, false, addrs, nil) 1066 if err != nil { 1067 watchError <- err 1068 cancel() 1069 return 1070 } 1071 } 1072 watchError <- nil 1073 }() 1074 var deriveError error 1075 loadBranchAddrs := func(branchKey *hdkeychain.ExtendedKey, start, end uint32) { 1076 if start == 0 && w.watchLast != 0 && end-w.gapLimit > w.watchLast { 1077 start = end - w.gapLimit - w.watchLast 1078 } 1079 const step = 256 1080 for ; start <= end; start += step { 1081 addrs := make([]stdaddr.Address, 0, step) 1082 stop := minUint32(end+1, start+step) 1083 for child := start; child < stop; child++ { 1084 addr, err := deriveChildAddress(branchKey, child, w.chainParams) 1085 if errors.Is(err, hdkeychain.ErrInvalidChild) { 1086 continue 1087 } 1088 if err != nil { 1089 deriveError = err 1090 return 1091 } 1092 addrs = append(addrs, addr) 1093 } 1094 select { 1095 case watchAddrs <- addrs: 1096 case <-ctx.Done(): 1097 return 1098 } 1099 } 1100 } 1101 for _, hd := range hdAccounts { 1102 loadBranchAddrs(hd.externalKey, hd.lastWatchedExternal, hd.externalCount) 1103 loadBranchAddrs(hd.internalKey, hd.lastWatchedInternal, hd.internalCount) 1104 if ctx.Err() != nil || deriveError != nil { 1105 break 1106 } 1107 } 1108 close(watchAddrs) 1109 if deriveError != nil { 1110 return 0, deriveError 1111 } 1112 err = <-watchError 1113 if err != nil { 1114 return 0, err 1115 } 1116 1117 w.addressBuffersMu.Lock() 1118 for acct, hd := range hdAccounts { 1119 ad := w.addressBuffers[acct] 1120 if ad.albExternal.lastWatched < hd.externalCount { 1121 ad.albExternal.lastWatched = hd.externalCount 1122 } 1123 if ad.albInternal.lastWatched < hd.internalCount { 1124 ad.albInternal.lastWatched = hd.internalCount 1125 } 1126 } 1127 w.addressBuffersMu.Unlock() 1128 1129 return count, nil 1130 } 1131 1132 // CoinType returns the active BIP0044 coin type. For watching-only wallets, 1133 // which do not save the coin type keys, this method will return an error with 1134 // code errors.WatchingOnly. 1135 func (w *Wallet) CoinType(ctx context.Context) (uint32, error) { 1136 const op errors.Op = "wallet.CoinType" 1137 var coinType uint32 1138 err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 1139 var err error 1140 coinType, err = w.manager.CoinType(tx) 1141 return err 1142 }) 1143 if err != nil { 1144 return coinType, errors.E(op, err) 1145 } 1146 return coinType, nil 1147 } 1148 1149 // CoinTypePrivKey returns the BIP0044 coin type private key. 1150 func (w *Wallet) CoinTypePrivKey(ctx context.Context) (*hdkeychain.ExtendedKey, error) { 1151 const op errors.Op = "wallet.CoinTypePrivKey" 1152 var coinTypePrivKey *hdkeychain.ExtendedKey 1153 err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 1154 var err error 1155 coinTypePrivKey, err = w.manager.CoinTypePrivKey(tx) 1156 return err 1157 }) 1158 if err != nil { 1159 return nil, errors.E(op, err) 1160 } 1161 return coinTypePrivKey, nil 1162 } 1163 1164 // LoadActiveDataFilters loads filters for all active addresses and unspent 1165 // outpoints for this wallet. 1166 func (w *Wallet) LoadActiveDataFilters(ctx context.Context, n NetworkBackend, reload bool) (err error) { 1167 const op errors.Op = "wallet.LoadActiveDataFilters" 1168 log.Infof("Loading active addresses and unspent outputs...") 1169 1170 if reload { 1171 err := n.LoadTxFilter(ctx, true, nil, nil) 1172 if err != nil { 1173 return err 1174 } 1175 } 1176 1177 buf := make([]wire.OutPoint, 0, 64) 1178 defer func() { 1179 if len(buf) > 0 && err == nil { 1180 err = n.LoadTxFilter(ctx, false, nil, buf) 1181 if err != nil { 1182 return 1183 } 1184 } 1185 }() 1186 watchOutPoint := func(op *wire.OutPoint) (err error) { 1187 buf = append(buf, *op) 1188 if len(buf) == cap(buf) { 1189 err = n.LoadTxFilter(ctx, false, nil, buf) 1190 buf = buf[:0] 1191 } 1192 return 1193 } 1194 1195 hdAddrCount, err := w.watchHDAddrs(ctx, true, n) 1196 if err != nil { 1197 return err 1198 } 1199 log.Infof("Registered for transaction notifications for %v HD address(es)", hdAddrCount) 1200 1201 // Watch individually-imported addresses (which must each be read out of 1202 // the DB). 1203 abuf := make([]stdaddr.Address, 0, 256) 1204 var importedAddrCount int 1205 watchAddress := func(a udb.ManagedAddress) error { 1206 addr := a.Address() 1207 abuf = append(abuf, addr) 1208 if len(abuf) == cap(abuf) { 1209 importedAddrCount += len(abuf) 1210 err := n.LoadTxFilter(ctx, false, abuf, nil) 1211 abuf = abuf[:0] 1212 return err 1213 } 1214 return nil 1215 } 1216 err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 1217 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) 1218 return w.manager.ForEachAccountAddress(addrmgrNs, udb.ImportedAddrAccount, watchAddress) 1219 }) 1220 if err != nil { 1221 return err 1222 } 1223 if len(abuf) != 0 { 1224 importedAddrCount += len(abuf) 1225 err := n.LoadTxFilter(ctx, false, abuf, nil) 1226 if err != nil { 1227 return err 1228 } 1229 } 1230 if importedAddrCount > 0 { 1231 log.Infof("Registered for transaction notifications for %v imported address(es)", importedAddrCount) 1232 } 1233 1234 defer w.lockedOutpointMu.Unlock() 1235 w.lockedOutpointMu.Lock() 1236 err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 1237 err := w.txStore.ForEachUnspentOutpoint(dbtx, watchOutPoint) 1238 if err != nil { 1239 return err 1240 } 1241 1242 _, height := w.txStore.MainChainTip(dbtx) 1243 tickets, err := w.txStore.UnspentTickets(dbtx, height, true) 1244 if err != nil { 1245 return err 1246 } 1247 for i := range tickets { 1248 op := wire.OutPoint{ 1249 Hash: tickets[i], 1250 Index: 0, 1251 Tree: wire.TxTreeStake, 1252 } 1253 err = watchOutPoint(&op) 1254 if err != nil { 1255 return err 1256 } 1257 } 1258 return nil 1259 }) 1260 if err != nil { 1261 return errors.E(op, err) 1262 } 1263 log.Infof("Registered for transaction notifications for all relevant outputs") 1264 1265 return nil 1266 } 1267 1268 // CommittedTickets takes a list of tickets and returns a filtered list of 1269 // tickets that are controlled by this wallet. 1270 func (w *Wallet) CommittedTickets(ctx context.Context, tickets []*chainhash.Hash) ([]*chainhash.Hash, []stdaddr.Address, error) { 1271 const op errors.Op = "wallet.CommittedTickets" 1272 hashes := make([]*chainhash.Hash, 0, len(tickets)) 1273 addresses := make([]stdaddr.Address, 0, len(tickets)) 1274 // Verify we own this ticket 1275 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 1276 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 1277 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) 1278 for _, v := range tickets { 1279 // Make sure ticket exists 1280 tx, err := w.txStore.Tx(txmgrNs, v) 1281 if err != nil { 1282 log.Debugf("%v", err) 1283 continue 1284 } 1285 if !stake.IsSStx(tx) { 1286 continue 1287 } 1288 1289 // Commitment outputs are at alternating output 1290 // indexes, starting at 1. 1291 var bestAddr stdaddr.StakeAddress 1292 var bestAmount dcrutil.Amount 1293 1294 for i := 1; i < len(tx.TxOut); i += 2 { 1295 scr := tx.TxOut[i].PkScript 1296 addr, err := stake.AddrFromSStxPkScrCommitment(scr, 1297 w.chainParams) 1298 if err != nil { 1299 log.Debugf("%v", err) 1300 break 1301 } 1302 if _, ok := addr.(*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0); !ok { 1303 log.Tracef("Skipping commitment at "+ 1304 "index %v: address is not "+ 1305 "P2PKH", i) 1306 continue 1307 } 1308 amt, err := stake.AmountFromSStxPkScrCommitment(scr) 1309 if err != nil { 1310 log.Debugf("%v", err) 1311 break 1312 } 1313 if amt > bestAmount { 1314 bestAddr = addr 1315 bestAmount = amt 1316 } 1317 } 1318 1319 if bestAddr == nil { 1320 log.Debugf("no best address") 1321 continue 1322 } 1323 1324 // We only support Hash160 addresses. 1325 var hash160 []byte 1326 switch bestAddr := bestAddr.(type) { 1327 case stdaddr.Hash160er: 1328 hash160 = bestAddr.Hash160()[:] 1329 } 1330 if hash160 == nil || !w.manager.ExistsHash160( 1331 addrmgrNs, hash160) { 1332 log.Debugf("not our address: hash160=%x", hash160) 1333 continue 1334 } 1335 ticketHash := tx.TxHash() 1336 log.Tracef("Ticket purchase %v: best commitment"+ 1337 " address %v amount %v", &ticketHash, bestAddr, 1338 bestAmount) 1339 1340 hashes = append(hashes, v) 1341 addresses = append(addresses, bestAddr) 1342 } 1343 return nil 1344 }) 1345 if err != nil { 1346 return nil, nil, errors.E(op, err) 1347 } 1348 1349 return hashes, addresses, nil 1350 } 1351 1352 // fetchMissingCFilters checks to see if there are any missing committed filters 1353 // then, if so requests them from the given peer. The progress channel, if 1354 // non-nil, is sent the first height and last height of the range of filters 1355 // that were retrieved in that peer request. 1356 func (w *Wallet) fetchMissingCFilters(ctx context.Context, p Peer, progress chan<- MissingCFilterProgress) error { 1357 var missing bool 1358 var height int32 1359 1360 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 1361 var err error 1362 missing = w.txStore.IsMissingMainChainCFilters(dbtx) 1363 if missing { 1364 height, err = w.txStore.MissingCFiltersHeight(dbtx) 1365 } 1366 return err 1367 }) 1368 if err != nil { 1369 return err 1370 } 1371 if !missing { 1372 return nil 1373 } 1374 1375 const span = 2000 1376 storage := make([]chainhash.Hash, span) 1377 storagePtrs := make([]*chainhash.Hash, span) 1378 for i := range storage { 1379 storagePtrs[i] = &storage[i] 1380 } 1381 for { 1382 if err := ctx.Err(); err != nil { 1383 return err 1384 } 1385 var hashes []chainhash.Hash 1386 var get []*chainhash.Hash 1387 var cont bool 1388 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 1389 ns := dbtx.ReadBucket(wtxmgrNamespaceKey) 1390 var err error 1391 missing = w.txStore.IsMissingMainChainCFilters(dbtx) 1392 if !missing { 1393 return nil 1394 } 1395 hash, err := w.txStore.GetMainChainBlockHashForHeight(ns, height) 1396 if err != nil { 1397 return err 1398 } 1399 _, _, err = w.txStore.CFilterV2(dbtx, &hash) 1400 if err == nil { 1401 height += span 1402 cont = true 1403 return nil 1404 } 1405 storage = storage[:cap(storage)] 1406 hashes, err = w.txStore.GetMainChainBlockHashes(ns, &hash, true, storage) 1407 if err != nil { 1408 return err 1409 } 1410 if len(hashes) == 0 { 1411 const op errors.Op = "udb.GetMainChainBlockHashes" 1412 return errors.E(op, errors.Bug, "expected over 0 results") 1413 } 1414 get = storagePtrs[:len(hashes)] 1415 if get[0] != &hashes[0] { 1416 const op errors.Op = "udb.GetMainChainBlockHashes" 1417 return errors.E(op, errors.Bug, "unexpected slice reallocation") 1418 } 1419 return nil 1420 }) 1421 if err != nil { 1422 return err 1423 } 1424 if !missing { 1425 return nil 1426 } 1427 if cont { 1428 continue 1429 } 1430 1431 filterData, err := p.CFiltersV2(ctx, get) 1432 if err != nil { 1433 return err 1434 } 1435 1436 // Validate the newly received filters against the previously 1437 // stored block header using the corresponding inclusion proof 1438 // returned by the peer. 1439 filters := make([]*gcs2.FilterV2, len(filterData)) 1440 err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 1441 for i, cf := range filterData { 1442 header, err := w.txStore.GetBlockHeader(dbtx, get[i]) 1443 if err != nil { 1444 return err 1445 } 1446 err = validate.CFilterV2HeaderCommitment(w.chainParams.Net, 1447 header, cf.Filter, cf.ProofIndex, cf.Proof) 1448 if err != nil { 1449 return err 1450 } 1451 1452 filters[i] = cf.Filter 1453 } 1454 return nil 1455 }) 1456 if err != nil { 1457 return err 1458 } 1459 1460 err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 1461 _, _, err := w.txStore.CFilterV2(dbtx, get[len(get)-1]) 1462 if err == nil { 1463 cont = true 1464 return nil 1465 } 1466 return w.txStore.InsertMissingCFilters(dbtx, get, filters) 1467 }) 1468 if err != nil { 1469 return err 1470 } 1471 if cont { 1472 continue 1473 } 1474 1475 if progress != nil { 1476 progress <- MissingCFilterProgress{BlockHeightStart: height, BlockHeightEnd: height + span - 1} 1477 } 1478 log.Infof("Fetched cfilters for blocks %v-%v", height, height+span-1) 1479 } 1480 } 1481 1482 // FetchMissingCFilters records any missing compact filters for main chain 1483 // blocks. A database upgrade requires all compact filters to be recorded for 1484 // the main chain before any more blocks may be attached, but this information 1485 // must be fetched at runtime after the upgrade as it is not already known at 1486 // the time of upgrade. 1487 func (w *Wallet) FetchMissingCFilters(ctx context.Context, p Peer) error { 1488 const opf = "wallet.FetchMissingCFilters(%v)" 1489 1490 err := w.fetchMissingCFilters(ctx, p, nil) 1491 if err != nil { 1492 op := errors.Opf(opf, p) 1493 return errors.E(op, err) 1494 } 1495 return nil 1496 } 1497 1498 // MissingCFilterProgress records the first and last height of the progress 1499 // that was received and any errors that were received during the fetching. 1500 type MissingCFilterProgress struct { 1501 Err error 1502 BlockHeightStart int32 1503 BlockHeightEnd int32 1504 } 1505 1506 // FetchMissingCFiltersWithProgress records any missing compact filters for main chain 1507 // blocks. A database upgrade requires all compact filters to be recorded for 1508 // the main chain before any more blocks may be attached, but this information 1509 // must be fetched at runtime after the upgrade as it is not already known at 1510 // the time of upgrade. This function reports to a channel with any progress 1511 // that may have seen. 1512 func (w *Wallet) FetchMissingCFiltersWithProgress(ctx context.Context, p Peer, progress chan<- MissingCFilterProgress) { 1513 const opf = "wallet.FetchMissingCFilters(%v)" 1514 1515 defer close(progress) 1516 1517 err := w.fetchMissingCFilters(ctx, p, progress) 1518 if err != nil { 1519 op := errors.Opf(opf, p) 1520 progress <- MissingCFilterProgress{Err: errors.E(op, err)} 1521 } 1522 } 1523 1524 // log2 calculates an integer approximation of log2(x). This is used to 1525 // approximate the cap to use when allocating memory for the block locators. 1526 func log2(x int) int { 1527 res := 0 1528 for x != 0 { 1529 x /= 2 1530 res++ 1531 } 1532 return res 1533 } 1534 1535 // BlockLocators returns block locators, suitable for use in a getheaders wire 1536 // message or dcrd JSON-RPC request, for the blocks in sidechain and saved in 1537 // the wallet's main chain. For memory and lookup efficiency, many older hashes 1538 // are skipped, with increasing gaps between included hashes. 1539 // 1540 // When sidechain has zero length, locators for only main chain blocks starting 1541 // from the tip are returned. Otherwise, locators are created starting with the 1542 // best (last) block of sidechain and sidechain[0] must be a child of a main 1543 // chain block (sidechain may not contain orphan blocks). 1544 func (w *Wallet) BlockLocators(ctx context.Context, sidechain []*BlockNode) ([]*chainhash.Hash, error) { 1545 const op errors.Op = "wallet.BlockLocators" 1546 var locators []*chainhash.Hash 1547 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 1548 var err error 1549 locators, err = w.blockLocators(dbtx, sidechain) 1550 return err 1551 }) 1552 if err != nil { 1553 return nil, errors.E(op, err) 1554 } 1555 return locators, nil 1556 } 1557 1558 func (w *Wallet) blockLocators(dbtx walletdb.ReadTx, sidechain []*BlockNode) ([]*chainhash.Hash, error) { 1559 ns := dbtx.ReadBucket(wtxmgrNamespaceKey) 1560 var hash chainhash.Hash 1561 var height int32 1562 if len(sidechain) == 0 { 1563 hash, height = w.txStore.MainChainTip(dbtx) 1564 } else { 1565 n := sidechain[len(sidechain)-1] 1566 hash = *n.Hash 1567 height = int32(n.Header.Height) 1568 } 1569 1570 locators := make([]*chainhash.Hash, 1, 10+log2(int(height))) 1571 locators[0] = &hash 1572 1573 var step int32 = 1 1574 for height >= 0 { 1575 if len(sidechain) > 0 && height-int32(sidechain[0].Header.Height) >= 0 { 1576 n := sidechain[height-int32(sidechain[0].Header.Height)] 1577 hash := n.Hash 1578 locators = append(locators, hash) 1579 } else { 1580 hash, err := w.txStore.GetMainChainBlockHashForHeight(ns, height) 1581 if err != nil { 1582 return nil, err 1583 } 1584 locators = append(locators, &hash) 1585 } 1586 1587 height -= step 1588 1589 if len(locators) > 10 { 1590 step *= 2 1591 } 1592 } 1593 1594 return locators, nil 1595 } 1596 1597 // Consolidate consolidates as many UTXOs as are passed in the inputs argument. 1598 // If that many UTXOs can not be found, it will use the maximum it finds. This 1599 // will only compress UTXOs in the default account 1600 func (w *Wallet) Consolidate(ctx context.Context, inputs int, account uint32, address stdaddr.Address) (*chainhash.Hash, error) { 1601 return w.compressWallet(ctx, "wallet.Consolidate", inputs, account, address) 1602 } 1603 1604 // CreateMultisigTx creates and signs a multisig transaction. 1605 func (w *Wallet) CreateMultisigTx(ctx context.Context, account uint32, amount dcrutil.Amount, 1606 pubkeys [][]byte, nrequired int8, minconf int32) (*CreatedTx, stdaddr.Address, []byte, error) { 1607 return w.txToMultisig(ctx, "wallet.CreateMultisigTx", account, amount, pubkeys, nrequired, minconf) 1608 } 1609 1610 // PurchaseTicketsRequest describes the parameters for purchasing tickets. 1611 type PurchaseTicketsRequest struct { 1612 Count int 1613 SourceAccount uint32 1614 VotingAddress stdaddr.StakeAddress 1615 MinConf int32 1616 Expiry int32 1617 VotingAccount uint32 // Used when VotingAddress == nil, or CSPPServer != "" 1618 UseVotingAccount bool // Forces use of supplied voting account. 1619 DontSignTx bool 1620 1621 // Mixed split buying through CoinShuffle++ 1622 CSPPServer string 1623 DialCSPPServer DialFunc 1624 MixedAccount uint32 1625 MixedAccountBranch uint32 1626 MixedSplitAccount uint32 1627 ChangeAccount uint32 1628 1629 // VSP ticket buying; not currently usable with CoinShuffle++. 1630 VSPAddress stdaddr.StakeAddress 1631 VSPFees float64 1632 1633 // VSPServer methods 1634 // XXX this should be an interface 1635 1636 // VSPFeeProcessFunc Process the fee price for the vsp to register a ticket 1637 // so we can reserve the amount. 1638 VSPFeeProcess func(context.Context) (float64, error) 1639 // VSPFeePaymentProcess processes the payment of the vsp fee and returns 1640 // the paid fee tx. 1641 VSPFeePaymentProcess func(context.Context, *chainhash.Hash, *wire.MsgTx) error 1642 1643 // extraSplitOutput is an additional transaction output created during 1644 // UTXO contention, to be used as the input to pay a VSP fee 1645 // transaction, in order that both VSP fees and a single ticket purchase 1646 // may be created by spending distinct outputs. After purchaseTickets 1647 // reentry, this output is locked and UTXO selection is only used to 1648 // fund the split transaction for a ticket purchase, without reserving 1649 // any additional outputs to pay the VSP fee. 1650 extraSplitOutput *Input 1651 } 1652 1653 // PurchaseTicketsResponse describes the response for purchasing tickets request. 1654 type PurchaseTicketsResponse struct { 1655 TicketHashes []*chainhash.Hash 1656 Tickets []*wire.MsgTx 1657 SplitTx *wire.MsgTx 1658 } 1659 1660 // PurchaseTickets purchases tickets, returning purchase tickets response. 1661 func (w *Wallet) PurchaseTickets(ctx context.Context, n NetworkBackend, 1662 req *PurchaseTicketsRequest) (*PurchaseTicketsResponse, error) { 1663 1664 const op errors.Op = "wallet.PurchaseTickets" 1665 1666 resp, err := w.purchaseTickets(ctx, op, n, req) 1667 if err == nil || !errors.Is(err, errVSPFeeRequiresUTXOSplit) || req.DontSignTx { 1668 return resp, err 1669 } 1670 1671 // Do not attempt to split utxos for a fee payment when spending from 1672 // the mixed account. This error is rather unlikely anyways, as mixed 1673 // accounts probably have very many outputs. 1674 if req.CSPPServer != "" && req.MixedAccount == req.SourceAccount { 1675 return nil, errors.E(op, errors.InsufficientBalance) 1676 } 1677 1678 feePercent, err := req.VSPFeeProcess(ctx) 1679 if err != nil { 1680 return nil, err 1681 } 1682 sdiff, err := w.NextStakeDifficulty(ctx) 1683 if err != nil { 1684 return nil, err 1685 } 1686 _, height := w.MainChainTip(ctx) 1687 dcp0010Active := true 1688 switch n := n.(type) { 1689 case *dcrd.RPC: 1690 dcp0010Active, err = deployments.DCP0010Active(ctx, 1691 height, w.chainParams, n) 1692 if err != nil { 1693 return nil, err 1694 } 1695 } 1696 relayFee := w.RelayFee() 1697 vspFee := txrules.StakePoolTicketFee(sdiff, relayFee, height, 1698 feePercent, w.chainParams, dcp0010Active) 1699 a := &authorTx{ 1700 outputs: make([]*wire.TxOut, 0, 2), 1701 account: req.SourceAccount, 1702 changeAccount: req.SourceAccount, // safe-ish; this is not mixed. 1703 minconf: req.MinConf, 1704 randomizeChangeIdx: true, 1705 txFee: relayFee, 1706 } 1707 addr, err := w.NewInternalAddress(ctx, req.SourceAccount) 1708 if err != nil { 1709 return nil, err 1710 } 1711 version, script := addr.(Address).PaymentScript() 1712 a.outputs = append(a.outputs, &wire.TxOut{Version: version, PkScript: script}) 1713 txsize := txsizes.EstimateSerializeSize([]int{txsizes.RedeemP2PKHInputSize}, 1714 a.outputs, txsizes.P2PKHPkScriptSize) 1715 txfee := txrules.FeeForSerializeSize(relayFee, txsize) 1716 a.outputs[0].Value = int64(vspFee + 2*txfee) 1717 err = w.authorTx(ctx, op, a) 1718 if err != nil { 1719 return nil, err 1720 } 1721 err = w.recordAuthoredTx(ctx, op, a) 1722 if err != nil { 1723 return nil, err 1724 } 1725 err = w.publishAndWatch(ctx, op, nil, a.atx.Tx, a.watch) 1726 if err != nil { 1727 return nil, err 1728 } 1729 err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 1730 for _, update := range a.changeSourceUpdates { 1731 err := update(dbtx) 1732 if err != nil { 1733 return err 1734 } 1735 } 1736 return nil 1737 }) 1738 if err != nil { 1739 return nil, err 1740 } 1741 1742 req.MinConf = 0 1743 req.Count = 1 1744 var index uint32 = 0 1745 if a.atx.ChangeIndex == 0 { 1746 index = 1 1747 } 1748 req.extraSplitOutput = &Input{ 1749 OutPoint: wire.OutPoint{ 1750 Hash: a.atx.Tx.TxHash(), 1751 Index: index, 1752 Tree: 0, 1753 }, 1754 PrevOut: *a.atx.Tx.TxOut[index], 1755 } 1756 return w.purchaseTickets(ctx, op, n, req) 1757 } 1758 1759 // Unlock unlocks the wallet, allowing access to private keys and secret scripts. 1760 // An unlocked wallet will be locked before returning with a Passphrase error if 1761 // the passphrase is incorrect. 1762 // If the wallet is currently unlocked without any timeout, timeout is ignored 1763 // and read in a background goroutine to avoid blocking sends. 1764 // If the wallet is locked and a non-nil timeout is provided, the wallet will be 1765 // locked in the background after reading from the channel. 1766 // If the wallet is already unlocked with a previous timeout, the new timeout 1767 // replaces the prior. 1768 func (w *Wallet) Unlock(ctx context.Context, passphrase []byte, timeout <-chan time.Time) error { 1769 const op errors.Op = "wallet.Unlock" 1770 1771 w.passphraseUsedMu.RLock() 1772 wasLocked := w.manager.IsLocked() 1773 err := w.manager.UnlockedWithPassphrase(passphrase) 1774 w.passphraseUsedMu.RUnlock() 1775 switch { 1776 case errors.Is(err, errors.WatchingOnly): 1777 return errors.E(op, err) 1778 case errors.Is(err, errors.Passphrase): 1779 w.Lock() 1780 if !wasLocked { 1781 log.Info("The wallet has been locked due to an incorrect passphrase.") 1782 } 1783 return errors.E(op, err) 1784 default: 1785 return errors.E(op, err) 1786 case errors.Is(err, errors.Locked): 1787 defer w.passphraseUsedMu.RUnlock() 1788 w.passphraseUsedMu.RLock() 1789 err = walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 1790 addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) 1791 return w.manager.Unlock(addrmgrNs, passphrase) 1792 }) 1793 if err != nil { 1794 return errors.E(op, errors.Passphrase, err) 1795 } 1796 case err == nil: 1797 } 1798 w.replacePassphraseTimeout(wasLocked, timeout) 1799 return nil 1800 } 1801 1802 // SetAccountPassphrase individually-encrypts or changes the passphrase for 1803 // account private keys. 1804 // 1805 // If the passphrase has zero length, the private keys are re-encrypted with the 1806 // manager's global passphrase. 1807 func (w *Wallet) SetAccountPassphrase(ctx context.Context, account uint32, passphrase []byte) error { 1808 return walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 1809 return w.manager.SetAccountPassphrase(dbtx, account, passphrase) 1810 }) 1811 } 1812 1813 // UnlockAccount decrypts a uniquely-encrypted account's private keys. 1814 func (w *Wallet) UnlockAccount(ctx context.Context, account uint32, passphrase []byte) error { 1815 return walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 1816 return w.manager.UnlockAccount(dbtx, account, passphrase) 1817 }) 1818 } 1819 1820 // LockAccount locks an individually-encrypted account by removing private key 1821 // access until unlocked again. 1822 func (w *Wallet) LockAccount(ctx context.Context, account uint32) error { 1823 return walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 1824 return w.manager.LockAccount(dbtx, account) 1825 }) 1826 } 1827 1828 // AccountUnlocked returns whether an individually-encrypted account is unlocked. 1829 func (w *Wallet) AccountUnlocked(ctx context.Context, account uint32) (bool, error) { 1830 const op errors.Op = "wallet.AccountUnlocked" 1831 var unlocked bool 1832 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 1833 var encrypted bool 1834 encrypted, unlocked = w.manager.AccountHasPassphrase(dbtx, account) 1835 if !encrypted { 1836 const s = "account is not individually encrypted" 1837 return errors.E(errors.Invalid, s) 1838 } 1839 return nil 1840 }) 1841 if err != nil { 1842 return false, errors.E(op, err) 1843 } 1844 return unlocked, nil 1845 } 1846 1847 func (w *Wallet) AccountHasPassphrase(ctx context.Context, account uint32) (bool, error) { 1848 const op errors.Op = "wallet.AccountHasPassphrase" 1849 var encrypted bool 1850 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 1851 encrypted, _ = w.manager.AccountHasPassphrase(dbtx, account) 1852 return nil 1853 }) 1854 if err != nil { 1855 return false, errors.E(op, err) 1856 } 1857 return encrypted, nil 1858 } 1859 1860 func (w *Wallet) replacePassphraseTimeout(wasLocked bool, newTimeout <-chan time.Time) { 1861 defer w.passphraseTimeoutMu.Unlock() 1862 w.passphraseTimeoutMu.Lock() 1863 hadTimeout := w.passphraseTimeoutCancel != nil 1864 if !wasLocked && !hadTimeout && newTimeout != nil { 1865 go func() { <-newTimeout }() 1866 } else { 1867 oldCancel := w.passphraseTimeoutCancel 1868 var newCancel chan struct{} 1869 if newTimeout != nil { 1870 newCancel = make(chan struct{}, 1) 1871 } 1872 w.passphraseTimeoutCancel = newCancel 1873 1874 if oldCancel != nil { 1875 oldCancel <- struct{}{} 1876 } 1877 if newTimeout != nil { 1878 go func() { 1879 select { 1880 case <-newTimeout: 1881 w.Lock() 1882 log.Info("The wallet has been locked due to timeout.") 1883 case <-newCancel: 1884 <-newTimeout 1885 } 1886 }() 1887 } 1888 } 1889 switch { 1890 case (wasLocked || hadTimeout) && newTimeout == nil: 1891 log.Info("The wallet has been unlocked without a time limit") 1892 case (wasLocked || !hadTimeout) && newTimeout != nil: 1893 log.Info("The wallet has been temporarily unlocked") 1894 } 1895 } 1896 1897 // Lock locks the wallet's address manager. 1898 func (w *Wallet) Lock() { 1899 w.passphraseUsedMu.Lock() 1900 w.passphraseTimeoutMu.Lock() 1901 _ = w.manager.Lock() 1902 w.passphraseTimeoutCancel = nil 1903 w.passphraseTimeoutMu.Unlock() 1904 w.passphraseUsedMu.Unlock() 1905 } 1906 1907 // Locked returns whether the account manager for a wallet is locked. 1908 func (w *Wallet) Locked() bool { 1909 return w.manager.IsLocked() 1910 } 1911 1912 // Unlocked returns whether the account manager for a wallet is unlocked. 1913 func (w *Wallet) Unlocked() bool { 1914 return !w.Locked() 1915 } 1916 1917 // WatchingOnly returns whether the wallet only contains public keys. 1918 func (w *Wallet) WatchingOnly() bool { 1919 return w.manager.WatchingOnly() 1920 } 1921 1922 // ChangePrivatePassphrase attempts to change the passphrase for a wallet from 1923 // old to new. Changing the passphrase is synchronized with all other address 1924 // manager locking and unlocking. The lock state will be the same as it was 1925 // before the password change. 1926 func (w *Wallet) ChangePrivatePassphrase(ctx context.Context, old, new []byte) error { 1927 const op errors.Op = "wallet.ChangePrivatePassphrase" 1928 defer w.passphraseUsedMu.Unlock() 1929 w.passphraseUsedMu.Lock() 1930 err := walletdb.Update(ctx, w.db, func(tx walletdb.ReadWriteTx) error { 1931 addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) 1932 return w.manager.ChangePassphrase(addrmgrNs, old, new, true) 1933 }) 1934 if err != nil { 1935 return errors.E(op, err) 1936 } 1937 return nil 1938 } 1939 1940 // ChangePublicPassphrase modifies the public passphrase of the wallet. 1941 func (w *Wallet) ChangePublicPassphrase(ctx context.Context, old, new []byte) error { 1942 const op errors.Op = "wallet.ChangePublicPassphrase" 1943 err := walletdb.Update(ctx, w.db, func(tx walletdb.ReadWriteTx) error { 1944 addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) 1945 return w.manager.ChangePassphrase(addrmgrNs, old, new, false) 1946 }) 1947 if err != nil { 1948 return errors.E(op, err) 1949 } 1950 return nil 1951 } 1952 1953 // Balances describes a breakdown of an account's balances in various 1954 // categories. 1955 type Balances struct { 1956 Account uint32 1957 ImmatureCoinbaseRewards dcrutil.Amount 1958 ImmatureStakeGeneration dcrutil.Amount 1959 LockedByTickets dcrutil.Amount 1960 Spendable dcrutil.Amount 1961 Total dcrutil.Amount 1962 VotingAuthority dcrutil.Amount 1963 Unconfirmed dcrutil.Amount 1964 } 1965 1966 // AccountBalance returns the balance breakdown for a single account. 1967 func (w *Wallet) AccountBalance(ctx context.Context, account uint32, confirms int32) (Balances, error) { 1968 const op errors.Op = "wallet.CalculateAccountBalance" 1969 var balance Balances 1970 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 1971 var err error 1972 balance, err = w.txStore.AccountBalance(dbtx, 1973 confirms, account) 1974 return err 1975 }) 1976 if err != nil { 1977 return balance, errors.E(op, err) 1978 } 1979 return balance, nil 1980 } 1981 1982 // AccountBalances returns the balance breakdowns for a each account. 1983 func (w *Wallet) AccountBalances(ctx context.Context, confirms int32) ([]Balances, error) { 1984 const op errors.Op = "wallet.CalculateAccountBalances" 1985 var balances []Balances 1986 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 1987 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) 1988 return w.manager.ForEachAccount(addrmgrNs, func(acct uint32) error { 1989 balance, err := w.txStore.AccountBalance(dbtx, 1990 confirms, acct) 1991 if err != nil { 1992 return err 1993 } 1994 balances = append(balances, balance) 1995 return nil 1996 }) 1997 }) 1998 if err != nil { 1999 return nil, errors.E(op, err) 2000 } 2001 return balances, nil 2002 } 2003 2004 // CurrentAddress gets the most recently requested payment address from a wallet. 2005 // If the address has already been used (there is at least one transaction 2006 // spending to it in the blockchain or dcrd mempool), the next chained address 2007 // is returned. 2008 func (w *Wallet) CurrentAddress(account uint32) (stdaddr.Address, error) { 2009 const op errors.Op = "wallet.CurrentAddress" 2010 defer w.addressBuffersMu.Unlock() 2011 w.addressBuffersMu.Lock() 2012 2013 data, ok := w.addressBuffers[account] 2014 if !ok { 2015 return nil, errors.E(op, errors.NotExist, errors.Errorf("no account %d", account)) 2016 } 2017 buf := &data.albExternal 2018 2019 childIndex := buf.lastUsed + 1 + buf.cursor 2020 child, err := buf.branchXpub.Child(childIndex) 2021 if err != nil { 2022 return nil, errors.E(op, err) 2023 } 2024 addr, err := compat.HD2Address(child, w.chainParams) 2025 if err != nil { 2026 return nil, errors.E(op, err) 2027 } 2028 return addr, nil 2029 } 2030 2031 // SignHashes returns signatures of signed transaction hashes using an 2032 // address' associated private key. 2033 func (w *Wallet) SignHashes(ctx context.Context, hashes [][]byte, addr stdaddr.Address) ([][]byte, 2034 []byte, error) { 2035 2036 var privKey *secp256k1.PrivateKey 2037 var done func() 2038 defer func() { 2039 if done != nil { 2040 done() 2041 } 2042 }() 2043 err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 2044 addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) 2045 var err error 2046 privKey, done, err = w.manager.PrivateKey(addrmgrNs, addr) 2047 return err 2048 }) 2049 if err != nil { 2050 return nil, nil, err 2051 } 2052 2053 signatures := make([][]byte, len(hashes)) 2054 for i, hash := range hashes { 2055 sig := ecdsa.Sign(privKey, hash) 2056 signatures[i] = sig.Serialize() 2057 } 2058 2059 return signatures, privKey.PubKey().SerializeCompressed(), nil 2060 } 2061 2062 // SignMessage returns the signature of a signed message using an address' 2063 // associated private key. 2064 func (w *Wallet) SignMessage(ctx context.Context, msg string, addr stdaddr.Address) (sig []byte, err error) { 2065 const op errors.Op = "wallet.SignMessage" 2066 var buf bytes.Buffer 2067 wire.WriteVarString(&buf, 0, "Decred Signed Message:\n") 2068 wire.WriteVarString(&buf, 0, msg) 2069 messageHash := chainhash.HashB(buf.Bytes()) 2070 var privKey *secp256k1.PrivateKey 2071 var done func() 2072 defer func() { 2073 if done != nil { 2074 done() 2075 } 2076 }() 2077 err = walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 2078 addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) 2079 var err error 2080 privKey, done, err = w.manager.PrivateKey(addrmgrNs, addr) 2081 return err 2082 }) 2083 if err != nil { 2084 return nil, errors.E(op, err) 2085 } 2086 sig = ecdsa.SignCompact(privKey, messageHash, true) 2087 return sig, nil 2088 } 2089 2090 // VerifyMessage verifies that sig is a valid signature of msg and was created 2091 // using the secp256k1 private key for addr. 2092 func VerifyMessage(msg string, addr stdaddr.Address, sig []byte, params stdaddr.AddressParams) (bool, error) { 2093 const op errors.Op = "wallet.VerifyMessage" 2094 // Validate the signature - this just shows that it was valid for any pubkey 2095 // at all. Whether the pubkey matches is checked below. 2096 var buf bytes.Buffer 2097 wire.WriteVarString(&buf, 0, "Decred Signed Message:\n") 2098 wire.WriteVarString(&buf, 0, msg) 2099 expectedMessageHash := chainhash.HashB(buf.Bytes()) 2100 pk, wasCompressed, err := ecdsa.RecoverCompact(sig, expectedMessageHash) 2101 if err != nil { 2102 return false, errors.E(op, err) 2103 } 2104 2105 // Reconstruct the pubkey hash address from the recovered pubkey. 2106 var pkHash []byte 2107 if wasCompressed { 2108 pkHash = stdaddr.Hash160(pk.SerializeCompressed()) 2109 } else { 2110 pkHash = stdaddr.Hash160(pk.SerializeUncompressed()) 2111 } 2112 address, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(pkHash, params) 2113 if err != nil { 2114 return false, errors.E(op, err) 2115 } 2116 2117 // Return whether addresses match. 2118 return address.String() == addr.String(), nil 2119 } 2120 2121 // HaveAddress returns whether the wallet is the owner of the address a. 2122 func (w *Wallet) HaveAddress(ctx context.Context, a stdaddr.Address) (bool, error) { 2123 const op errors.Op = "wallet.HaveAddress" 2124 err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 2125 addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) 2126 _, err := w.manager.Address(addrmgrNs, a) 2127 return err 2128 }) 2129 if err != nil { 2130 if errors.Is(err, errors.NotExist) { 2131 return false, nil 2132 } 2133 return false, errors.E(op, err) 2134 } 2135 return true, nil 2136 } 2137 2138 // AccountNumber returns the account number for an account name. 2139 func (w *Wallet) AccountNumber(ctx context.Context, accountName string) (uint32, error) { 2140 const op errors.Op = "wallet.AccountNumber" 2141 var account uint32 2142 err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 2143 addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) 2144 var err error 2145 account, err = w.manager.LookupAccount(addrmgrNs, accountName) 2146 return err 2147 }) 2148 if err != nil { 2149 return 0, errors.E(op, err) 2150 } 2151 return account, nil 2152 } 2153 2154 // AccountName returns the name of an account. 2155 func (w *Wallet) AccountName(ctx context.Context, accountNumber uint32) (string, error) { 2156 const op errors.Op = "wallet.AccountName" 2157 var accountName string 2158 err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 2159 addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) 2160 var err error 2161 accountName, err = w.manager.AccountName(addrmgrNs, accountNumber) 2162 return err 2163 }) 2164 if err != nil { 2165 return "", errors.E(op, err) 2166 } 2167 return accountName, nil 2168 } 2169 2170 // RenameAccount sets the name for an account number to newName. 2171 func (w *Wallet) RenameAccount(ctx context.Context, account uint32, newName string) error { 2172 const op errors.Op = "wallet.RenameAccount" 2173 var props *udb.AccountProperties 2174 err := walletdb.Update(ctx, w.db, func(tx walletdb.ReadWriteTx) error { 2175 addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) 2176 err := w.manager.RenameAccount(addrmgrNs, account, newName) 2177 if err != nil { 2178 return err 2179 } 2180 props, err = w.manager.AccountProperties(addrmgrNs, account) 2181 return err 2182 }) 2183 if err != nil { 2184 return errors.E(op, err) 2185 } 2186 w.NtfnServer.notifyAccountProperties(props) 2187 return nil 2188 } 2189 2190 // NextAccount creates the next account and returns its account number. The 2191 // name must be unique to the account. In order to support automatic seed 2192 // restoring, new accounts may not be created when all of the previous 100 2193 // accounts have no transaction history (this is a deviation from the BIP0044 2194 // spec, which allows no unused account gaps). 2195 func (w *Wallet) NextAccount(ctx context.Context, name string) (uint32, error) { 2196 const op errors.Op = "wallet.NextAccount" 2197 maxEmptyAccounts := uint32(w.accountGapLimit) 2198 var account uint32 2199 var props *udb.AccountProperties 2200 var xpub *hdkeychain.ExtendedKey 2201 err := walletdb.Update(ctx, w.db, func(tx walletdb.ReadWriteTx) error { 2202 addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) 2203 2204 // Ensure that there is transaction history in the last 100 accounts. 2205 var err error 2206 lastAcct, err := w.manager.LastAccount(addrmgrNs) 2207 if err != nil { 2208 return err 2209 } 2210 canCreate := false 2211 for i := uint32(0); i < maxEmptyAccounts; i++ { 2212 a := lastAcct - i 2213 if a == 0 && i < maxEmptyAccounts-1 { 2214 // Less than 100 accounts total. 2215 canCreate = true 2216 break 2217 } 2218 props, err := w.manager.AccountProperties(addrmgrNs, a) 2219 if err != nil { 2220 return err 2221 } 2222 if props.LastUsedExternalIndex != ^uint32(0) || props.LastUsedInternalIndex != ^uint32(0) { 2223 canCreate = true 2224 break 2225 } 2226 } 2227 if !canCreate { 2228 return errors.Errorf("last %d accounts have no transaction history", 2229 maxEmptyAccounts) 2230 } 2231 2232 account, err = w.manager.NewAccount(addrmgrNs, name) 2233 if err != nil { 2234 return err 2235 } 2236 2237 props, err = w.manager.AccountProperties(addrmgrNs, account) 2238 if err != nil { 2239 return err 2240 } 2241 2242 xpub, err = w.manager.AccountExtendedPubKey(tx, account) 2243 if err != nil { 2244 return err 2245 } 2246 2247 err = w.manager.SyncAccountToAddrIndex(addrmgrNs, account, 2248 w.gapLimit, udb.ExternalBranch) 2249 if err != nil { 2250 return err 2251 } 2252 return w.manager.SyncAccountToAddrIndex(addrmgrNs, account, 2253 w.gapLimit, udb.InternalBranch) 2254 }) 2255 if err != nil { 2256 return 0, errors.E(op, err) 2257 } 2258 2259 extKey, intKey, err := deriveBranches(xpub) 2260 if err != nil { 2261 return 0, errors.E(op, err) 2262 } 2263 w.addressBuffersMu.Lock() 2264 w.addressBuffers[account] = &bip0044AccountData{ 2265 xpub: xpub, 2266 albExternal: addressBuffer{branchXpub: extKey, lastUsed: ^uint32(0)}, 2267 albInternal: addressBuffer{branchXpub: intKey, lastUsed: ^uint32(0)}, 2268 } 2269 w.addressBuffersMu.Unlock() 2270 2271 if n, err := w.NetworkBackend(); err == nil { 2272 errs := make(chan error, 2) 2273 for _, branchKey := range []*hdkeychain.ExtendedKey{extKey, intKey} { 2274 branchKey := branchKey 2275 go func() { 2276 addrs, err := deriveChildAddresses(branchKey, 0, 2277 w.gapLimit, w.chainParams) 2278 if err != nil { 2279 errs <- err 2280 return 2281 } 2282 errs <- n.LoadTxFilter(ctx, false, addrs, nil) 2283 }() 2284 } 2285 for i := 0; i < cap(errs); i++ { 2286 err := <-errs 2287 if err != nil { 2288 return 0, errors.E(op, err) 2289 } 2290 } 2291 } 2292 2293 w.NtfnServer.notifyAccountProperties(props) 2294 2295 return account, nil 2296 } 2297 2298 // AccountXpub returns a BIP0044 account's extended public key. 2299 func (w *Wallet) AccountXpub(ctx context.Context, account uint32) (*hdkeychain.ExtendedKey, error) { 2300 const op errors.Op = "wallet.AccountXpub" 2301 2302 var pubKey *hdkeychain.ExtendedKey 2303 err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 2304 var err error 2305 pubKey, err = w.manager.AccountExtendedPubKey(tx, account) 2306 return err 2307 }) 2308 if err != nil { 2309 return nil, errors.E(op, err) 2310 } 2311 2312 return pubKey, nil 2313 } 2314 2315 // AccountXpriv returns a BIP0044 account's extended private key. The account 2316 // must exist and the wallet must be unlocked, otherwise this function fails. 2317 func (w *Wallet) AccountXpriv(ctx context.Context, account uint32) (*hdkeychain.ExtendedKey, error) { 2318 const op errors.Op = "wallet.AccountXpriv" 2319 2320 var privKey *hdkeychain.ExtendedKey 2321 err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 2322 var err error 2323 privKey, err = w.manager.AccountExtendedPrivKey(tx, account) 2324 return err 2325 }) 2326 if err != nil { 2327 return nil, errors.E(op, err) 2328 } 2329 2330 return privKey, nil 2331 } 2332 2333 // TxBlock returns the hash and height of a block which mines a transaction. 2334 func (w *Wallet) TxBlock(ctx context.Context, hash *chainhash.Hash) (chainhash.Hash, int32, error) { 2335 var blockHash chainhash.Hash 2336 var height int32 2337 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 2338 ns := dbtx.ReadBucket(wtxmgrNamespaceKey) 2339 var err error 2340 height, err = w.txStore.TxBlockHeight(dbtx, hash) 2341 if err != nil { 2342 return err 2343 } 2344 if height == -1 { 2345 return nil 2346 } 2347 blockHash, err = w.txStore.GetMainChainBlockHashForHeight(ns, height) 2348 return err 2349 }) 2350 if err != nil { 2351 return blockHash, 0, err 2352 } 2353 return blockHash, height, nil 2354 } 2355 2356 // TxConfirms returns the current number of block confirmations a transaction. 2357 func (w *Wallet) TxConfirms(ctx context.Context, hash *chainhash.Hash) (int32, error) { 2358 var tip, txheight int32 2359 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 2360 _, tip = w.txStore.MainChainTip(dbtx) 2361 var err error 2362 txheight, err = w.txStore.TxBlockHeight(dbtx, hash) 2363 return err 2364 }) 2365 if err != nil { 2366 return 0, err 2367 } 2368 return confirms(txheight, tip), nil 2369 } 2370 2371 // GetTransactionsByHashes returns all known transactions identified by a slice 2372 // of transaction hashes. It is possible that not all transactions are found, 2373 // and in this case the known results will be returned along with an inventory 2374 // vector of all missing transactions and an error with code 2375 // NotExist. 2376 func (w *Wallet) GetTransactionsByHashes(ctx context.Context, txHashes []*chainhash.Hash) (txs []*wire.MsgTx, notFound []*wire.InvVect, err error) { 2377 err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 2378 ns := dbtx.ReadBucket(wtxmgrNamespaceKey) 2379 for _, hash := range txHashes { 2380 tx, err := w.txStore.Tx(ns, hash) 2381 switch { 2382 case err != nil && !errors.Is(err, errors.NotExist): 2383 return err 2384 case tx == nil: 2385 notFound = append(notFound, wire.NewInvVect(wire.InvTypeTx, hash)) 2386 default: 2387 txs = append(txs, tx) 2388 } 2389 } 2390 return nil 2391 }) 2392 if err != nil { 2393 return 2394 } 2395 if len(notFound) != 0 { 2396 err = errors.E(errors.NotExist, "transaction(s) not found") 2397 } 2398 return 2399 } 2400 2401 // CreditCategory describes the type of wallet transaction output. The category 2402 // of "sent transactions" (debits) is always "send", and is not expressed by 2403 // this type. 2404 // 2405 // TODO: This is a requirement of the RPC server and should be moved. 2406 type CreditCategory byte 2407 2408 // These constants define the possible credit categories. 2409 const ( 2410 CreditReceive CreditCategory = iota 2411 CreditGenerate 2412 CreditImmature 2413 ) 2414 2415 // String returns the category as a string. This string may be used as the 2416 // JSON string for categories as part of listtransactions and gettransaction 2417 // RPC responses. 2418 func (c CreditCategory) String() string { 2419 switch c { 2420 case CreditReceive: 2421 return "receive" 2422 case CreditGenerate: 2423 return "generate" 2424 case CreditImmature: 2425 return "immature" 2426 default: 2427 return "unknown" 2428 } 2429 } 2430 2431 // recvCategory returns the category of received credit outputs from a 2432 // transaction record. The passed block chain height is used to distinguish 2433 // immature from mature coinbase outputs. 2434 // 2435 // TODO: This is intended for use by the RPC server and should be moved out of 2436 // this package at a later time. 2437 func recvCategory(details *udb.TxDetails, syncHeight int32, chainParams *chaincfg.Params) CreditCategory { 2438 if compat.IsEitherCoinBaseTx(&details.MsgTx) { 2439 if coinbaseMatured(chainParams, details.Block.Height, syncHeight) { 2440 return CreditGenerate 2441 } 2442 return CreditImmature 2443 } 2444 return CreditReceive 2445 } 2446 2447 // listTransactions creates a object that may be marshalled to a response result 2448 // for a listtransactions RPC. 2449 // 2450 // TODO: This should be moved to the jsonrpc package. 2451 func listTransactions(tx walletdb.ReadTx, details *udb.TxDetails, addrMgr *udb.Manager, syncHeight int32, net *chaincfg.Params) (sends, receives []types.ListTransactionsResult) { 2452 addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) 2453 2454 var ( 2455 blockHashStr string 2456 blockTime int64 2457 confirmations int64 2458 ) 2459 if details.Block.Height != -1 { 2460 blockHashStr = details.Block.Hash.String() 2461 blockTime = details.Block.Time.Unix() 2462 confirmations = int64(confirms(details.Block.Height, syncHeight)) 2463 } 2464 2465 txHashStr := details.Hash.String() 2466 received := details.Received.Unix() 2467 generated := compat.IsEitherCoinBaseTx(&details.MsgTx) 2468 recvCat := recvCategory(details, syncHeight, net).String() 2469 2470 send := len(details.Debits) != 0 2471 2472 txTypeStr := types.LTTTRegular 2473 switch details.TxType { 2474 case stake.TxTypeSStx: 2475 txTypeStr = types.LTTTTicket 2476 case stake.TxTypeSSGen: 2477 txTypeStr = types.LTTTVote 2478 case stake.TxTypeSSRtx: 2479 txTypeStr = types.LTTTRevocation 2480 } 2481 2482 // Fee can only be determined if every input is a debit. 2483 var feeF64 float64 2484 if len(details.Debits) == len(details.MsgTx.TxIn) { 2485 var debitTotal dcrutil.Amount 2486 for _, deb := range details.Debits { 2487 debitTotal += deb.Amount 2488 } 2489 var outputTotal dcrutil.Amount 2490 for _, output := range details.MsgTx.TxOut { 2491 outputTotal += dcrutil.Amount(output.Value) 2492 } 2493 // Note: The actual fee is debitTotal - outputTotal. However, 2494 // this RPC reports negative numbers for fees, so the inverse 2495 // is calculated. 2496 feeF64 = (outputTotal - debitTotal).ToCoin() 2497 } 2498 2499 outputs: 2500 for i, output := range details.MsgTx.TxOut { 2501 // Determine if this output is a credit. Change outputs are skipped. 2502 var isCredit bool 2503 for _, cred := range details.Credits { 2504 if cred.Index == uint32(i) { 2505 if cred.Change { 2506 continue outputs 2507 } 2508 isCredit = true 2509 break 2510 } 2511 } 2512 2513 var address string 2514 var accountName string 2515 _, addrs := stdscript.ExtractAddrs(output.Version, output.PkScript, net) 2516 if len(addrs) == 1 { 2517 addr := addrs[0] 2518 address = addr.String() 2519 account, err := addrMgr.AddrAccount(addrmgrNs, addrs[0]) 2520 if err == nil { 2521 accountName, err = addrMgr.AccountName(addrmgrNs, account) 2522 if err != nil { 2523 accountName = "" 2524 } 2525 } 2526 } 2527 2528 amountF64 := dcrutil.Amount(output.Value).ToCoin() 2529 result := types.ListTransactionsResult{ 2530 // Fields left zeroed: 2531 // InvolvesWatchOnly 2532 // BlockIndex 2533 // 2534 // Fields set below: 2535 // Account (only for non-"send" categories) 2536 // Category 2537 // Amount 2538 // Fee 2539 Address: address, 2540 Vout: uint32(i), 2541 Confirmations: confirmations, 2542 Generated: generated, 2543 BlockHash: blockHashStr, 2544 BlockTime: blockTime, 2545 TxID: txHashStr, 2546 WalletConflicts: []string{}, 2547 Time: received, 2548 TimeReceived: received, 2549 TxType: &txTypeStr, 2550 } 2551 2552 // Add a received/generated/immature result if this is a credit. 2553 // If the output was spent, create a second result under the 2554 // send category with the inverse of the output amount. It is 2555 // therefore possible that a single output may be included in 2556 // the results set zero, one, or two times. 2557 // 2558 // Since credits are not saved for outputs that are not 2559 // controlled by this wallet, all non-credits from transactions 2560 // with debits are grouped under the send category. 2561 2562 if send { 2563 result.Category = "send" 2564 result.Amount = -amountF64 2565 result.Fee = &feeF64 2566 sends = append(sends, result) 2567 } 2568 if isCredit { 2569 result.Account = accountName 2570 result.Category = recvCat 2571 result.Amount = amountF64 2572 result.Fee = nil 2573 receives = append(receives, result) 2574 } 2575 } 2576 return sends, receives 2577 } 2578 2579 // ListSinceBlock returns a slice of objects with details about transactions 2580 // since the given block. If the block is -1 then all transactions are included. 2581 // This is intended to be used for listsinceblock RPC replies. 2582 func (w *Wallet) ListSinceBlock(ctx context.Context, start, end, syncHeight int32) ([]types.ListTransactionsResult, error) { 2583 const op errors.Op = "wallet.ListSinceBlock" 2584 txList := []types.ListTransactionsResult{} 2585 err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 2586 txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) 2587 2588 rangeFn := func(details []udb.TxDetails) (bool, error) { 2589 for _, detail := range details { 2590 sends, receives := listTransactions(tx, &detail, 2591 w.manager, syncHeight, w.chainParams) 2592 txList = append(txList, receives...) 2593 txList = append(txList, sends...) 2594 } 2595 return false, nil 2596 } 2597 2598 return w.txStore.RangeTransactions(ctx, txmgrNs, start, end, rangeFn) 2599 }) 2600 if err != nil { 2601 return nil, errors.E(op, err) 2602 } 2603 return txList, nil 2604 } 2605 2606 // ListTransactions returns a slice of objects with details about a recorded 2607 // transaction. This is intended to be used for listtransactions RPC 2608 // replies. 2609 func (w *Wallet) ListTransactions(ctx context.Context, from, count int) ([]types.ListTransactionsResult, error) { 2610 const op errors.Op = "wallet.ListTransactions" 2611 txList := []types.ListTransactionsResult{} 2612 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 2613 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 2614 2615 // Get current block. The block height used for calculating 2616 // the number of tx confirmations. 2617 _, tipHeight := w.txStore.MainChainTip(dbtx) 2618 2619 // Need to skip the first from transactions, and after those, only 2620 // include the next count transactions. 2621 skipped := 0 2622 n := 0 2623 2624 rangeFn := func(details []udb.TxDetails) (bool, error) { 2625 // Iterate over transactions at this height in reverse order. 2626 // This does nothing for unmined transactions, which are 2627 // unsorted, but it will process mined transactions in the 2628 // reverse order they were marked mined. 2629 for i := len(details) - 1; i >= 0; i-- { 2630 if n >= count { 2631 return true, nil 2632 } 2633 2634 if from > skipped { 2635 skipped++ 2636 continue 2637 } 2638 2639 sends, receives := listTransactions(dbtx, &details[i], 2640 w.manager, tipHeight, w.chainParams) 2641 txList = append(txList, sends...) 2642 txList = append(txList, receives...) 2643 2644 if len(sends) != 0 || len(receives) != 0 { 2645 n++ 2646 } 2647 } 2648 2649 return false, nil 2650 } 2651 2652 // Return newer results first by starting at mempool height and working 2653 // down to the genesis block. 2654 return w.txStore.RangeTransactions(ctx, txmgrNs, -1, 0, rangeFn) 2655 }) 2656 if err != nil { 2657 return nil, errors.E(op, err) 2658 } 2659 2660 // reverse the list so that it is sorted from old to new. 2661 for i, j := 0, len(txList)-1; i < j; i, j = i+1, j-1 { 2662 txList[i], txList[j] = txList[j], txList[i] 2663 } 2664 return txList, nil 2665 } 2666 2667 // ListAddressTransactions returns a slice of objects with details about 2668 // recorded transactions to or from any address belonging to a set. This is 2669 // intended to be used for listaddresstransactions RPC replies. 2670 func (w *Wallet) ListAddressTransactions(ctx context.Context, pkHashes map[string]struct{}) ([]types.ListTransactionsResult, error) { 2671 const op errors.Op = "wallet.ListAddressTransactions" 2672 txList := []types.ListTransactionsResult{} 2673 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 2674 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 2675 2676 // Get current block. The block height used for calculating 2677 // the number of tx confirmations. 2678 _, tipHeight := w.txStore.MainChainTip(dbtx) 2679 rangeFn := func(details []udb.TxDetails) (bool, error) { 2680 loopDetails: 2681 for i := range details { 2682 detail := &details[i] 2683 2684 for _, cred := range detail.Credits { 2685 if detail.MsgTx.TxOut[cred.Index].Version != scriptVersionAssumed { 2686 continue 2687 } 2688 pkh := stdscript.ExtractPubKeyHashV0(detail.MsgTx.TxOut[cred.Index].PkScript) 2689 if _, ok := pkHashes[string(pkh)]; !ok { 2690 continue 2691 } 2692 2693 sends, receives := listTransactions(dbtx, detail, 2694 w.manager, tipHeight, w.chainParams) 2695 txList = append(txList, receives...) 2696 txList = append(txList, sends...) 2697 continue loopDetails 2698 } 2699 } 2700 return false, nil 2701 } 2702 2703 return w.txStore.RangeTransactions(ctx, txmgrNs, 0, -1, rangeFn) 2704 }) 2705 if err != nil { 2706 return nil, errors.E(op, err) 2707 } 2708 return txList, nil 2709 } 2710 2711 // ListAllTransactions returns a slice of objects with details about a recorded 2712 // transaction. This is intended to be used for listalltransactions RPC 2713 // replies. 2714 func (w *Wallet) ListAllTransactions(ctx context.Context) ([]types.ListTransactionsResult, error) { 2715 const op errors.Op = "wallet.ListAllTransactions" 2716 txList := []types.ListTransactionsResult{} 2717 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 2718 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 2719 2720 // Get current block. The block height used for calculating 2721 // the number of tx confirmations. 2722 _, tipHeight := w.txStore.MainChainTip(dbtx) 2723 2724 rangeFn := func(details []udb.TxDetails) (bool, error) { 2725 // Iterate over transactions at this height in reverse 2726 // order. This does nothing for unmined transactions, 2727 // which are unsorted, but it will process mined 2728 // transactions in the reverse order they were marked 2729 // mined. 2730 for i := len(details) - 1; i >= 0; i-- { 2731 sends, receives := listTransactions(dbtx, &details[i], 2732 w.manager, tipHeight, w.chainParams) 2733 txList = append(txList, sends...) 2734 txList = append(txList, receives...) 2735 } 2736 return false, nil 2737 } 2738 2739 // Return newer results first by starting at mempool height and 2740 // working down to the genesis block. 2741 return w.txStore.RangeTransactions(ctx, txmgrNs, -1, 0, rangeFn) 2742 }) 2743 if err != nil { 2744 return nil, errors.E(op, err) 2745 } 2746 2747 // reverse the list so that it is sorted from old to new. 2748 for i, j := 0, len(txList)-1; i < j; i, j = i+1, j-1 { 2749 txList[i], txList[j] = txList[j], txList[i] 2750 } 2751 return txList, nil 2752 } 2753 2754 // ListTransactionDetails returns the listtransaction results for a single 2755 // transaction. 2756 func (w *Wallet) ListTransactionDetails(ctx context.Context, txHash *chainhash.Hash) ([]types.ListTransactionsResult, error) { 2757 const op errors.Op = "wallet.ListTransactionDetails" 2758 txList := []types.ListTransactionsResult{} 2759 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 2760 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 2761 2762 // Get current block. The block height used for calculating 2763 // the number of tx confirmations. 2764 _, tipHeight := w.txStore.MainChainTip(dbtx) 2765 2766 txd, err := w.txStore.TxDetails(txmgrNs, txHash) 2767 if err != nil { 2768 return err 2769 } 2770 sends, receives := listTransactions(dbtx, txd, w.manager, tipHeight, w.chainParams) 2771 txList = make([]types.ListTransactionsResult, 0, len(sends)+len(receives)) 2772 txList = append(txList, receives...) 2773 txList = append(txList, sends...) 2774 return nil 2775 }) 2776 if err != nil { 2777 return nil, errors.E(op, err) 2778 } 2779 return txList, nil 2780 } 2781 2782 // BlockIdentifier identifies a block by either a height in the main chain or a 2783 // hash. 2784 type BlockIdentifier struct { 2785 height int32 2786 hash *chainhash.Hash 2787 } 2788 2789 // NewBlockIdentifierFromHeight constructs a BlockIdentifier for a block height. 2790 func NewBlockIdentifierFromHeight(height int32) *BlockIdentifier { 2791 return &BlockIdentifier{height: height} 2792 } 2793 2794 // NewBlockIdentifierFromHash constructs a BlockIdentifier for a block hash. 2795 func NewBlockIdentifierFromHash(hash *chainhash.Hash) *BlockIdentifier { 2796 return &BlockIdentifier{hash: hash} 2797 } 2798 2799 // BlockInfo records info pertaining to a block. It does not include any 2800 // information about wallet transactions contained in the block. 2801 type BlockInfo struct { 2802 Hash chainhash.Hash 2803 Height int32 2804 Confirmations int32 2805 Header []byte 2806 Timestamp int64 2807 StakeInvalidated bool 2808 } 2809 2810 // BlockInfo returns info regarding a block recorded by the wallet. 2811 func (w *Wallet) BlockInfo(ctx context.Context, blockID *BlockIdentifier) (*BlockInfo, error) { 2812 const op errors.Op = "wallet.BlockInfo" 2813 var blockInfo *BlockInfo 2814 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 2815 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 2816 _, tipHeight := w.txStore.MainChainTip(dbtx) 2817 blockHash := blockID.hash 2818 if blockHash == nil { 2819 hash, err := w.txStore.GetMainChainBlockHashForHeight(txmgrNs, 2820 blockID.height) 2821 if err != nil { 2822 return err 2823 } 2824 blockHash = &hash 2825 } 2826 header, err := w.txStore.GetSerializedBlockHeader(txmgrNs, blockHash) 2827 if err != nil { 2828 return err 2829 } 2830 height := udb.ExtractBlockHeaderHeight(header) 2831 inMainChain, invalidated := w.txStore.BlockInMainChain(dbtx, blockHash) 2832 var confs int32 2833 if inMainChain { 2834 confs = confirms(height, tipHeight) 2835 } 2836 blockInfo = &BlockInfo{ 2837 Hash: *blockHash, 2838 Height: height, 2839 Confirmations: confs, 2840 Header: header, 2841 Timestamp: udb.ExtractBlockHeaderTime(header), 2842 StakeInvalidated: invalidated, 2843 } 2844 return nil 2845 }) 2846 if err != nil { 2847 return nil, errors.E(op, err) 2848 } 2849 return blockInfo, nil 2850 } 2851 2852 // TransactionSummary returns details about a recorded transaction that is 2853 // relevant to the wallet in some way. 2854 func (w *Wallet) TransactionSummary(ctx context.Context, txHash *chainhash.Hash) (txSummary *TransactionSummary, confs int32, blockHash *chainhash.Hash, err error) { 2855 const opf = "wallet.TransactionSummary(%v)" 2856 err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 2857 ns := dbtx.ReadBucket(wtxmgrNamespaceKey) 2858 _, tipHeight := w.txStore.MainChainTip(dbtx) 2859 txDetails, err := w.txStore.TxDetails(ns, txHash) 2860 if err != nil { 2861 return err 2862 } 2863 txSummary = new(TransactionSummary) 2864 *txSummary = makeTxSummary(dbtx, w, txDetails) 2865 confs = confirms(txDetails.Height(), tipHeight) 2866 if confs > 0 { 2867 blockHash = &txDetails.Block.Hash 2868 } 2869 return nil 2870 }) 2871 if err != nil { 2872 op := errors.Opf(opf, txHash) 2873 return nil, 0, nil, errors.E(op, err) 2874 } 2875 return txSummary, confs, blockHash, nil 2876 } 2877 2878 // fetchTicketDetails returns the ticket details of the provided ticket hash. 2879 func (w *Wallet) fetchTicketDetails(ns walletdb.ReadBucket, hash *chainhash.Hash) (*udb.TicketDetails, error) { 2880 txDetail, err := w.txStore.TxDetails(ns, hash) 2881 if err != nil { 2882 return nil, err 2883 } 2884 2885 if txDetail.TxType != stake.TxTypeSStx { 2886 return nil, errors.Errorf("%v is not a ticket", hash) 2887 } 2888 2889 ticketDetails, err := w.txStore.TicketDetails(ns, txDetail) 2890 if err != nil { 2891 return nil, errors.Errorf("%v while trying to get ticket"+ 2892 " details for txhash: %v", err, hash) 2893 } 2894 2895 return ticketDetails, nil 2896 } 2897 2898 // TicketSummary contains the properties to describe a ticket's current status 2899 type TicketSummary struct { 2900 Ticket *TransactionSummary 2901 Spender *TransactionSummary 2902 Status TicketStatus 2903 } 2904 2905 // TicketStatus describes the current status a ticket can be observed to be. 2906 type TicketStatus uint 2907 2908 //go:generate stringer -type=TicketStatus -linecomment 2909 2910 const ( 2911 // TicketStatusUnknown any ticket that its status was unable to be determined. 2912 TicketStatusUnknown TicketStatus = iota // unknown 2913 2914 // TicketStatusUnmined any not yet mined ticket. 2915 TicketStatusUnmined // unmined 2916 2917 // TicketStatusImmature any so to be live ticket. 2918 TicketStatusImmature // immature 2919 2920 // TicketStatusLive any currently live ticket. 2921 TicketStatusLive // live 2922 2923 // TicketStatusVoted any ticket that was seen to have voted. 2924 TicketStatusVoted // voted 2925 2926 // TicketStatusMissed any ticket that has yet to be revoked, and was missed. 2927 TicketStatusMissed // missed 2928 2929 // TicketStatusExpired any ticket that has yet to be revoked, and was expired. 2930 // In SPV mode, this status may be used by unspent tickets definitely 2931 // past the expiry period, even if the ticket was actually missed rather than 2932 // expiring. 2933 TicketStatusExpired // expired 2934 2935 // TicketStatusUnspent is a matured ticket that has not been spent. It 2936 // is only used under SPV mode where it is unknown if a ticket is live, 2937 // was missed, or expired. 2938 TicketStatusUnspent // unspent 2939 2940 // TicketStatusRevoked any ticket that has been previously revoked. 2941 // 2942 // Deprecated: The ticket status will be either missed or expired 2943 // instead. There are no more unrevoked missed/expired tickets. 2944 TicketStatusRevoked // revoked 2945 ) 2946 2947 func makeTicketSummary(ctx context.Context, rpc *dcrd.RPC, dbtx walletdb.ReadTx, 2948 w *Wallet, details *udb.TicketDetails) *TicketSummary { 2949 2950 ticketHeight := details.Ticket.Height() 2951 _, tipHeight := w.txStore.MainChainTip(dbtx) 2952 2953 ticketTransactionDetails := makeTxSummary(dbtx, w, details.Ticket) 2954 summary := &TicketSummary{ 2955 Ticket: &ticketTransactionDetails, 2956 Status: TicketStatusLive, 2957 } 2958 if rpc == nil { 2959 summary.Status = TicketStatusUnspent 2960 } 2961 // Check if ticket is unmined or immature 2962 switch { 2963 case ticketHeight == int32(-1): 2964 summary.Status = TicketStatusUnmined 2965 case !ticketMatured(w.chainParams, ticketHeight, tipHeight): 2966 summary.Status = TicketStatusImmature 2967 } 2968 2969 if details.Spender != nil { 2970 spenderTransactionDetails := makeTxSummary(dbtx, w, details.Spender) 2971 summary.Spender = &spenderTransactionDetails 2972 2973 switch details.Spender.TxType { 2974 case stake.TxTypeSSGen: 2975 summary.Status = TicketStatusVoted 2976 case stake.TxTypeSSRtx: 2977 params := w.chainParams 2978 ticketHeight := details.Ticket.Block.Height 2979 revocationHeight := details.Spender.Block.Height 2980 expired := revocationHeight-ticketHeight >= 2981 int32(params.TicketExpiryBlocks())+ 2982 int32(params.TicketMaturity) 2983 if expired { 2984 summary.Status = TicketStatusExpired 2985 } else { 2986 summary.Status = TicketStatusMissed 2987 } 2988 } 2989 } 2990 2991 return summary 2992 } 2993 2994 // GetTicketInfoPrecise returns the ticket summary and the corresponding block header 2995 // for the provided ticket. The ticket summary is comprised of the transaction 2996 // summmary for the ticket, the spender (if already spent) and the ticket's 2997 // current status. 2998 // 2999 // If the ticket is unmined, then the returned block header will be nil. 3000 // 3001 // The argument chainClient is always expected to be not nil in this case, 3002 // otherwise one should use the alternative GetTicketInfo instead. With 3003 // the ability to use the rpc chain client, this function is able to determine 3004 // whether a ticket has been missed or not. Otherwise, it is just known to be 3005 // unspent (possibly live or missed). 3006 func (w *Wallet) GetTicketInfoPrecise(ctx context.Context, rpcCaller Caller, hash *chainhash.Hash) (*TicketSummary, *wire.BlockHeader, error) { 3007 const op errors.Op = "wallet.GetTicketInfoPrecise" 3008 3009 var ticketSummary *TicketSummary 3010 var blockHeader *wire.BlockHeader 3011 3012 rpc := dcrd.New(rpcCaller) 3013 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 3014 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 3015 3016 ticketDetails, err := w.fetchTicketDetails(txmgrNs, hash) 3017 if err != nil { 3018 return err 3019 } 3020 3021 ticketSummary = makeTicketSummary(ctx, rpc, dbtx, w, ticketDetails) 3022 if ticketDetails.Ticket.Block.Height == -1 { 3023 // unmined tickets do not have an associated block header 3024 return nil 3025 } 3026 3027 // Fetch the associated block header of the ticket. 3028 hBytes, err := w.txStore.GetSerializedBlockHeader(txmgrNs, 3029 &ticketDetails.Ticket.Block.Hash) 3030 if err != nil { 3031 return err 3032 } 3033 3034 blockHeader = new(wire.BlockHeader) 3035 err = blockHeader.FromBytes(hBytes) 3036 if err != nil { 3037 return err 3038 } 3039 3040 return nil 3041 }) 3042 if err != nil { 3043 return nil, nil, errors.E(op, err) 3044 } 3045 3046 return ticketSummary, blockHeader, nil 3047 } 3048 3049 // GetTicketInfo returns the ticket summary and the corresponding block header 3050 // for the provided ticket. The ticket summary is comprised of the transaction 3051 // summmary for the ticket, the spender (if already spent) and the ticket's 3052 // current status. 3053 // 3054 // If the ticket is unmined, then the returned block header will be nil. 3055 func (w *Wallet) GetTicketInfo(ctx context.Context, hash *chainhash.Hash) (*TicketSummary, *wire.BlockHeader, error) { 3056 const op errors.Op = "wallet.GetTicketInfo" 3057 3058 var ticketSummary *TicketSummary 3059 var blockHeader *wire.BlockHeader 3060 3061 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 3062 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 3063 3064 ticketDetails, err := w.fetchTicketDetails(txmgrNs, hash) 3065 if err != nil { 3066 return err 3067 } 3068 3069 ticketSummary = makeTicketSummary(ctx, nil, dbtx, w, ticketDetails) 3070 if ticketDetails.Ticket.Block.Height == -1 { 3071 // unmined tickets do not have an associated block header 3072 return nil 3073 } 3074 3075 // Fetch the associated block header of the ticket. 3076 hBytes, err := w.txStore.GetSerializedBlockHeader(txmgrNs, 3077 &ticketDetails.Ticket.Block.Hash) 3078 if err != nil { 3079 return err 3080 } 3081 3082 blockHeader = new(wire.BlockHeader) 3083 err = blockHeader.FromBytes(hBytes) 3084 if err != nil { 3085 return err 3086 } 3087 3088 return nil 3089 }) 3090 if err != nil { 3091 return nil, nil, errors.E(op, err) 3092 } 3093 3094 return ticketSummary, blockHeader, nil 3095 } 3096 3097 // blockRange returns the start and ending heights for the given set of block 3098 // identifiers or the default values used to range over the entire chain 3099 // (including unmined transactions). 3100 func (w *Wallet) blockRange(dbtx walletdb.ReadTx, startBlock, endBlock *BlockIdentifier) (int32, int32, error) { 3101 var start, end int32 = 0, -1 3102 ns := dbtx.ReadBucket(wtxmgrNamespaceKey) 3103 3104 switch { 3105 case startBlock == nil: 3106 // Hardcoded default start of 0. 3107 case startBlock.hash == nil: 3108 start = startBlock.height 3109 default: 3110 serHeader, err := w.txStore.GetSerializedBlockHeader(ns, startBlock.hash) 3111 if err != nil { 3112 return 0, 0, err 3113 } 3114 var startHeader wire.BlockHeader 3115 err = startHeader.Deserialize(bytes.NewReader(serHeader)) 3116 if err != nil { 3117 return 0, 0, err 3118 } 3119 start = int32(startHeader.Height) 3120 } 3121 3122 switch { 3123 case endBlock == nil: 3124 // Hardcoded default end of -1. 3125 case endBlock.hash == nil: 3126 end = endBlock.height 3127 default: 3128 serHeader, err := w.txStore.GetSerializedBlockHeader(ns, endBlock.hash) 3129 if err != nil { 3130 return 0, 0, err 3131 } 3132 var endHeader wire.BlockHeader 3133 err = endHeader.Deserialize(bytes.NewReader(serHeader)) 3134 if err != nil { 3135 return 0, 0, err 3136 } 3137 end = int32(endHeader.Height) 3138 } 3139 3140 return start, end, nil 3141 } 3142 3143 // GetTicketsPrecise calls function f for all tickets located in between the 3144 // given startBlock and endBlock. TicketSummary includes TransactionSummmary 3145 // for the ticket and the spender (if already spent) and the ticket's current 3146 // status. The function f also receives block header of the ticket. All 3147 // tickets on a given call belong to the same block and at least one ticket 3148 // is present when f is called. If the ticket is unmined, the block header will 3149 // be nil. 3150 // 3151 // The function f may return an error which, if non-nil, is propagated to the 3152 // caller. Additionally, a boolean return value allows exiting the function 3153 // early without reading any additional transactions when true. 3154 // 3155 // The arguments to f may be reused and should not be kept by the caller. 3156 // 3157 // The argument chainClient is always expected to be not nil in this case, 3158 // otherwise one should use the alternative GetTickets instead. With 3159 // the ability to use the rpc chain client, this function is able to determine 3160 // whether tickets have been missed or not. Otherwise, tickets are just known 3161 // to be unspent (possibly live or missed). 3162 func (w *Wallet) GetTicketsPrecise(ctx context.Context, rpcCaller Caller, 3163 f func([]*TicketSummary, *wire.BlockHeader) (bool, error), 3164 startBlock, endBlock *BlockIdentifier) error { 3165 3166 const op errors.Op = "wallet.GetTicketsPrecise" 3167 3168 rpc := dcrd.New(rpcCaller) 3169 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 3170 start, end, err := w.blockRange(dbtx, startBlock, endBlock) 3171 if err != nil { 3172 return err 3173 } 3174 3175 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 3176 header := &wire.BlockHeader{} 3177 3178 rangeFn := func(details []udb.TxDetails) (bool, error) { 3179 tickets := make([]*TicketSummary, 0, len(details)) 3180 3181 for i := range details { 3182 ticketInfo, err := w.txStore.TicketDetails(txmgrNs, &details[i]) 3183 if err != nil { 3184 return false, errors.Errorf("%v while trying to get "+ 3185 "ticket details for txhash: %v", err, &details[i].Hash) 3186 } 3187 // Continue if not a ticket 3188 if ticketInfo == nil { 3189 continue 3190 } 3191 summary := makeTicketSummary(ctx, rpc, dbtx, w, ticketInfo) 3192 tickets = append(tickets, summary) 3193 } 3194 3195 if len(tickets) == 0 { 3196 return false, nil 3197 } 3198 3199 if details[0].Block.Height == -1 { 3200 return f(tickets, nil) 3201 } 3202 3203 blockHash := &details[0].Block.Hash 3204 headerBytes, err := w.txStore.GetSerializedBlockHeader(txmgrNs, blockHash) 3205 if err != nil { 3206 return false, err 3207 } 3208 header.FromBytes(headerBytes) 3209 return f(tickets, header) 3210 } 3211 3212 return w.txStore.RangeTransactions(ctx, txmgrNs, start, end, rangeFn) 3213 }) 3214 if err != nil { 3215 return errors.E(op, err) 3216 } 3217 return nil 3218 } 3219 3220 // GetTickets calls function f for all tickets located in between the 3221 // given startBlock and endBlock. TicketSummary includes TransactionSummmary 3222 // for the ticket and the spender (if already spent) and the ticket's current 3223 // status. The function f also receives block header of the ticket. All 3224 // tickets on a given call belong to the same block and at least one ticket 3225 // is present when f is called. If the ticket is unmined, the block header will 3226 // be nil. 3227 // 3228 // The function f may return an error which, if non-nil, is propagated to the 3229 // caller. Additionally, a boolean return value allows exiting the function 3230 // early without reading any additional transactions when true. 3231 // 3232 // The arguments to f may be reused and should not be kept by the caller. 3233 // 3234 // Because this function does not have any chain client argument, tickets are 3235 // unable to be determined whether or not they have been missed, simply unspent. 3236 func (w *Wallet) GetTickets(ctx context.Context, 3237 f func([]*TicketSummary, *wire.BlockHeader) (bool, error), 3238 startBlock, endBlock *BlockIdentifier) error { 3239 3240 const op errors.Op = "wallet.GetTickets" 3241 3242 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 3243 start, end, err := w.blockRange(dbtx, startBlock, endBlock) 3244 if err != nil { 3245 return err 3246 } 3247 3248 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 3249 header := &wire.BlockHeader{} 3250 3251 rangeFn := func(details []udb.TxDetails) (bool, error) { 3252 tickets := make([]*TicketSummary, 0, len(details)) 3253 3254 for i := range details { 3255 ticketInfo, err := w.txStore.TicketDetails(txmgrNs, &details[i]) 3256 if err != nil { 3257 return false, errors.Errorf("%v while trying to get "+ 3258 "ticket details for txhash: %v", err, &details[i].Hash) 3259 } 3260 // Continue if not a ticket 3261 if ticketInfo == nil { 3262 continue 3263 } 3264 summary := makeTicketSummary(ctx, nil, dbtx, w, ticketInfo) 3265 tickets = append(tickets, summary) 3266 } 3267 3268 if len(tickets) == 0 { 3269 return false, nil 3270 } 3271 3272 if details[0].Block.Height == -1 { 3273 return f(tickets, nil) 3274 } 3275 3276 blockHash := &details[0].Block.Hash 3277 headerBytes, err := w.txStore.GetSerializedBlockHeader(txmgrNs, blockHash) 3278 if err != nil { 3279 return false, err 3280 } 3281 header.FromBytes(headerBytes) 3282 return f(tickets, header) 3283 } 3284 3285 return w.txStore.RangeTransactions(ctx, txmgrNs, start, end, rangeFn) 3286 }) 3287 if err != nil { 3288 return errors.E(op, err) 3289 } 3290 return nil 3291 } 3292 3293 // GetTransactions runs the function f on all transactions between a starting 3294 // and ending block. Blocks in the block range may be specified by either a 3295 // height or a hash. 3296 // 3297 // The function f may return an error which, if non-nil, is propagated to the 3298 // caller. Additionally, a boolean return value allows exiting the function 3299 // early without reading any additional transactions when true. 3300 // 3301 // Transaction results are organized by blocks in ascending order and unmined 3302 // transactions in an unspecified order. Mined transactions are saved in a 3303 // Block structure which records properties about the block. Unmined 3304 // transactions are returned on a Block structure with height == -1. 3305 // 3306 // Internally this function uses the udb store RangeTransactions function, 3307 // therefore the notes and restrictions of that function also apply here. 3308 func (w *Wallet) GetTransactions(ctx context.Context, f func(*Block) (bool, error), startBlock, endBlock *BlockIdentifier) error { 3309 const op errors.Op = "wallet.GetTransactions" 3310 3311 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 3312 start, end, err := w.blockRange(dbtx, startBlock, endBlock) 3313 if err != nil { 3314 return err 3315 } 3316 3317 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 3318 3319 rangeFn := func(details []udb.TxDetails) (bool, error) { 3320 // TODO: probably should make RangeTransactions not reuse the 3321 // details backing array memory. 3322 dets := make([]udb.TxDetails, len(details)) 3323 copy(dets, details) 3324 details = dets 3325 3326 txs := make([]TransactionSummary, 0, len(details)) 3327 for i := range details { 3328 txs = append(txs, makeTxSummary(dbtx, w, &details[i])) 3329 } 3330 3331 var block *Block 3332 if details[0].Block.Height != -1 { 3333 serHeader, err := w.txStore.GetSerializedBlockHeader(txmgrNs, 3334 &details[0].Block.Hash) 3335 if err != nil { 3336 return false, err 3337 } 3338 header := new(wire.BlockHeader) 3339 err = header.Deserialize(bytes.NewReader(serHeader)) 3340 if err != nil { 3341 return false, err 3342 } 3343 block = &Block{ 3344 Header: header, 3345 Transactions: txs, 3346 } 3347 } else { 3348 block = &Block{ 3349 Header: nil, 3350 Transactions: txs, 3351 } 3352 } 3353 3354 return f(block) 3355 } 3356 3357 return w.txStore.RangeTransactions(ctx, txmgrNs, start, end, rangeFn) 3358 }) 3359 if err != nil { 3360 return errors.E(op, err) 3361 } 3362 return nil 3363 } 3364 3365 // Spender queries for the transaction and input index which spends a Credit. 3366 // If the output is not a Credit, an error with code ErrInput is returned. If 3367 // the output is unspent, the ErrNoExist code is used. 3368 func (w *Wallet) Spender(ctx context.Context, out *wire.OutPoint) (*wire.MsgTx, uint32, error) { 3369 var spender *wire.MsgTx 3370 var spenderIndex uint32 3371 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 3372 var err error 3373 spender, spenderIndex, err = w.txStore.Spender(dbtx, out) 3374 return err 3375 }) 3376 return spender, spenderIndex, err 3377 } 3378 3379 // UnspentOutput returns information about an unspent received transaction 3380 // output. Returns error NotExist if the specified outpoint cannot be found or 3381 // has been spent by a mined transaction. Mined transactions that are spent by 3382 // a mempool transaction are not affected by this. 3383 func (w *Wallet) UnspentOutput(ctx context.Context, op wire.OutPoint, includeMempool bool) (*udb.Credit, error) { 3384 var utxo *udb.Credit 3385 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 3386 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 3387 var err error 3388 utxo, err = w.txStore.UnspentOutput(txmgrNs, op, includeMempool) 3389 return err 3390 }) 3391 return utxo, err 3392 } 3393 3394 // AccountProperties contains properties associated with each account, such as 3395 // the account name, number, and the nubmer of derived and imported keys. If no 3396 // address usage has been recorded on any of the external or internal branches, 3397 // the child index is ^uint32(0). 3398 type AccountProperties struct { 3399 AccountNumber uint32 3400 AccountName string 3401 AccountType uint8 3402 LastUsedExternalIndex uint32 3403 LastUsedInternalIndex uint32 3404 LastReturnedExternalIndex uint32 3405 LastReturnedInternalIndex uint32 3406 ImportedKeyCount uint32 3407 AccountEncrypted bool 3408 AccountUnlocked bool 3409 } 3410 3411 // AccountResult is a single account result for the AccountsResult type. 3412 type AccountResult struct { 3413 AccountProperties 3414 TotalBalance dcrutil.Amount 3415 } 3416 3417 // AccountsResult is the resutl of the wallet's Accounts method. See that 3418 // method for more details. 3419 type AccountsResult struct { 3420 Accounts []AccountResult 3421 CurrentBlockHash *chainhash.Hash 3422 CurrentBlockHeight int32 3423 } 3424 3425 // Accounts returns the current names, numbers, and total balances of all 3426 // accounts in the wallet. The current chain tip is included in the result for 3427 // atomicity reasons. 3428 // 3429 // TODO(jrick): Is the chain tip really needed, since only the total balances 3430 // are included? 3431 func (w *Wallet) Accounts(ctx context.Context) (*AccountsResult, error) { 3432 const op errors.Op = "wallet.Accounts" 3433 var ( 3434 accounts []AccountResult 3435 tipHash chainhash.Hash 3436 tipHeight int32 3437 ) 3438 3439 defer w.lockedOutpointMu.Unlock() 3440 w.lockedOutpointMu.Lock() 3441 3442 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 3443 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) 3444 3445 tipHash, tipHeight = w.txStore.MainChainTip(dbtx) 3446 unspent, err := w.txStore.UnspentOutputs(dbtx) 3447 if err != nil { 3448 return err 3449 } 3450 err = w.manager.ForEachAccount(addrmgrNs, func(acct uint32) error { 3451 props, err := w.manager.AccountProperties(addrmgrNs, acct) 3452 if err != nil { 3453 return err 3454 } 3455 accounts = append(accounts, AccountResult{ 3456 AccountProperties: *props, 3457 // TotalBalance set below 3458 }) 3459 return nil 3460 }) 3461 if err != nil { 3462 return err 3463 } 3464 m := make(map[uint32]*dcrutil.Amount) 3465 for i := range accounts { 3466 a := &accounts[i] 3467 m[a.AccountNumber] = &a.TotalBalance 3468 } 3469 for i := range unspent { 3470 output := unspent[i] 3471 _, addrs := stdscript.ExtractAddrs(scriptVersionAssumed, output.PkScript, w.chainParams) 3472 if len(addrs) == 0 { 3473 continue 3474 } 3475 outputAcct, err := w.manager.AddrAccount(addrmgrNs, addrs[0]) 3476 if err == nil { 3477 amt, ok := m[outputAcct] 3478 if ok { 3479 *amt += output.Amount 3480 } 3481 } 3482 } 3483 return nil 3484 }) 3485 if err != nil { 3486 return nil, errors.E(op, err) 3487 } 3488 return &AccountsResult{ 3489 Accounts: accounts, 3490 CurrentBlockHash: &tipHash, 3491 CurrentBlockHeight: tipHeight, 3492 }, nil 3493 } 3494 3495 // creditSlice satisifies the sort.Interface interface to provide sorting 3496 // transaction credits from oldest to newest. Credits with the same receive 3497 // time and mined in the same block are not guaranteed to be sorted by the order 3498 // they appear in the block. Credits from the same transaction are sorted by 3499 // output index. 3500 type creditSlice []*udb.Credit 3501 3502 func (s creditSlice) Len() int { 3503 return len(s) 3504 } 3505 3506 func (s creditSlice) Less(i, j int) bool { 3507 switch { 3508 // If both credits are from the same tx, sort by output index. 3509 case s[i].OutPoint.Hash == s[j].OutPoint.Hash: 3510 return s[i].OutPoint.Index < s[j].OutPoint.Index 3511 3512 // If both transactions are unmined, sort by their received date. 3513 case s[i].Height == -1 && s[j].Height == -1: 3514 return s[i].Received.Before(s[j].Received) 3515 3516 // Unmined (newer) txs always come last. 3517 case s[i].Height == -1: 3518 return false 3519 case s[j].Height == -1: 3520 return true 3521 3522 // If both txs are mined in different blocks, sort by block height. 3523 default: 3524 return s[i].Height < s[j].Height 3525 } 3526 } 3527 3528 func (s creditSlice) Swap(i, j int) { 3529 s[i], s[j] = s[j], s[i] 3530 } 3531 3532 // ListUnspent returns a slice of objects representing the unspent wallet 3533 // transactions fitting the given criteria. The confirmations will be more than 3534 // minconf, less than maxconf and if addresses is populated only the addresses 3535 // contained within it will be considered. If we know nothing about a 3536 // transaction an empty array will be returned. 3537 func (w *Wallet) ListUnspent(ctx context.Context, minconf, maxconf int32, addresses map[string]struct{}, accountName string) ([]*types.ListUnspentResult, error) { 3538 const op errors.Op = "wallet.ListUnspent" 3539 var results []*types.ListUnspentResult 3540 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 3541 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) 3542 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 3543 3544 _, tipHeight := w.txStore.MainChainTip(dbtx) 3545 3546 filter := len(addresses) != 0 3547 unspent, err := w.txStore.UnspentOutputs(dbtx) 3548 if err != nil { 3549 return err 3550 } 3551 sort.Sort(sort.Reverse(creditSlice(unspent))) 3552 3553 defaultAccountName, err := w.manager.AccountName( 3554 addrmgrNs, udb.DefaultAccountNum) 3555 if err != nil { 3556 return err 3557 } 3558 3559 for i := range unspent { 3560 output := unspent[i] 3561 3562 details, err := w.txStore.TxDetails(txmgrNs, &output.Hash) 3563 if err != nil { 3564 return err 3565 } 3566 3567 // Outputs with fewer confirmations than the minimum or more 3568 // confs than the maximum are excluded. 3569 confs := confirms(output.Height, tipHeight) 3570 if confs < minconf || confs > maxconf { 3571 continue 3572 } 3573 3574 // Only mature coinbase outputs are included. 3575 if output.FromCoinBase { 3576 if !coinbaseMatured(w.chainParams, output.Height, tipHeight) { 3577 continue 3578 } 3579 } 3580 3581 switch details.TxRecord.TxType { 3582 case stake.TxTypeSStx: 3583 // Ticket commitment, only spendable after ticket maturity. 3584 if output.Index == 0 { 3585 if !ticketMatured(w.chainParams, details.Height(), tipHeight) { 3586 continue 3587 } 3588 } 3589 // Change outputs. 3590 if (output.Index > 0) && (output.Index%2 == 0) { 3591 if !ticketChangeMatured(w.chainParams, details.Height(), tipHeight) { 3592 continue 3593 } 3594 } 3595 case stake.TxTypeSSGen: 3596 // All non-OP_RETURN outputs for SSGen tx are only spendable 3597 // after coinbase maturity many blocks. 3598 if !coinbaseMatured(w.chainParams, details.Height(), tipHeight) { 3599 continue 3600 } 3601 case stake.TxTypeSSRtx: 3602 // All outputs for SSRtx tx are only spendable 3603 // after coinbase maturity many blocks. 3604 if !coinbaseMatured(w.chainParams, details.Height(), tipHeight) { 3605 continue 3606 } 3607 } 3608 3609 // Exclude locked outputs from the result set. 3610 if w.LockedOutpoint(&output.OutPoint.Hash, output.OutPoint.Index) { 3611 continue 3612 } 3613 3614 // Lookup the associated account for the output. Use the 3615 // default account name in case there is no associated account 3616 // for some reason, although this should never happen. 3617 // 3618 // This will be unnecessary once transactions and outputs are 3619 // grouped under the associated account in the db. 3620 acctName := defaultAccountName 3621 sc, addrs := stdscript.ExtractAddrs(scriptVersionAssumed, output.PkScript, w.chainParams) 3622 if len(addrs) > 0 { 3623 acct, err := w.manager.AddrAccount( 3624 addrmgrNs, addrs[0]) 3625 if err == nil { 3626 s, err := w.manager.AccountName( 3627 addrmgrNs, acct) 3628 if err == nil { 3629 acctName = s 3630 } 3631 } 3632 } 3633 if accountName != "" && accountName != acctName { 3634 continue 3635 } 3636 if filter { 3637 for _, addr := range addrs { 3638 _, ok := addresses[addr.String()] 3639 if ok { 3640 goto include 3641 } 3642 } 3643 continue 3644 } 3645 3646 include: 3647 // At the moment watch-only addresses are not supported, so all 3648 // recorded outputs that are not multisig are "spendable". 3649 // Multisig outputs are only "spendable" if all keys are 3650 // controlled by this wallet. 3651 // 3652 // TODO: Each case will need updates when watch-only addrs 3653 // is added. For P2PK, P2PKH, and P2SH, the address must be 3654 // looked up and not be watching-only. For multisig, all 3655 // pubkeys must belong to the manager with the associated 3656 // private key (currently it only checks whether the pubkey 3657 // exists, since the private key is required at the moment). 3658 var spendable bool 3659 var redeemScript []byte 3660 scSwitch: 3661 switch sc { 3662 case stdscript.STPubKeyHashEcdsaSecp256k1: 3663 spendable = true 3664 case stdscript.STPubKeyEcdsaSecp256k1: 3665 spendable = true 3666 case stdscript.STScriptHash: 3667 spendable = true 3668 if len(addrs) != 1 { 3669 return errors.Errorf("invalid address count for pay-to-script-hash output") 3670 } 3671 redeemScript, err = w.manager.RedeemScript(addrmgrNs, addrs[0]) 3672 if err != nil { 3673 return err 3674 } 3675 case stdscript.STStakeGenPubKeyHash, stdscript.STStakeGenScriptHash: 3676 spendable = true 3677 case stdscript.STStakeRevocationPubKeyHash, stdscript.STStakeRevocationScriptHash: 3678 spendable = true 3679 case stdscript.STStakeChangePubKeyHash, stdscript.STStakeChangeScriptHash: 3680 spendable = true 3681 case stdscript.STMultiSig: 3682 for _, a := range addrs { 3683 _, err := w.manager.Address(addrmgrNs, a) 3684 if err == nil { 3685 continue 3686 } 3687 if errors.Is(err, errors.NotExist) { 3688 break scSwitch 3689 } 3690 return err 3691 } 3692 spendable = true 3693 } 3694 3695 // If address decoding failed, the output is not spendable 3696 // regardless of detected script type. 3697 spendable = spendable && len(addrs) > 0 3698 3699 result := &types.ListUnspentResult{ 3700 TxID: output.OutPoint.Hash.String(), 3701 Vout: output.OutPoint.Index, 3702 Tree: output.OutPoint.Tree, 3703 Account: acctName, 3704 ScriptPubKey: hex.EncodeToString(output.PkScript), 3705 RedeemScript: hex.EncodeToString(redeemScript), 3706 TxType: int(details.TxType), 3707 Amount: output.Amount.ToCoin(), 3708 Confirmations: int64(confs), 3709 Spendable: spendable, 3710 } 3711 3712 // BUG: this should be a JSON array so that all 3713 // addresses can be included, or removed (and the 3714 // caller extracts addresses from the pkScript). 3715 if len(addrs) > 0 { 3716 result.Address = addrs[0].String() 3717 } 3718 3719 results = append(results, result) 3720 } 3721 return nil 3722 }) 3723 if err != nil { 3724 return nil, errors.E(op, err) 3725 } 3726 return results, nil 3727 } 3728 3729 func (w *Wallet) LoadPrivateKey(ctx context.Context, addr stdaddr.Address) (key *secp256k1.PrivateKey, 3730 zero func(), err error) { 3731 3732 const op errors.Op = "wallet.LoadPrivateKey" 3733 err = walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 3734 addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) 3735 var err error 3736 key, zero, err = w.manager.PrivateKey(addrmgrNs, addr) 3737 return err 3738 }) 3739 if err != nil { 3740 return nil, nil, errors.E(op, err) 3741 } 3742 3743 return key, zero, nil 3744 } 3745 3746 // DumpWIFPrivateKey returns the WIF encoded private key for a 3747 // single wallet address. 3748 func (w *Wallet) DumpWIFPrivateKey(ctx context.Context, addr stdaddr.Address) (string, error) { 3749 const op errors.Op = "wallet.DumpWIFPrivateKey" 3750 privKey, zero, err := w.LoadPrivateKey(ctx, addr) 3751 if err != nil { 3752 return "", errors.E(op, err) 3753 } 3754 defer zero() 3755 wif, err := dcrutil.NewWIF(privKey.Serialize(), w.chainParams.PrivateKeyID, 3756 dcrec.STEcdsaSecp256k1) 3757 if err != nil { 3758 return "", errors.E(op, err) 3759 } 3760 return wif.String(), nil 3761 } 3762 3763 // ImportPrivateKey imports a private key to the wallet and writes the new 3764 // wallet to disk. 3765 func (w *Wallet) ImportPrivateKey(ctx context.Context, wif *dcrutil.WIF) (string, error) { 3766 const op errors.Op = "wallet.ImportPrivateKey" 3767 // Attempt to import private key into wallet. 3768 var addr stdaddr.Address 3769 var props *udb.AccountProperties 3770 err := walletdb.Update(ctx, w.db, func(tx walletdb.ReadWriteTx) error { 3771 addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) 3772 maddr, err := w.manager.ImportPrivateKey(addrmgrNs, wif) 3773 if err == nil { 3774 addr = maddr.Address() 3775 props, err = w.manager.AccountProperties( 3776 addrmgrNs, udb.ImportedAddrAccount) 3777 } 3778 return err 3779 }) 3780 if err != nil { 3781 return "", errors.E(op, err) 3782 } 3783 3784 if n, err := w.NetworkBackend(); err == nil { 3785 err := n.LoadTxFilter(ctx, false, []stdaddr.Address{addr}, nil) 3786 if err != nil { 3787 return "", errors.E(op, err) 3788 } 3789 } 3790 3791 addrStr := addr.String() 3792 log.Infof("Imported payment address %s", addrStr) 3793 3794 w.NtfnServer.notifyAccountProperties(props) 3795 3796 // Return the payment address string of the imported private key. 3797 return addrStr, nil 3798 } 3799 3800 // ImportPublicKey imports a compressed secp256k1 public key and its derived 3801 // P2PKH address. 3802 func (w *Wallet) ImportPublicKey(ctx context.Context, pubkey []byte) (string, error) { 3803 const op errors.Op = "wallet.ImportPublicKey" 3804 // Attempt to import private key into wallet. 3805 var addr stdaddr.Address 3806 var props *udb.AccountProperties 3807 err := walletdb.Update(ctx, w.db, func(tx walletdb.ReadWriteTx) error { 3808 addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) 3809 maddr, err := w.manager.ImportPublicKey(addrmgrNs, pubkey) 3810 if err == nil { 3811 addr = maddr.Address() 3812 props, err = w.manager.AccountProperties( 3813 addrmgrNs, udb.ImportedAddrAccount) 3814 } 3815 return err 3816 }) 3817 if err != nil { 3818 return "", errors.E(op, err) 3819 } 3820 3821 if n, err := w.NetworkBackend(); err == nil { 3822 err := n.LoadTxFilter(ctx, false, []stdaddr.Address{addr}, nil) 3823 if err != nil { 3824 return "", errors.E(op, err) 3825 } 3826 } 3827 3828 addrStr := addr.String() 3829 log.Infof("Imported payment address %s", addrStr) 3830 3831 w.NtfnServer.notifyAccountProperties(props) 3832 3833 // Return the payment address string of the imported private key. 3834 return addrStr, nil 3835 } 3836 3837 // ImportScript imports a redeemscript to the wallet. If it also allows the 3838 // user to specify whether or not they want the redeemscript to be rescanned, 3839 // and how far back they wish to rescan. 3840 func (w *Wallet) ImportScript(ctx context.Context, rs []byte) error { 3841 const op errors.Op = "wallet.ImportScript" 3842 err := walletdb.Update(ctx, w.db, func(tx walletdb.ReadWriteTx) error { 3843 addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) 3844 mscriptaddr, err := w.manager.ImportScript(addrmgrNs, rs) 3845 if err != nil { 3846 return err 3847 } 3848 3849 addr := mscriptaddr.Address() 3850 if n, err := w.NetworkBackend(); err == nil { 3851 addrs := []stdaddr.Address{addr} 3852 err := n.LoadTxFilter(ctx, false, addrs, nil) 3853 if err != nil { 3854 return err 3855 } 3856 } 3857 3858 log.Infof("Imported script with P2SH address %v", addr) 3859 return nil 3860 }) 3861 if err != nil { 3862 return errors.E(op, err) 3863 } 3864 return nil 3865 } 3866 3867 // VotingXprivFromSeed derives a voting xpriv from a byte seed. 3868 func (w *Wallet) VotingXprivFromSeed(seed []byte) (*hdkeychain.ExtendedKey, error) { 3869 return votingXprivFromSeed(seed, w.ChainParams()) 3870 } 3871 3872 // votingXprivFromSeed derives a voting xpriv from a byte seed. The key is at 3873 // the same path as the zeroth slip0044 account key for seed. 3874 func votingXprivFromSeed(seed []byte, params *chaincfg.Params) (*hdkeychain.ExtendedKey, error) { 3875 const op errors.Op = "wallet.VotingXprivFromSeed" 3876 3877 seedSize := len(seed) 3878 if seedSize < hdkeychain.MinSeedBytes || seedSize > hdkeychain.MaxSeedBytes { 3879 return nil, errors.E(op, errors.Invalid, errors.New("invalid seed length")) 3880 } 3881 3882 // Generate the BIP0044 HD key structure to ensure the provided seed 3883 // can generate the required structure with no issues. 3884 coinTypeLegacyKeyPriv, coinTypeSLIP0044KeyPriv, acctKeyLegacyPriv, acctKeySLIP0044Priv, err := udb.HDKeysFromSeed(seed, params) 3885 if err != nil { 3886 return nil, err 3887 } 3888 coinTypeLegacyKeyPriv.Zero() 3889 coinTypeSLIP0044KeyPriv.Zero() 3890 acctKeyLegacyPriv.Zero() 3891 3892 return acctKeySLIP0044Priv, nil 3893 } 3894 3895 // ImportVotingAccount imports a voting account to the wallet. A password and 3896 // unique name must be supplied. The xpriv must be for the current running 3897 // network. 3898 func (w *Wallet) ImportVotingAccount(ctx context.Context, xpriv *hdkeychain.ExtendedKey, 3899 passphrase []byte, name string) (uint32, error) { 3900 const op errors.Op = "wallet.ImportVotingAccount" 3901 var accountN uint32 3902 err := walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 3903 var err error 3904 accountN, err = w.manager.ImportVotingAccount(dbtx, xpriv, passphrase, name) 3905 if err != nil { 3906 return err 3907 } 3908 return err 3909 }) 3910 if err != nil { 3911 return 0, errors.E(op, err) 3912 } 3913 3914 xpub := xpriv.Neuter() 3915 3916 extKey, intKey, err := deriveBranches(xpub) 3917 if err != nil { 3918 return 0, errors.E(op, err) 3919 } 3920 3921 // Internal addresses are used in signing messages and are not expected 3922 // to be found used on chain. 3923 if n, err := w.NetworkBackend(); err == nil { 3924 extAddrs, err := deriveChildAddresses(extKey, 0, w.gapLimit, w.chainParams) 3925 if err != nil { 3926 return 0, errors.E(op, err) 3927 } 3928 err = n.LoadTxFilter(ctx, false, extAddrs, nil) 3929 if err != nil { 3930 return 0, errors.E(op, err) 3931 } 3932 } 3933 3934 defer w.addressBuffersMu.Unlock() 3935 w.addressBuffersMu.Lock() 3936 albExternal := addressBuffer{ 3937 branchXpub: extKey, 3938 lastUsed: ^uint32(0), 3939 cursor: 0, 3940 lastWatched: w.gapLimit - 1, 3941 } 3942 albInternal := albExternal 3943 albInternal.branchXpub = intKey 3944 w.addressBuffers[accountN] = &bip0044AccountData{ 3945 xpub: xpub, 3946 albExternal: albExternal, 3947 albInternal: albInternal, 3948 } 3949 3950 return accountN, nil 3951 } 3952 3953 func (w *Wallet) ImportXpubAccount(ctx context.Context, name string, xpub *hdkeychain.ExtendedKey) error { 3954 const op errors.Op = "wallet.ImportXpubAccount" 3955 if xpub.IsPrivate() { 3956 return errors.E(op, "extended key must be an xpub") 3957 } 3958 3959 extKey, intKey, err := deriveBranches(xpub) 3960 if err != nil { 3961 return errors.E(op, err) 3962 } 3963 3964 if n, err := w.NetworkBackend(); err == nil { 3965 extAddrs, err := deriveChildAddresses(extKey, 0, w.gapLimit, w.chainParams) 3966 if err != nil { 3967 return errors.E(op, err) 3968 } 3969 intAddrs, err := deriveChildAddresses(intKey, 0, w.gapLimit, w.chainParams) 3970 if err != nil { 3971 return errors.E(op, err) 3972 } 3973 watch := append(extAddrs, intAddrs...) 3974 err = n.LoadTxFilter(ctx, false, watch, nil) 3975 if err != nil { 3976 return errors.E(op, err) 3977 } 3978 } 3979 3980 var account uint32 3981 err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 3982 ns := dbtx.ReadWriteBucket(waddrmgrNamespaceKey) 3983 err := w.manager.ImportXpubAccount(ns, name, xpub) 3984 if err != nil { 3985 return err 3986 } 3987 account, err = w.manager.LookupAccount(ns, name) 3988 return err 3989 }) 3990 if err != nil { 3991 return errors.E(op, err) 3992 } 3993 3994 defer w.addressBuffersMu.Unlock() 3995 w.addressBuffersMu.Lock() 3996 albExternal := addressBuffer{ 3997 branchXpub: extKey, 3998 lastUsed: ^uint32(0), 3999 cursor: 0, 4000 lastWatched: w.gapLimit - 1, 4001 } 4002 albInternal := albExternal 4003 albInternal.branchXpub = intKey 4004 w.addressBuffers[account] = &bip0044AccountData{ 4005 xpub: xpub, 4006 albExternal: albExternal, 4007 albInternal: albInternal, 4008 } 4009 4010 return nil 4011 } 4012 4013 // StakeInfoData carries counts of ticket states and other various stake data. 4014 type StakeInfoData struct { 4015 BlockHeight int64 4016 TotalSubsidy dcrutil.Amount 4017 Sdiff dcrutil.Amount 4018 4019 OwnMempoolTix uint32 4020 Unspent uint32 4021 Voted uint32 4022 Revoked uint32 4023 UnspentExpired uint32 4024 4025 PoolSize uint32 4026 AllMempoolTix uint32 4027 Immature uint32 4028 Live uint32 4029 Missed uint32 4030 Expired uint32 4031 } 4032 4033 func isVote(tx *wire.MsgTx) bool { 4034 return stake.IsSSGen(tx) 4035 } 4036 4037 func isRevocation(tx *wire.MsgTx) bool { 4038 return stake.IsSSRtx(tx) 4039 } 4040 4041 func isTreasurySpend(tx *wire.MsgTx) bool { 4042 return stake.IsTSpend(tx) 4043 } 4044 4045 // hasVotingAuthority returns whether the 0th output of a ticket purchase can be 4046 // spent by a vote or revocation created by this wallet. 4047 func (w *Wallet) hasVotingAuthority(addrmgrNs walletdb.ReadBucket, ticketPurchase *wire.MsgTx) ( 4048 mine, havePrivKey bool, err error) { 4049 out := ticketPurchase.TxOut[0] 4050 _, addrs := stdscript.ExtractAddrs(out.Version, out.PkScript, w.chainParams) 4051 for _, a := range addrs { 4052 var hash160 *[20]byte 4053 switch a := a.(type) { 4054 case stdaddr.Hash160er: 4055 hash160 = a.Hash160() 4056 default: 4057 continue 4058 } 4059 if w.manager.ExistsHash160(addrmgrNs, hash160[:]) { 4060 haveKey, err := w.manager.HavePrivateKey(addrmgrNs, a) 4061 return true, haveKey, err 4062 } 4063 } 4064 return false, false, nil 4065 } 4066 4067 // StakeInfo collects and returns staking statistics for this wallet. 4068 func (w *Wallet) StakeInfo(ctx context.Context) (*StakeInfoData, error) { 4069 const op errors.Op = "wallet.StakeInfo" 4070 4071 var res StakeInfoData 4072 4073 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 4074 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) 4075 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 4076 tipHash, tipHeight := w.txStore.MainChainTip(dbtx) 4077 res.BlockHeight = int64(tipHeight) 4078 if deployments.DCP0001.Active(tipHeight, w.chainParams.Net) { 4079 tipHeader, err := w.txStore.GetBlockHeader(dbtx, &tipHash) 4080 if err != nil { 4081 return err 4082 } 4083 sdiff, err := w.nextRequiredDCP0001PoSDifficulty(dbtx, tipHeader, nil) 4084 if err != nil { 4085 return err 4086 } 4087 res.Sdiff = sdiff 4088 } 4089 it := w.txStore.IterateTickets(dbtx) 4090 defer it.Close() 4091 for it.Next() { 4092 // Skip tickets which are not owned by this wallet. 4093 owned, _, err := w.hasVotingAuthority(addrmgrNs, &it.MsgTx) 4094 if err != nil { 4095 return err 4096 } 4097 if !owned { 4098 continue 4099 } 4100 4101 // Check for tickets in mempool 4102 if it.Block.Height == -1 { 4103 res.OwnMempoolTix++ 4104 continue 4105 } 4106 4107 // Check for immature tickets 4108 if !ticketMatured(w.chainParams, it.Block.Height, tipHeight) { 4109 res.Immature++ 4110 continue 4111 } 4112 4113 // If the ticket was spent, look up the spending tx and determine if 4114 // it is a vote or revocation. If it is a vote, add the earned 4115 // subsidy. 4116 if it.SpenderHash != (chainhash.Hash{}) { 4117 spender, err := w.txStore.Tx(txmgrNs, &it.SpenderHash) 4118 if err != nil { 4119 return err 4120 } 4121 switch { 4122 case isVote(spender): 4123 res.Voted++ 4124 4125 // Add the subsidy. 4126 // 4127 // This is not the actual subsidy that was earned by this 4128 // wallet, but rather the stakebase sum. If a user uses a 4129 // stakepool for voting, this value will include the total 4130 // subsidy earned by both the user and the pool together. 4131 // Similarly, for stakepool wallets, this includes the 4132 // customer's subsidy rather than being just the subsidy 4133 // earned by fees. 4134 res.TotalSubsidy += dcrutil.Amount(spender.TxIn[0].ValueIn) 4135 4136 case isRevocation(spender): 4137 res.Revoked++ 4138 4139 default: 4140 return errors.E(errors.IO, errors.Errorf("ticket spender %v is neither vote nor revocation", &it.SpenderHash)) 4141 } 4142 continue 4143 } 4144 4145 // Ticket is matured but unspent. Possible states are that the 4146 // ticket is live, expired, or missed. 4147 res.Unspent++ 4148 if ticketExpired(w.chainParams, it.Block.Height, tipHeight) { 4149 res.UnspentExpired++ 4150 } 4151 } 4152 return it.Err() 4153 }) 4154 if err != nil { 4155 return nil, errors.E(op, err) 4156 } 4157 return &res, nil 4158 } 4159 4160 // StakeInfoPrecise collects and returns staking statistics for this wallet. It 4161 // uses RPC to query further information than StakeInfo. 4162 func (w *Wallet) StakeInfoPrecise(ctx context.Context, rpcCaller Caller) (*StakeInfoData, error) { 4163 const op errors.Op = "wallet.StakeInfoPrecise" 4164 4165 res := &StakeInfoData{} 4166 rpc := dcrd.New(rpcCaller) 4167 var g errgroup.Group 4168 g.Go(func() error { 4169 unminedTicketCount, err := rpc.MempoolCount(ctx, "tickets") 4170 if err != nil { 4171 return err 4172 } 4173 res.AllMempoolTix = uint32(unminedTicketCount) 4174 return nil 4175 }) 4176 4177 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 4178 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) 4179 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 4180 tipHash, tipHeight := w.txStore.MainChainTip(dbtx) 4181 res.BlockHeight = int64(tipHeight) 4182 if deployments.DCP0001.Active(tipHeight, w.chainParams.Net) { 4183 tipHeader, err := w.txStore.GetBlockHeader(dbtx, &tipHash) 4184 if err != nil { 4185 return err 4186 } 4187 sdiff, err := w.nextRequiredDCP0001PoSDifficulty(dbtx, tipHeader, nil) 4188 if err != nil { 4189 return err 4190 } 4191 res.Sdiff = sdiff 4192 } 4193 it := w.txStore.IterateTickets(dbtx) 4194 defer it.Close() 4195 for it.Next() { 4196 // Skip tickets which are not owned by this wallet. 4197 owned, _, err := w.hasVotingAuthority(addrmgrNs, &it.MsgTx) 4198 if err != nil { 4199 return err 4200 } 4201 if !owned { 4202 continue 4203 } 4204 4205 // Check for tickets in mempool 4206 if it.Block.Height == -1 { 4207 res.OwnMempoolTix++ 4208 continue 4209 } 4210 4211 // Check for immature tickets 4212 if !ticketMatured(w.chainParams, it.Block.Height, tipHeight) { 4213 res.Immature++ 4214 continue 4215 } 4216 4217 // If the ticket was spent, look up the spending tx and determine if 4218 // it is a vote or revocation. If it is a vote, add the earned 4219 // subsidy. 4220 if it.SpenderHash != (chainhash.Hash{}) { 4221 spender, err := w.txStore.TxDetails(txmgrNs, &it.SpenderHash) 4222 if err != nil { 4223 return err 4224 } 4225 spenderTx := &spender.MsgTx 4226 switch { 4227 case isVote(spenderTx): 4228 res.Voted++ 4229 4230 // Add the subsidy. 4231 // 4232 // This is not the actual subsidy that was earned by this 4233 // wallet, but rather the stakebase sum. If a user uses a 4234 // stakepool for voting, this value will include the total 4235 // subsidy earned by both the user and the pool together. 4236 // Similarly, for stakepool wallets, this includes the 4237 // customer's subsidy rather than being just the subsidy 4238 // earned by fees. 4239 res.TotalSubsidy += dcrutil.Amount(spenderTx.TxIn[0].ValueIn) 4240 4241 case isRevocation(spenderTx): 4242 res.Revoked++ 4243 // Revoked tickets must be either expired or missed. 4244 // Assume expired unless the revocation occurs before 4245 // the expiry time. This assumption may not be accurate 4246 // for tickets that were missed prior to the activation 4247 // of DCP0009. 4248 params := w.chainParams 4249 expired := spender.Block.Height-it.Block.Height >= 4250 int32(params.TicketExpiryBlocks())+ 4251 int32(params.TicketMaturity) 4252 if expired { 4253 res.Expired++ 4254 } else { 4255 res.Missed++ 4256 } 4257 4258 default: 4259 return errors.E(errors.IO, errors.Errorf("ticket spender %v is neither vote nor revocation", &it.SpenderHash)) 4260 } 4261 continue 4262 } 4263 4264 // Ticket matured, unspent, and therefore live. 4265 res.Unspent++ 4266 res.Live++ 4267 } 4268 if err := it.Err(); err != nil { 4269 return err 4270 } 4271 4272 // Include an estimate of the live ticket pool size. The correct 4273 // poolsize would be the pool size to be mined into the next block, 4274 // which takes into account maturing stake tickets, voters, and expiring 4275 // tickets. There currently isn't a way to get this from the consensus 4276 // RPC server, so just use the current block pool size as a "good 4277 // enough" estimate for now. 4278 serHeader, err := w.txStore.GetSerializedBlockHeader(txmgrNs, &tipHash) 4279 if err != nil { 4280 return err 4281 } 4282 var tipHeader wire.BlockHeader 4283 err = tipHeader.Deserialize(bytes.NewReader(serHeader)) 4284 if err != nil { 4285 return err 4286 } 4287 res.PoolSize = tipHeader.PoolSize 4288 4289 return nil 4290 }) 4291 if err != nil { 4292 return nil, errors.E(op, err) 4293 } 4294 4295 // Wait for MempoolCount call from beginning of function to complete. 4296 err = g.Wait() 4297 if err != nil { 4298 return nil, errors.E(op, err) 4299 } 4300 4301 return res, nil 4302 } 4303 4304 // LockedOutpoint returns whether an outpoint has been marked as locked and 4305 // should not be used as an input for created transactions. 4306 func (w *Wallet) LockedOutpoint(txHash *chainhash.Hash, index uint32) bool { 4307 op := outpoint{*txHash, index} 4308 w.lockedOutpointMu.Lock() 4309 _, locked := w.lockedOutpoints[op] 4310 w.lockedOutpointMu.Unlock() 4311 return locked 4312 } 4313 4314 // LockOutpoint marks an outpoint as locked, that is, it should not be used as 4315 // an input for newly created transactions. 4316 func (w *Wallet) LockOutpoint(txHash *chainhash.Hash, index uint32) { 4317 op := outpoint{*txHash, index} 4318 w.lockedOutpointMu.Lock() 4319 w.lockedOutpoints[op] = struct{}{} 4320 w.lockedOutpointMu.Unlock() 4321 } 4322 4323 // UnlockOutpoint marks an outpoint as unlocked, that is, it may be used as an 4324 // input for newly created transactions. 4325 func (w *Wallet) UnlockOutpoint(txHash *chainhash.Hash, index uint32) { 4326 op := outpoint{*txHash, index} 4327 w.lockedOutpointMu.Lock() 4328 delete(w.lockedOutpoints, op) 4329 w.lockedOutpointMu.Unlock() 4330 } 4331 4332 // ResetLockedOutpoints resets the set of locked outpoints so all may be used 4333 // as inputs for new transactions. 4334 func (w *Wallet) ResetLockedOutpoints() { 4335 w.lockedOutpointMu.Lock() 4336 w.lockedOutpoints = make(map[outpoint]struct{}) 4337 w.lockedOutpointMu.Unlock() 4338 } 4339 4340 // LockedOutpoints returns a slice of currently locked outpoints. This is 4341 // intended to be used by marshaling the result as a JSON array for 4342 // listlockunspent RPC results. 4343 func (w *Wallet) LockedOutpoints(ctx context.Context, accountName string) ([]dcrdtypes.TransactionInput, error) { 4344 w.lockedOutpointMu.Lock() 4345 allLocked := make([]outpoint, len(w.lockedOutpoints)) 4346 i := 0 4347 for op := range w.lockedOutpoints { 4348 allLocked[i] = op 4349 i++ 4350 } 4351 w.lockedOutpointMu.Unlock() 4352 4353 allAccts := accountName == "" || accountName == "*" 4354 acctLocked := make([]dcrdtypes.TransactionInput, 0, len(allLocked)) 4355 err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 4356 addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) 4357 txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) 4358 4359 // Outpoints are not validated before they're locked, so 4360 // invalid outpoints may be locked. Simply ignore. 4361 for i := range allLocked { 4362 op := &allLocked[i] 4363 details, err := w.txStore.TxDetails(txmgrNs, &op.hash) 4364 if err != nil { 4365 if errors.Is(err, errors.NotExist) { 4366 // invalid txid 4367 continue 4368 } 4369 return err 4370 } 4371 if int(op.index) >= len(details.MsgTx.TxOut) { 4372 // valid txid, invalid vout 4373 continue 4374 } 4375 output := details.MsgTx.TxOut[op.index] 4376 4377 if !allAccts { 4378 // Lookup the associated account for the output. 4379 _, addrs := stdscript.ExtractAddrs(output.Version, output.PkScript, w.chainParams) 4380 if len(addrs) == 0 { 4381 continue 4382 } 4383 var opAcct string 4384 acct, err := w.manager.AddrAccount(addrmgrNs, addrs[0]) 4385 if err == nil { 4386 s, err := w.manager.AccountName(addrmgrNs, acct) 4387 if err == nil { 4388 opAcct = s 4389 } 4390 } 4391 if opAcct != accountName { 4392 continue 4393 } 4394 } 4395 4396 var tree int8 4397 if details.TxType != stake.TxTypeRegular { 4398 tree = 1 4399 } 4400 acctLocked = append(acctLocked, dcrdtypes.TransactionInput{ 4401 Amount: dcrutil.Amount(output.Value).ToCoin(), 4402 Txid: op.hash.String(), 4403 Vout: op.index, 4404 Tree: tree, 4405 }) 4406 } 4407 return nil 4408 }) 4409 4410 return acctLocked, err 4411 } 4412 4413 // UnminedTransactions returns all unmined transactions from the wallet. 4414 // Transactions are sorted in dependency order making it suitable to range them 4415 // in order to broadcast at wallet startup. This method skips over any 4416 // transactions that are recorded as unpublished. 4417 func (w *Wallet) UnminedTransactions(ctx context.Context) ([]*wire.MsgTx, error) { 4418 const op errors.Op = "wallet.UnminedTransactions" 4419 var recs []*udb.TxRecord 4420 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 4421 var err error 4422 recs, err = w.txStore.UnminedTxs(dbtx) 4423 return err 4424 }) 4425 if err != nil { 4426 return nil, errors.E(op, err) 4427 } 4428 txs := make([]*wire.MsgTx, 0, len(recs)) 4429 for i := range recs { 4430 if recs[i].Unpublished { 4431 continue 4432 } 4433 txs = append(txs, &recs[i].MsgTx) 4434 } 4435 return txs, nil 4436 } 4437 4438 // SortedActivePaymentAddresses returns a slice of all active payment 4439 // addresses in a wallet. 4440 func (w *Wallet) SortedActivePaymentAddresses(ctx context.Context) ([]string, error) { 4441 const op errors.Op = "wallet.SortedActivePaymentAddresses" 4442 var addrStrs []string 4443 err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 4444 addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) 4445 return w.manager.ForEachActiveAddress(addrmgrNs, func(addr stdaddr.Address) error { 4446 addrStrs = append(addrStrs, addr.String()) 4447 return nil 4448 }) 4449 }) 4450 if err != nil { 4451 return nil, errors.E(op, err) 4452 } 4453 4454 sort.Strings(addrStrs) 4455 return addrStrs, nil 4456 } 4457 4458 // confirmed checks whether a transaction at height txHeight has met minconf 4459 // confirmations for a blockchain at height curHeight. 4460 func confirmed(minconf, txHeight, curHeight int32) bool { 4461 return confirms(txHeight, curHeight) >= minconf 4462 } 4463 4464 // confirms returns the number of confirmations for a transaction in a block at 4465 // height txHeight (or -1 for an unconfirmed tx) given the chain height 4466 // curHeight. 4467 func confirms(txHeight, curHeight int32) int32 { 4468 switch { 4469 case txHeight == -1, txHeight > curHeight: 4470 return 0 4471 default: 4472 return curHeight - txHeight + 1 4473 } 4474 } 4475 4476 // coinbaseMatured returns whether a transaction mined at txHeight has 4477 // reached coinbase maturity in a chain with tip height curHeight. 4478 func coinbaseMatured(params *chaincfg.Params, txHeight, curHeight int32) bool { 4479 return txHeight >= 0 && curHeight-txHeight+1 > int32(params.CoinbaseMaturity) 4480 } 4481 4482 // ticketChangeMatured returns whether a ticket change mined at 4483 // txHeight has reached ticket maturity in a chain with a tip height 4484 // curHeight. 4485 func ticketChangeMatured(params *chaincfg.Params, txHeight, curHeight int32) bool { 4486 return txHeight >= 0 && curHeight-txHeight+1 > int32(params.SStxChangeMaturity) 4487 } 4488 4489 // ticketMatured returns whether a ticket mined at txHeight has 4490 // reached ticket maturity in a chain with a tip height curHeight. 4491 func ticketMatured(params *chaincfg.Params, txHeight, curHeight int32) bool { 4492 // dcrd has an off-by-one in the calculation of the ticket 4493 // maturity, which results in maturity being one block higher 4494 // than the params would indicate. 4495 return txHeight >= 0 && curHeight-txHeight > int32(params.TicketMaturity) 4496 } 4497 4498 // ticketExpired returns whether a ticket mined at txHeight has 4499 // reached ticket expiry in a chain with a tip height curHeight. 4500 func ticketExpired(params *chaincfg.Params, txHeight, curHeight int32) bool { 4501 // Ticket maturity off-by-one extends to the expiry depth as well. 4502 return txHeight >= 0 && curHeight-txHeight > int32(params.TicketMaturity)+int32(params.TicketExpiry) 4503 } 4504 4505 // AccountTotalReceivedResult is a single result for the 4506 // Wallet.TotalReceivedForAccounts method. 4507 type AccountTotalReceivedResult struct { 4508 AccountNumber uint32 4509 AccountName string 4510 TotalReceived dcrutil.Amount 4511 LastConfirmation int32 4512 } 4513 4514 // TotalReceivedForAccounts iterates through a wallet's transaction history, 4515 // returning the total amount of decred received for all accounts. 4516 func (w *Wallet) TotalReceivedForAccounts(ctx context.Context, minConf int32) ([]AccountTotalReceivedResult, error) { 4517 const op errors.Op = "wallet.TotalReceivedForAccounts" 4518 var results []AccountTotalReceivedResult 4519 resultIdxs := make(map[uint32]int) 4520 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 4521 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) 4522 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 4523 4524 _, tipHeight := w.txStore.MainChainTip(dbtx) 4525 4526 err := w.manager.ForEachAccount(addrmgrNs, func(account uint32) error { 4527 accountName, err := w.manager.AccountName(addrmgrNs, account) 4528 if err != nil { 4529 return err 4530 } 4531 resultIdxs[account] = len(resultIdxs) 4532 results = append(results, AccountTotalReceivedResult{ 4533 AccountNumber: account, 4534 AccountName: accountName, 4535 }) 4536 return nil 4537 }) 4538 if err != nil { 4539 return err 4540 } 4541 4542 var stopHeight int32 4543 4544 if minConf > 0 { 4545 stopHeight = tipHeight - minConf + 1 4546 } else { 4547 stopHeight = -1 4548 } 4549 4550 rangeFn := func(details []udb.TxDetails) (bool, error) { 4551 for i := range details { 4552 detail := &details[i] 4553 for _, cred := range detail.Credits { 4554 pkVersion := detail.MsgTx.TxOut[cred.Index].Version 4555 pkScript := detail.MsgTx.TxOut[cred.Index].PkScript 4556 _, addrs := stdscript.ExtractAddrs(pkVersion, pkScript, w.chainParams) 4557 if len(addrs) == 0 { 4558 continue 4559 } 4560 outputAcct, err := w.manager.AddrAccount(addrmgrNs, addrs[0]) 4561 if err == nil { 4562 acctIndex := resultIdxs[outputAcct] 4563 res := &results[acctIndex] 4564 res.TotalReceived += cred.Amount 4565 res.LastConfirmation = confirms( 4566 detail.Block.Height, tipHeight) 4567 } 4568 } 4569 } 4570 return false, nil 4571 } 4572 return w.txStore.RangeTransactions(ctx, txmgrNs, 0, stopHeight, rangeFn) 4573 }) 4574 if err != nil { 4575 return nil, errors.E(op, err) 4576 } 4577 return results, nil 4578 } 4579 4580 // TotalReceivedForAddr iterates through a wallet's transaction history, 4581 // returning the total amount of decred received for a single wallet 4582 // address. 4583 func (w *Wallet) TotalReceivedForAddr(ctx context.Context, addr stdaddr.Address, minConf int32) (dcrutil.Amount, error) { 4584 const op errors.Op = "wallet.TotalReceivedForAddr" 4585 var amount dcrutil.Amount 4586 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 4587 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 4588 4589 _, tipHeight := w.txStore.MainChainTip(dbtx) 4590 4591 var ( 4592 addrStr = addr.String() 4593 stopHeight int32 4594 ) 4595 4596 if minConf > 0 { 4597 stopHeight = tipHeight - minConf + 1 4598 } else { 4599 stopHeight = -1 4600 } 4601 rangeFn := func(details []udb.TxDetails) (bool, error) { 4602 for i := range details { 4603 detail := &details[i] 4604 for _, cred := range detail.Credits { 4605 pkVersion := detail.MsgTx.TxOut[cred.Index].Version 4606 pkScript := detail.MsgTx.TxOut[cred.Index].PkScript 4607 _, addrs := stdscript.ExtractAddrs(pkVersion, pkScript, w.chainParams) 4608 for _, a := range addrs { // no addresses means non-standard credit, ignored 4609 if addrStr == a.String() { 4610 amount += cred.Amount 4611 break 4612 } 4613 } 4614 } 4615 } 4616 return false, nil 4617 } 4618 return w.txStore.RangeTransactions(ctx, txmgrNs, 0, stopHeight, rangeFn) 4619 }) 4620 if err != nil { 4621 return 0, errors.E(op, err) 4622 } 4623 return amount, nil 4624 } 4625 4626 // SendOutputs creates and sends payment transactions. It returns the 4627 // transaction hash upon success 4628 func (w *Wallet) SendOutputs(ctx context.Context, outputs []*wire.TxOut, account, changeAccount uint32, minconf int32) (*chainhash.Hash, error) { 4629 const op errors.Op = "wallet.SendOutputs" 4630 relayFee := w.RelayFee() 4631 for _, output := range outputs { 4632 err := txrules.CheckOutput(output, relayFee) 4633 if err != nil { 4634 return nil, errors.E(op, err) 4635 } 4636 } 4637 4638 a := &authorTx{ 4639 outputs: outputs, 4640 account: account, 4641 changeAccount: changeAccount, 4642 minconf: minconf, 4643 randomizeChangeIdx: true, 4644 txFee: relayFee, 4645 dontSignTx: false, 4646 isTreasury: false, 4647 } 4648 err := w.authorTx(ctx, op, a) 4649 if err != nil { 4650 return nil, err 4651 } 4652 err = w.recordAuthoredTx(ctx, op, a) 4653 if err != nil { 4654 return nil, err 4655 } 4656 err = w.publishAndWatch(ctx, op, nil, a.atx.Tx, a.watch) 4657 if err != nil { 4658 return nil, err 4659 } 4660 hash := a.atx.Tx.TxHash() 4661 return &hash, nil 4662 } 4663 4664 // transaction hash upon success 4665 func (w *Wallet) SendOutputsToTreasury(ctx context.Context, outputs []*wire.TxOut, account, changeAccount uint32, minconf int32) (*chainhash.Hash, error) { 4666 const op errors.Op = "wallet.SendOutputsToTreasury" 4667 relayFee := w.RelayFee() 4668 for _, output := range outputs { 4669 err := txrules.CheckOutput(output, relayFee) 4670 if err != nil { 4671 return nil, errors.E(op, err) 4672 } 4673 } 4674 4675 a := &authorTx{ 4676 outputs: outputs, 4677 account: account, 4678 changeAccount: changeAccount, 4679 minconf: minconf, 4680 randomizeChangeIdx: false, 4681 txFee: relayFee, 4682 dontSignTx: false, 4683 isTreasury: true, 4684 } 4685 err := w.authorTx(ctx, op, a) 4686 if err != nil { 4687 return nil, err 4688 } 4689 err = w.recordAuthoredTx(ctx, op, a) 4690 if err != nil { 4691 return nil, err 4692 } 4693 err = w.publishAndWatch(ctx, op, nil, a.atx.Tx, a.watch) 4694 if err != nil { 4695 return nil, err 4696 } 4697 hash := a.atx.Tx.TxHash() 4698 return &hash, nil 4699 } 4700 4701 // SignatureError records the underlying error when validating a transaction 4702 // input signature. 4703 type SignatureError struct { 4704 InputIndex uint32 4705 Error error 4706 } 4707 4708 type sigDataSource struct { 4709 key func(stdaddr.Address) ([]byte, dcrec.SignatureType, bool, error) 4710 script func(stdaddr.Address) ([]byte, error) 4711 } 4712 4713 func (s sigDataSource) GetKey(a stdaddr.Address) ([]byte, dcrec.SignatureType, bool, error) { 4714 return s.key(a) 4715 } 4716 func (s sigDataSource) GetScript(a stdaddr.Address) ([]byte, error) { return s.script(a) } 4717 4718 // SignTransaction uses secrets of the wallet, as well as additional secrets 4719 // passed in by the caller, to create and add input signatures to a transaction. 4720 // 4721 // Transaction input script validation is used to confirm that all signatures 4722 // are valid. For any invalid input, a SignatureError is added to the returns. 4723 // The final error return is reserved for unexpected or fatal errors, such as 4724 // being unable to determine a previous output script to redeem. 4725 // 4726 // The transaction pointed to by tx is modified by this function. 4727 func (w *Wallet) SignTransaction(ctx context.Context, tx *wire.MsgTx, hashType txscript.SigHashType, additionalPrevScripts map[wire.OutPoint][]byte, 4728 additionalKeysByAddress map[string]*dcrutil.WIF, p2shRedeemScriptsByAddress map[string][]byte) ([]SignatureError, error) { 4729 4730 const op errors.Op = "wallet.SignTransaction" 4731 4732 var doneFuncs []func() 4733 defer func() { 4734 for _, f := range doneFuncs { 4735 f() 4736 } 4737 }() 4738 4739 var signErrors []SignatureError 4740 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 4741 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) 4742 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 4743 4744 for i, txIn := range tx.TxIn { 4745 // For an SSGen tx, skip the first input as it is a stake base 4746 // and doesn't need to be signed. The transaction is expected 4747 // to already contain the consensus-validated stakebase script. 4748 if i == 0 && stake.IsSSGen(tx) { 4749 continue 4750 } 4751 4752 prevOutScript, ok := additionalPrevScripts[txIn.PreviousOutPoint] 4753 if !ok { 4754 prevHash := &txIn.PreviousOutPoint.Hash 4755 prevIndex := txIn.PreviousOutPoint.Index 4756 txDetails, err := w.txStore.TxDetails(txmgrNs, prevHash) 4757 if errors.Is(err, errors.NotExist) { 4758 return errors.Errorf("%v not found", &txIn.PreviousOutPoint) 4759 } else if err != nil { 4760 return err 4761 } 4762 prevOutScript = txDetails.MsgTx.TxOut[prevIndex].PkScript 4763 } 4764 4765 // Set up our callbacks that we pass to txscript so it can 4766 // look up the appropriate keys and scripts by address. 4767 var source sigDataSource 4768 source.key = func(addr stdaddr.Address) ([]byte, dcrec.SignatureType, bool, error) { 4769 if len(additionalKeysByAddress) != 0 { 4770 addrStr := addr.String() 4771 wif, ok := additionalKeysByAddress[addrStr] 4772 if !ok { 4773 return nil, 0, false, 4774 errors.Errorf("no key for address (needed: %v, have %v)", 4775 addr, additionalKeysByAddress) 4776 } 4777 return wif.PrivKey(), dcrec.STEcdsaSecp256k1, true, nil 4778 } 4779 4780 key, done, err := w.manager.PrivateKey(addrmgrNs, addr) 4781 if err != nil { 4782 return nil, 0, false, err 4783 } 4784 doneFuncs = append(doneFuncs, done) 4785 return key.Serialize(), dcrec.STEcdsaSecp256k1, true, nil 4786 } 4787 source.script = func(addr stdaddr.Address) ([]byte, error) { 4788 // If keys were provided then we can only use the 4789 // redeem scripts provided with our inputs, too. 4790 if len(additionalKeysByAddress) != 0 { 4791 addrStr := addr.String() 4792 script, ok := p2shRedeemScriptsByAddress[addrStr] 4793 if !ok { 4794 return nil, errors.New("no script for " + 4795 "address") 4796 } 4797 return script, nil 4798 } 4799 4800 return w.manager.RedeemScript(addrmgrNs, addr) 4801 } 4802 4803 // SigHashSingle inputs can only be signed if there's a 4804 // corresponding output. However this could be already signed, 4805 // so we always verify the output. 4806 if (hashType&txscript.SigHashSingle) != 4807 txscript.SigHashSingle || i < len(tx.TxOut) { 4808 4809 script, err := sign.SignTxOutput(w.ChainParams(), 4810 tx, i, prevOutScript, hashType, source, source, txIn.SignatureScript, true) // Yes treasury 4811 // Failure to sign isn't an error, it just means that 4812 // the tx isn't complete. 4813 if err != nil { 4814 signErrors = append(signErrors, SignatureError{ 4815 InputIndex: uint32(i), 4816 Error: errors.E(op, err), 4817 }) 4818 continue 4819 } 4820 txIn.SignatureScript = script 4821 } 4822 4823 // Either it was already signed or we just signed it. 4824 // Find out if it is completely satisfied or still needs more. 4825 vm, err := txscript.NewEngine(prevOutScript, tx, i, 4826 sanityVerifyFlags, scriptVersionAssumed, nil) 4827 if err == nil { 4828 err = vm.Execute() 4829 } 4830 if err != nil { 4831 var multisigNotEnoughSigs bool 4832 if errors.Is(err, txscript.ErrInvalidStackOperation) { 4833 pkScript := additionalPrevScripts[txIn.PreviousOutPoint] 4834 class, addr := stdscript.ExtractAddrs(scriptVersionAssumed, pkScript, w.ChainParams()) 4835 if class == stdscript.STScriptHash && len(addr) > 0 { 4836 redeemScript, _ := source.script(addr[0]) 4837 if stdscript.IsMultiSigScriptV0(redeemScript) { 4838 multisigNotEnoughSigs = true 4839 } 4840 } 4841 } 4842 // Only report an error for the script engine in the event 4843 // that it's not a multisignature underflow, indicating that 4844 // we didn't have enough signatures in front of the 4845 // redeemScript rather than an actual error. 4846 if !multisigNotEnoughSigs { 4847 signErrors = append(signErrors, SignatureError{ 4848 InputIndex: uint32(i), 4849 Error: errors.E(op, err), 4850 }) 4851 } 4852 } 4853 } 4854 return nil 4855 }) 4856 if err != nil { 4857 return nil, errors.E(op, err) 4858 } 4859 return signErrors, nil 4860 } 4861 4862 // CreateSignature returns the raw signature created by the private key of addr 4863 // for tx's idx'th input script and the serialized compressed pubkey for the 4864 // address. 4865 func (w *Wallet) CreateSignature(ctx context.Context, tx *wire.MsgTx, idx uint32, addr stdaddr.Address, 4866 hashType txscript.SigHashType, prevPkScript []byte) (sig, pubkey []byte, err error) { 4867 const op errors.Op = "wallet.CreateSignature" 4868 var privKey *secp256k1.PrivateKey 4869 var pubKey *secp256k1.PublicKey 4870 var done func() 4871 defer func() { 4872 if done != nil { 4873 done() 4874 } 4875 }() 4876 4877 err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 4878 ns := dbtx.ReadBucket(waddrmgrNamespaceKey) 4879 4880 var err error 4881 privKey, done, err = w.manager.PrivateKey(ns, addr) 4882 if err != nil { 4883 return err 4884 } 4885 pubKey = privKey.PubKey() 4886 return nil 4887 }) 4888 if err != nil { 4889 return nil, nil, errors.E(op, err) 4890 } 4891 4892 sig, err = sign.RawTxInSignature(tx, int(idx), prevPkScript, hashType, 4893 privKey.Serialize(), dcrec.STEcdsaSecp256k1) 4894 if err != nil { 4895 return nil, nil, errors.E(op, err) 4896 } 4897 4898 return sig, pubKey.SerializeCompressed(), nil 4899 } 4900 4901 // isRelevantTx determines whether the transaction is relevant to the wallet and 4902 // should be recorded in the database. 4903 func (w *Wallet) isRelevantTx(dbtx walletdb.ReadTx, tx *wire.MsgTx) bool { 4904 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) 4905 4906 for _, in := range tx.TxIn { 4907 // Input is relevant if it contains a saved redeem script or spends a 4908 // wallet output. 4909 rs := stdscript.MultiSigRedeemScriptFromScriptSigV0(in.SignatureScript) 4910 if rs != nil && w.manager.ExistsHash160(addrmgrNs, 4911 dcrutil.Hash160(rs)) { 4912 return true 4913 } 4914 if w.txStore.ExistsUTXO(dbtx, &in.PreviousOutPoint) { 4915 return true 4916 } 4917 } 4918 for _, out := range tx.TxOut { 4919 _, addrs := stdscript.ExtractAddrs(out.Version, out.PkScript, w.chainParams) 4920 for _, a := range addrs { 4921 var hash160 *[20]byte 4922 switch a := a.(type) { 4923 case stdaddr.Hash160er: 4924 hash160 = a.Hash160() 4925 default: 4926 continue 4927 } 4928 if w.manager.ExistsHash160(addrmgrNs, hash160[:]) { 4929 return true 4930 } 4931 } 4932 } 4933 4934 return false 4935 } 4936 4937 // DetermineRelevantTxs splits the given transactions into slices of relevant 4938 // and non-wallet-relevant transactions (respectively). 4939 func (w *Wallet) DetermineRelevantTxs(ctx context.Context, txs ...*wire.MsgTx) ([]*wire.MsgTx, []*wire.MsgTx, error) { 4940 var relevant, nonRelevant []*wire.MsgTx 4941 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 4942 for _, tx := range txs { 4943 switch w.isRelevantTx(dbtx, tx) { 4944 case true: 4945 relevant = append(relevant, tx) 4946 default: 4947 nonRelevant = append(nonRelevant, tx) 4948 } 4949 } 4950 return nil 4951 }) 4952 return relevant, nonRelevant, err 4953 } 4954 4955 func (w *Wallet) appendRelevantOutpoints(relevant []wire.OutPoint, dbtx walletdb.ReadTx, tx *wire.MsgTx) []wire.OutPoint { 4956 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) 4957 4958 if relevant == nil { 4959 relevant = make([]wire.OutPoint, 0, len(tx.TxOut)) 4960 } 4961 4962 txHash := tx.TxHash() 4963 op := wire.OutPoint{ 4964 Hash: txHash, 4965 } 4966 isTicket := stake.DetermineTxType(tx) == stake.TxTypeSStx 4967 var watchedTicketOutputZero bool 4968 for i, out := range tx.TxOut { 4969 if isTicket && i > 0 && i&1 == 1 && !watchedTicketOutputZero { 4970 addr, err := stake.AddrFromSStxPkScrCommitment(out.PkScript, w.chainParams) 4971 if err != nil { 4972 continue 4973 } 4974 var hash160 *[20]byte 4975 if addr, ok := addr.(stdaddr.Hash160er); ok { 4976 hash160 = addr.Hash160() 4977 } 4978 if hash160 != nil && w.manager.ExistsHash160(addrmgrNs, hash160[:]) { 4979 op.Index = 0 4980 op.Tree = wire.TxTreeStake 4981 relevant = append(relevant, op) 4982 watchedTicketOutputZero = true 4983 continue 4984 } 4985 } 4986 4987 class, addrs := stdscript.ExtractAddrs(out.Version, out.PkScript, w.chainParams) 4988 tree := wire.TxTreeRegular 4989 if _, isStake := txrules.StakeSubScriptType(class); isStake { 4990 tree = wire.TxTreeStake 4991 } 4992 4993 for _, a := range addrs { 4994 var hash160 *[20]byte 4995 if a, ok := a.(stdaddr.Hash160er); ok { 4996 hash160 = a.Hash160() 4997 } 4998 if hash160 != nil && w.manager.ExistsHash160(addrmgrNs, hash160[:]) { 4999 op.Index = uint32(i) 5000 op.Tree = tree 5001 relevant = append(relevant, op) 5002 if isTicket && i == 0 { 5003 watchedTicketOutputZero = true 5004 } 5005 break 5006 } 5007 } 5008 } 5009 5010 return relevant 5011 } 5012 5013 // AbandonTransaction removes a transaction, identified by its hash, from 5014 // the wallet if present. All transaction spend chains deriving from the 5015 // transaction's outputs are also removed. Does not error if the transaction 5016 // doesn't already exist unmined, but will if the transaction is marked mined in 5017 // a block on the main chain. 5018 // 5019 // Purged transactions may have already been published to the network and may 5020 // still appear in future blocks, and new transactions spending the same inputs 5021 // as purged transactions may be rejected by full nodes due to being double 5022 // spends. In turn, this can cause the purged transaction to be mined later and 5023 // replace other transactions authored by the wallet. 5024 func (w *Wallet) AbandonTransaction(ctx context.Context, hash *chainhash.Hash) error { 5025 const opf = "wallet.AbandonTransaction(%v)" 5026 err := walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 5027 ns := dbtx.ReadWriteBucket(wtxmgrNamespaceKey) 5028 details, err := w.txStore.TxDetails(ns, hash) 5029 if err != nil { 5030 return err 5031 } 5032 if details.Block.Height != -1 { 5033 return errors.E(errors.Invalid, errors.Errorf("transaction %v is mined in main chain", hash)) 5034 } 5035 return w.txStore.RemoveUnconfirmed(ns, &details.MsgTx, hash) 5036 }) 5037 if err != nil { 5038 op := errors.Opf(opf, hash) 5039 return errors.E(op, err) 5040 } 5041 w.NtfnServer.notifyRemovedTransaction(*hash) 5042 return nil 5043 } 5044 5045 // AllowsHighFees returns whether the wallet is configured to allow or prevent 5046 // the creation and publishing of transactions with very large fees. 5047 func (w *Wallet) AllowsHighFees() bool { 5048 return w.allowHighFees 5049 } 5050 5051 // PublishTransaction saves (if relevant) and sends the transaction to the 5052 // consensus RPC server so it can be propagated to other nodes and eventually 5053 // mined. If the send fails, the transaction is not added to the wallet. 5054 // 5055 // This method does not check if a transaction pays high fees or not, and it is 5056 // the caller's responsibility to check this using either the current wallet 5057 // policy or other configuration parameters. See txrules.TxPaysHighFees for a 5058 // check for insanely high transaction fees. 5059 func (w *Wallet) PublishTransaction(ctx context.Context, tx *wire.MsgTx, n NetworkBackend) (*chainhash.Hash, error) { 5060 const opf = "wallet.PublishTransaction(%v)" 5061 5062 txHash := tx.TxHash() 5063 5064 var relevant bool 5065 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 5066 relevant = w.isRelevantTx(dbtx, tx) 5067 return nil 5068 }) 5069 if err != nil { 5070 op := errors.Opf(opf, &txHash) 5071 return nil, errors.E(op, err) 5072 } 5073 5074 var watchOutPoints []wire.OutPoint 5075 if relevant { 5076 txBuf := new(bytes.Buffer) 5077 txBuf.Grow(tx.SerializeSize()) 5078 if err = tx.Serialize(txBuf); err != nil { 5079 op := errors.Opf(opf, &txHash) 5080 return nil, errors.E(op, err) 5081 } 5082 5083 w.lockedOutpointMu.Lock() 5084 err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 5085 rec, err := udb.NewTxRecord(txBuf.Bytes(), time.Now()) 5086 if err != nil { 5087 return err 5088 } 5089 watchOutPoints, err = w.processTransactionRecord(ctx, dbtx, rec, nil, nil) 5090 return err 5091 }) 5092 w.lockedOutpointMu.Unlock() 5093 if err != nil { 5094 op := errors.Opf(opf, &txHash) 5095 return nil, errors.E(op, err) 5096 } 5097 } 5098 5099 err = n.PublishTransactions(ctx, tx) 5100 if err != nil { 5101 if relevant { 5102 if err := w.AbandonTransaction(ctx, &txHash); err != nil { 5103 log.Warnf("Failed to abandon unmined transaction: %v", err) 5104 } 5105 } 5106 op := errors.Opf(opf, &txHash) 5107 return nil, errors.E(op, err) 5108 } 5109 5110 if len(watchOutPoints) > 0 { 5111 err := n.LoadTxFilter(ctx, false, nil, watchOutPoints) 5112 if err != nil { 5113 log.Errorf("Failed to watch outpoints: %v", err) 5114 } 5115 } 5116 5117 return &txHash, nil 5118 } 5119 5120 // PublishUnminedTransactions rebroadcasts all unmined transactions 5121 // to the consensus RPC server so it can be propagated to other nodes 5122 // and eventually mined. 5123 func (w *Wallet) PublishUnminedTransactions(ctx context.Context, p Peer) error { 5124 const op errors.Op = "wallet.PublishUnminedTransactions" 5125 unminedTxs, err := w.UnminedTransactions(ctx) 5126 if err != nil { 5127 return errors.E(op, err) 5128 } 5129 err = p.PublishTransactions(ctx, unminedTxs...) 5130 if err != nil { 5131 return errors.E(op, err) 5132 } 5133 return nil 5134 } 5135 5136 // ChainParams returns the network parameters for the blockchain the wallet 5137 // belongs to. 5138 func (w *Wallet) ChainParams() *chaincfg.Params { 5139 return w.chainParams 5140 } 5141 5142 // NeedsAccountsSync returns whether or not the wallet is void of any generated 5143 // keys and accounts (other than the default account), and records the genesis 5144 // block as the main chain tip. When these are both true, an accounts sync 5145 // should be performed to restore, per BIP0044, any generated accounts and 5146 // addresses from a restored seed. 5147 func (w *Wallet) NeedsAccountsSync(ctx context.Context) (bool, error) { 5148 _, tipHeight := w.MainChainTip(ctx) 5149 if tipHeight != 0 { 5150 return false, nil 5151 } 5152 5153 needsSync := true 5154 err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 5155 addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) 5156 lastAcct, err := w.manager.LastAccount(addrmgrNs) 5157 if err != nil { 5158 return err 5159 } 5160 if lastAcct != 0 { 5161 // There are more accounts than just the default account and no 5162 // accounts sync is required. 5163 needsSync = false 5164 return nil 5165 } 5166 // Begin iteration over all addresses in the first account. If any 5167 // exist, "break out" of the loop with a special error. 5168 errBreak := errors.New("break") 5169 err = w.manager.ForEachAccountAddress(addrmgrNs, 0, func(udb.ManagedAddress) error { 5170 needsSync = false 5171 return errBreak 5172 }) 5173 if errors.Is(err, errBreak) { 5174 return nil 5175 } 5176 return err 5177 }) 5178 return needsSync, err 5179 } 5180 5181 // ValidatePreDCP0005CFilters verifies that all stored cfilters prior to the 5182 // DCP0005 activation height are the expected ones. 5183 // 5184 // Verification is done by hashing all stored cfilter data and comparing the 5185 // resulting hash to a known, hardcoded hash. 5186 func (w *Wallet) ValidatePreDCP0005CFilters(ctx context.Context) error { 5187 const op errors.Op = "wallet.ValidatePreDCP0005CFilters" 5188 5189 // Hardcoded activation heights for mainnet and testnet3. Simnet 5190 // already follows DCP0005 rules. 5191 var end *BlockIdentifier 5192 switch w.chainParams.Net { 5193 case wire.MainNet: 5194 end = NewBlockIdentifierFromHeight(validate.DCP0005ActiveHeightMainNet - 1) 5195 case wire.TestNet3: 5196 end = NewBlockIdentifierFromHeight(validate.DCP0005ActiveHeightTestNet3 - 1) 5197 default: 5198 return errors.E(op, "The current network does not have pre-DCP0005 cfilters") 5199 } 5200 5201 // Sum up all the cfilter data. 5202 hasher := blake256.New() 5203 rangeFn := func(_ chainhash.Hash, _ [gcs2.KeySize]byte, filter *gcs2.FilterV2) (bool, error) { 5204 _, err := hasher.Write(filter.Bytes()) 5205 return false, err 5206 } 5207 5208 err := w.RangeCFiltersV2(ctx, nil, end, rangeFn) 5209 if err != nil { 5210 return errors.E(op, err) 5211 } 5212 5213 // Verify against the hardcoded hash. 5214 var cfsethash chainhash.Hash 5215 err = cfsethash.SetBytes(hasher.Sum(nil)) 5216 if err != nil { 5217 return errors.E(op, err) 5218 } 5219 err = validate.PreDCP0005CFilterHash(w.chainParams.Net, &cfsethash) 5220 if err != nil { 5221 return errors.E(op, err) 5222 } 5223 return nil 5224 } 5225 5226 // ImportCFiltersV2 imports the provided v2 cfilters starting at the specified 5227 // block height. Headers for all the provided filters must have already been 5228 // imported into the wallet, otherwise this method fails. Existing filters for 5229 // the respective blocks are overridden. 5230 // 5231 // Note: No validation is performed on the contents of the imported filters. 5232 // Importing filters that do not correspond to the actual contents of a block 5233 // might cause the wallet to miss relevant transactions. 5234 func (w *Wallet) ImportCFiltersV2(ctx context.Context, startBlockHeight int32, filterData [][]byte) error { 5235 const op errors.Op = "wallet.ImportCFiltersV2" 5236 err := walletdb.Update(ctx, w.db, func(tx walletdb.ReadWriteTx) error { 5237 return w.txStore.ImportCFiltersV2(tx, startBlockHeight, filterData) 5238 }) 5239 if err != nil { 5240 return errors.E(op, err) 5241 } 5242 return nil 5243 } 5244 5245 // Create creates an new wallet, writing it to an empty database. If the passed 5246 // seed is non-nil, it is used. Otherwise, a secure random seed of the 5247 // recommended length is generated. 5248 func Create(ctx context.Context, db DB, pubPass, privPass, seed []byte, params *chaincfg.Params) error { 5249 const op errors.Op = "wallet.Create" 5250 // If a seed was provided, ensure that it is of valid length. Otherwise, 5251 // we generate a random seed for the wallet with the recommended seed 5252 // length. 5253 if seed == nil { 5254 hdSeed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen) 5255 if err != nil { 5256 return errors.E(op, err) 5257 } 5258 seed = hdSeed 5259 } 5260 if len(seed) < hdkeychain.MinSeedBytes || len(seed) > hdkeychain.MaxSeedBytes { 5261 return errors.E(op, hdkeychain.ErrInvalidSeedLen) 5262 } 5263 5264 err := udb.Initialize(ctx, db.internal(), params, seed, pubPass, privPass) 5265 if err != nil { 5266 return errors.E(op, err) 5267 } 5268 return nil 5269 } 5270 5271 // CreateWatchOnly creates a watchonly wallet on the provided db. 5272 func CreateWatchOnly(ctx context.Context, db DB, extendedPubKey string, pubPass []byte, params *chaincfg.Params) error { 5273 const op errors.Op = "wallet.CreateWatchOnly" 5274 err := udb.InitializeWatchOnly(ctx, db.internal(), params, extendedPubKey, pubPass) 5275 if err != nil { 5276 return errors.E(op, err) 5277 } 5278 return nil 5279 } 5280 5281 // decodeStakePoolColdExtKey decodes the string of stake pool addresses 5282 // to search incoming tickets for. The format for the passed string is: 5283 // 5284 // "xpub...:end" 5285 // 5286 // where xpub... is the extended public key and end is the last 5287 // address index to scan to, exclusive. Effectively, it returns the derived 5288 // addresses for this public key for the address indexes [0,end). The branch 5289 // used for the derivation is always the external branch. 5290 func decodeStakePoolColdExtKey(encStr string, params *chaincfg.Params) (map[string]struct{}, error) { 5291 // Default option; stake pool is disabled. 5292 if encStr == "" { 5293 return nil, nil 5294 } 5295 5296 // Split the string. 5297 splStrs := strings.Split(encStr, ":") 5298 if len(splStrs) != 2 { 5299 return nil, errors.Errorf("failed to correctly parse passed stakepool " + 5300 "address public key and index") 5301 } 5302 5303 // Parse the extended public key and ensure it's the right network. 5304 key, err := hdkeychain.NewKeyFromString(splStrs[0], params) 5305 if err != nil { 5306 return nil, err 5307 } 5308 5309 // Parse the ending index and ensure it's valid. 5310 end, err := strconv.Atoi(splStrs[1]) 5311 if err != nil { 5312 return nil, err 5313 } 5314 if end < 0 || end > udb.MaxAddressesPerAccount { 5315 return nil, errors.Errorf("pool address index is invalid (got %v)", 5316 end) 5317 } 5318 5319 log.Infof("Please wait, deriving %v stake pool fees addresses "+ 5320 "for extended public key %s", end, splStrs[0]) 5321 5322 // Derive from external branch 5323 branchKey, err := key.Child(udb.ExternalBranch) 5324 if err != nil { 5325 return nil, err 5326 } 5327 5328 // Derive the addresses from [0, end) for this extended public key. 5329 addrs, err := deriveChildAddresses(branchKey, 0, uint32(end)+1, params) 5330 if err != nil { 5331 return nil, err 5332 } 5333 5334 addrMap := make(map[string]struct{}) 5335 for i := range addrs { 5336 addrMap[addrs[i].String()] = struct{}{} 5337 } 5338 5339 return addrMap, nil 5340 } 5341 5342 // GapLimit returns the currently used gap limit. 5343 func (w *Wallet) GapLimit() uint32 { 5344 return w.gapLimit 5345 } 5346 5347 // ManualTickets returns whether network syncers should avoid adding ticket 5348 // transactions to the wallet, instead requiring the wallet administrator to 5349 // manually record any tickets. This can be used to prevent wallets from voting 5350 // using tickets bought by others but which reuse our voting address. 5351 func (w *Wallet) ManualTickets() bool { 5352 return w.manualTickets 5353 } 5354 5355 // Open loads an already-created wallet from the passed database and namespaces 5356 // configuration options and sets it up it according to the rest of options. 5357 func Open(ctx context.Context, cfg *Config) (*Wallet, error) { 5358 const op errors.Op = "wallet.Open" 5359 // Migrate to the unified DB if necessary. 5360 db := cfg.DB.internal() 5361 needsMigration, err := udb.NeedsMigration(ctx, db) 5362 if err != nil { 5363 return nil, errors.E(op, err) 5364 } 5365 if needsMigration { 5366 err := udb.Migrate(ctx, db, cfg.Params) 5367 if err != nil { 5368 return nil, errors.E(op, err) 5369 } 5370 } 5371 5372 // Perform upgrades as necessary. 5373 err = udb.Upgrade(ctx, db, cfg.PubPassphrase, cfg.Params) 5374 if err != nil { 5375 return nil, errors.E(op, err) 5376 } 5377 5378 // Impose a maximum difficulty target on the test network to prevent runaway 5379 // difficulty on testnet by ASICs and GPUs since it's not reasonable to 5380 // require high-powered hardware to keep the test network running smoothly. 5381 var minTestNetTarget *big.Int 5382 var minTestNetDiffBits uint32 5383 params := cfg.Params 5384 if params.Net == wire.TestNet3 { 5385 // This equates to a maximum difficulty of 2^6 = 64. 5386 const maxTestDiffShift = 6 5387 minTestNetTarget = new(big.Int).Rsh(cfg.Params.PowLimit, maxTestDiffShift) 5388 minTestNetDiffBits = blockchain.BigToCompact(minTestNetTarget) 5389 } 5390 // Deployment IDs are guaranteed to be unique across all stake versions. 5391 deploymentsByID := make(map[string]*chaincfg.ConsensusDeployment) 5392 for _, deployments := range params.Deployments { 5393 for i := range deployments { 5394 deployment := &deployments[i] 5395 id := deployment.Vote.Id 5396 if _, ok := deploymentsByID[id]; ok { 5397 panic(fmt.Sprintf("reused deployment ID %q", id)) 5398 } 5399 deploymentsByID[id] = deployment 5400 } 5401 } 5402 5403 w := &Wallet{ 5404 db: db, 5405 5406 // StakeOptions 5407 votingEnabled: cfg.VotingEnabled, 5408 addressReuse: cfg.AddressReuse, 5409 ticketAddress: cfg.VotingAddress, 5410 poolAddress: cfg.PoolAddress, 5411 poolFees: cfg.PoolFees, 5412 tspends: make(map[chainhash.Hash]wire.MsgTx), 5413 tspendPolicy: make(map[chainhash.Hash]stake.TreasuryVoteT), 5414 tspendKeyPolicy: make(map[string]stake.TreasuryVoteT), 5415 vspTSpendPolicy: make(map[udb.VSPTSpend]stake.TreasuryVoteT), 5416 vspTSpendKeyPolicy: make(map[udb.VSPTreasuryKey]stake.TreasuryVoteT), 5417 5418 // LoaderOptions 5419 gapLimit: cfg.GapLimit, 5420 watchLast: cfg.WatchLast, 5421 allowHighFees: cfg.AllowHighFees, 5422 accountGapLimit: cfg.AccountGapLimit, 5423 disableCoinTypeUpgrades: cfg.DisableCoinTypeUpgrades, 5424 manualTickets: cfg.ManualTickets, 5425 5426 // Chain params 5427 subsidyCache: blockchain.NewSubsidyCache(params), 5428 chainParams: params, 5429 deploymentsByID: deploymentsByID, 5430 minTestNetTarget: minTestNetTarget, 5431 minTestNetDiffBits: minTestNetDiffBits, 5432 5433 lockedOutpoints: make(map[outpoint]struct{}), 5434 5435 recentlyPublished: make(map[chainhash.Hash]struct{}), 5436 5437 addressBuffers: make(map[uint32]*bip0044AccountData), 5438 5439 mixSems: newMixSemaphores(cfg.MixSplitLimit), 5440 } 5441 5442 // Open database managers 5443 w.manager, w.txStore, w.stakeMgr, err = udb.Open(ctx, db, params, cfg.PubPassphrase) 5444 if err != nil { 5445 return nil, errors.E(op, err) 5446 } 5447 log.Infof("Opened wallet") // TODO: log balance? last sync height? 5448 5449 err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 5450 return w.rollbackInvalidCheckpoints(dbtx) 5451 }) 5452 if err != nil { 5453 return nil, errors.E(op, err) 5454 } 5455 5456 var vb stake.VoteBits 5457 var tspendPolicy map[chainhash.Hash]stake.TreasuryVoteT 5458 var treasuryKeyPolicy map[string]stake.TreasuryVoteT 5459 var vspTSpendPolicy map[udb.VSPTSpend]stake.TreasuryVoteT 5460 var vspTreasuryKeyPolicy map[udb.VSPTreasuryKey]stake.TreasuryVoteT 5461 err = walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error { 5462 ns := tx.ReadBucket(waddrmgrNamespaceKey) 5463 lastAcct, err := w.manager.LastAccount(ns) 5464 if err != nil { 5465 return err 5466 } 5467 lastImported, err := w.manager.LastImportedAccount(tx) 5468 if err != nil { 5469 return err 5470 } 5471 addAccountBuffers := func(acct uint32) error { 5472 xpub, err := w.manager.AccountExtendedPubKey(tx, acct) 5473 if err != nil { 5474 return err 5475 } 5476 extKey, intKey, err := deriveBranches(xpub) 5477 if err != nil { 5478 return err 5479 } 5480 props, err := w.manager.AccountProperties(ns, acct) 5481 if err != nil { 5482 return err 5483 } 5484 w.addressBuffers[acct] = &bip0044AccountData{ 5485 xpub: xpub, 5486 albExternal: addressBuffer{ 5487 branchXpub: extKey, 5488 lastUsed: props.LastUsedExternalIndex, 5489 cursor: props.LastReturnedExternalIndex - props.LastUsedExternalIndex, 5490 }, 5491 albInternal: addressBuffer{ 5492 branchXpub: intKey, 5493 lastUsed: props.LastUsedInternalIndex, 5494 cursor: props.LastReturnedInternalIndex - props.LastUsedInternalIndex, 5495 }, 5496 } 5497 return nil 5498 } 5499 for acct := uint32(0); acct <= lastAcct; acct++ { 5500 if err := addAccountBuffers(acct); err != nil { 5501 return err 5502 } 5503 } 5504 for acct := uint32(udb.ImportedAddrAccount + 1); acct <= lastImported; acct++ { 5505 if err := addAccountBuffers(acct); err != nil { 5506 return err 5507 } 5508 } 5509 5510 vb = w.readDBVoteBits(tx) 5511 5512 tspendPolicy, vspTSpendPolicy, err = w.readDBTreasuryPolicies(tx) 5513 if err != nil { 5514 return err 5515 } 5516 treasuryKeyPolicy, vspTreasuryKeyPolicy, err = w.readDBTreasuryKeyPolicies(tx) 5517 if err != nil { 5518 return err 5519 } 5520 5521 return nil 5522 }) 5523 if err != nil { 5524 return nil, errors.E(op, err) 5525 } 5526 5527 w.NtfnServer = newNotificationServer(w) 5528 w.defaultVoteBits = vb 5529 w.tspendPolicy = tspendPolicy 5530 w.tspendKeyPolicy = treasuryKeyPolicy 5531 w.vspTSpendPolicy = vspTSpendPolicy 5532 w.vspTSpendKeyPolicy = vspTreasuryKeyPolicy 5533 5534 w.stakePoolColdAddrs, err = decodeStakePoolColdExtKey(cfg.StakePoolColdExtKey, 5535 params) 5536 if err != nil { 5537 return nil, errors.E(op, err) 5538 } 5539 w.stakePoolEnabled = len(w.stakePoolColdAddrs) > 0 5540 5541 // Amounts 5542 w.relayFee = cfg.RelayFee 5543 5544 // Record current tip as initialHeight. 5545 _, w.initialHeight = w.MainChainTip(ctx) 5546 5547 return w, nil 5548 } 5549 5550 // getCoinjoinTxsSumbByAcct returns a map with key representing the account and 5551 // the sum of coinjoin output transactions from the account. 5552 func (w *Wallet) getCoinjoinTxsSumbByAcct(ctx context.Context) (map[uint32]int, error) { 5553 const op errors.Op = "wallet.getCoinjoinTxsSumbByAcct" 5554 coinJoinTxsByAcctSum := make(map[uint32]int) 5555 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 5556 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) 5557 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 5558 // Get current block. The block height used for calculating 5559 // the number of tx confirmations. 5560 _, tipHeight := w.txStore.MainChainTip(dbtx) 5561 rangeFn := func(details []udb.TxDetails) (bool, error) { 5562 for _, detail := range details { 5563 if detail.TxType != stake.TxTypeRegular { 5564 continue 5565 } 5566 isMixedTx, mixDenom, _ := PossibleCoinJoin(&detail.MsgTx) 5567 if !isMixedTx { 5568 continue 5569 } 5570 for _, output := range detail.MsgTx.TxOut { 5571 if mixDenom != output.Value { 5572 continue 5573 } 5574 _, addrs := stdscript.ExtractAddrs(output.Version, output.PkScript, w.chainParams) 5575 if len(addrs) == 1 { 5576 acct, err := w.manager.AddrAccount(addrmgrNs, addrs[0]) 5577 // mixed output belongs to wallet. 5578 if err == nil { 5579 coinJoinTxsByAcctSum[acct]++ 5580 } 5581 } 5582 } 5583 } 5584 return false, nil 5585 } 5586 return w.txStore.RangeTransactions(ctx, txmgrNs, 0, tipHeight, rangeFn) 5587 }) 5588 if err != nil { 5589 return nil, errors.E(op, err) 5590 } 5591 5592 return coinJoinTxsByAcctSum, nil 5593 } 5594 5595 // GetCoinjoinTxsSumbByAcct gets all coinjoin outputs sum by accounts. This is done 5596 // in case we need to guess a mixed account on wallet recovery. 5597 func (w *Wallet) GetCoinjoinTxsSumbByAcct(ctx context.Context) (map[uint32]int, error) { 5598 const op errors.Op = "wallet.GetCoinjoinTxsSumbByAcct" 5599 allTxsByAcct, err := w.getCoinjoinTxsSumbByAcct(ctx) 5600 if err != nil { 5601 return nil, errors.E(op, err) 5602 } 5603 5604 return allTxsByAcct, nil 5605 } 5606 5607 // GetVSPTicketsByFeeStatus returns the ticket hashes of tickets with the 5608 // informed fee status. 5609 func (w *Wallet) GetVSPTicketsByFeeStatus(ctx context.Context, feeStatus int) ([]chainhash.Hash, error) { 5610 const op errors.Op = "wallet.GetVSPTicketsByFeeStatus" 5611 tickets := map[chainhash.Hash]*udb.VSPTicket{} 5612 var err error 5613 err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 5614 tickets, err = udb.GetVSPTicketsByFeeStatus(dbtx, feeStatus) 5615 return err 5616 }) 5617 if err != nil { 5618 return nil, errors.E(op, err) 5619 } 5620 5621 response := make([]chainhash.Hash, len(tickets)) 5622 i := 0 5623 for hash := range tickets { 5624 copy(response[i][:], hash[:]) 5625 i++ 5626 } 5627 5628 return response, nil 5629 } 5630 5631 // SetPublished sets the informed hash as true or false. 5632 func (w *Wallet) SetPublished(ctx context.Context, hash *chainhash.Hash, published bool) error { 5633 err := walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 5634 hash := hash 5635 err := w.txStore.SetPublished(dbtx, hash, published) 5636 if err != nil { 5637 return err 5638 } 5639 return nil 5640 }) 5641 if err != nil { 5642 return err 5643 } 5644 return nil 5645 } 5646 5647 type VSPTicket struct { 5648 FeeHash chainhash.Hash 5649 FeeTxStatus uint32 5650 VSPHostID uint32 5651 Host string 5652 PubKey []byte 5653 } 5654 5655 // VSPTicketInfo returns the various information for a given vsp ticket 5656 func (w *Wallet) VSPTicketInfo(ctx context.Context, ticketHash *chainhash.Hash) (*VSPTicket, error) { 5657 var data *udb.VSPTicket 5658 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 5659 var err error 5660 data, err = udb.GetVSPTicket(dbtx, *ticketHash) 5661 if err != nil { 5662 return err 5663 } 5664 return nil 5665 }) 5666 if err == nil && data == nil { 5667 err = errors.E(errors.NotExist) 5668 return nil, err 5669 } else if data == nil { 5670 return nil, err 5671 } 5672 convertedData := &VSPTicket{ 5673 FeeHash: data.FeeHash, 5674 FeeTxStatus: data.FeeTxStatus, 5675 VSPHostID: data.VSPHostID, 5676 Host: data.Host, 5677 PubKey: data.PubKey, 5678 } 5679 return convertedData, err 5680 } 5681 5682 // VSPFeeHashForTicket returns the hash of the fee transaction associated with a 5683 // VSP payment. 5684 func (w *Wallet) VSPFeeHashForTicket(ctx context.Context, ticketHash *chainhash.Hash) (chainhash.Hash, error) { 5685 var feeHash chainhash.Hash 5686 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 5687 data, err := udb.GetVSPTicket(dbtx, *ticketHash) 5688 if err != nil { 5689 return err 5690 } 5691 feeHash = data.FeeHash 5692 return nil 5693 }) 5694 if err == nil && feeHash == (chainhash.Hash{}) { 5695 err = errors.E(errors.NotExist) 5696 } 5697 return feeHash, err 5698 } 5699 5700 // VSPHostForTicket returns the current vsp host associated with VSP Ticket. 5701 func (w *Wallet) VSPHostForTicket(ctx context.Context, ticketHash *chainhash.Hash) (string, error) { 5702 var host string 5703 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 5704 data, err := udb.GetVSPTicket(dbtx, *ticketHash) 5705 if err != nil { 5706 return err 5707 } 5708 host = data.Host 5709 return nil 5710 }) 5711 if err == nil && host == "" { 5712 err = errors.E(errors.NotExist) 5713 } 5714 return host, err 5715 } 5716 5717 // IsVSPTicketConfirmed returns whether or not a VSP ticket has been confirmed 5718 // by a VSP. 5719 func (w *Wallet) IsVSPTicketConfirmed(ctx context.Context, ticketHash *chainhash.Hash) (bool, error) { 5720 confirmed := false 5721 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 5722 data, err := udb.GetVSPTicket(dbtx, *ticketHash) 5723 if err != nil { 5724 return err 5725 } 5726 if data.FeeTxStatus == uint32(udb.VSPFeeProcessConfirmed) { 5727 confirmed = true 5728 } 5729 return nil 5730 }) 5731 return confirmed, err 5732 } 5733 5734 // UpdateVspTicketFeeToPaid updates a vsp ticket fee status to paid. 5735 // This is needed when finishing the fee payment on VSPs Process. 5736 func (w *Wallet) UpdateVspTicketFeeToPaid(ctx context.Context, ticketHash, feeHash *chainhash.Hash, host string, pubkey []byte) error { 5737 var err error 5738 err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 5739 err = udb.SetVSPTicket(dbtx, ticketHash, &udb.VSPTicket{ 5740 FeeHash: *feeHash, 5741 FeeTxStatus: uint32(udb.VSPFeeProcessPaid), 5742 Host: host, 5743 PubKey: pubkey, 5744 }) 5745 return err 5746 }) 5747 5748 return err 5749 } 5750 5751 func (w *Wallet) UpdateVspTicketFeeToStarted(ctx context.Context, ticketHash, feeHash *chainhash.Hash, host string, pubkey []byte) error { 5752 var err error 5753 err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 5754 err = udb.SetVSPTicket(dbtx, ticketHash, &udb.VSPTicket{ 5755 FeeHash: *feeHash, 5756 FeeTxStatus: uint32(udb.VSPFeeProcessStarted), 5757 Host: host, 5758 PubKey: pubkey, 5759 }) 5760 return err 5761 }) 5762 5763 return err 5764 } 5765 5766 func (w *Wallet) UpdateVspTicketFeeToErrored(ctx context.Context, ticketHash *chainhash.Hash, host string, pubkey []byte) error { 5767 var err error 5768 err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 5769 err = udb.SetVSPTicket(dbtx, ticketHash, &udb.VSPTicket{ 5770 FeeHash: chainhash.Hash{}, 5771 FeeTxStatus: uint32(udb.VSPFeeProcessErrored), 5772 Host: host, 5773 PubKey: pubkey, 5774 }) 5775 return err 5776 }) 5777 5778 return err 5779 } 5780 5781 func (w *Wallet) UpdateVspTicketFeeToConfirmed(ctx context.Context, ticketHash, feeHash *chainhash.Hash, host string, pubkey []byte) error { 5782 var err error 5783 err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 5784 err = udb.SetVSPTicket(dbtx, ticketHash, &udb.VSPTicket{ 5785 FeeHash: *feeHash, 5786 FeeTxStatus: uint32(udb.VSPFeeProcessConfirmed), 5787 Host: host, 5788 PubKey: pubkey, 5789 }) 5790 return err 5791 }) 5792 5793 return err 5794 } 5795 5796 // ForUnspentUnexpiredTickets performs a function on every unexpired and unspent 5797 // ticket from the wallet. 5798 func (w *Wallet) ForUnspentUnexpiredTickets(ctx context.Context, 5799 f func(hash *chainhash.Hash) error) error { 5800 5801 params := w.ChainParams() 5802 5803 iter := func(ticketSummaries []*TicketSummary, _ *wire.BlockHeader) (bool, error) { 5804 for _, ticketSummary := range ticketSummaries { 5805 switch ticketSummary.Status { 5806 case TicketStatusLive: 5807 case TicketStatusImmature: 5808 case TicketStatusUnspent: 5809 default: 5810 continue 5811 } 5812 5813 ticketHash := *ticketSummary.Ticket.Hash 5814 err := f(&ticketHash) 5815 if err != nil { 5816 return false, err 5817 } 5818 } 5819 5820 return false, nil 5821 } 5822 5823 const requiredConfs = 6 + 2 5824 _, blockHeight := w.MainChainTip(ctx) 5825 startBlockNum := blockHeight - 5826 int32(params.TicketExpiry+uint32(params.TicketMaturity)-requiredConfs) 5827 startBlock := NewBlockIdentifierFromHeight(startBlockNum) 5828 endBlock := NewBlockIdentifierFromHeight(blockHeight) 5829 return w.GetTickets(ctx, iter, startBlock, endBlock) 5830 }