decred.org/dcrwallet/v3@v3.1.0/wallet/chainntfns.go (about) 1 // Copyright (c) 2013-2015 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 "context" 10 "math/big" 11 "time" 12 13 "decred.org/dcrwallet/v3/deployments" 14 "decred.org/dcrwallet/v3/errors" 15 "decred.org/dcrwallet/v3/wallet/txrules" 16 "decred.org/dcrwallet/v3/wallet/udb" 17 "decred.org/dcrwallet/v3/wallet/walletdb" 18 "github.com/decred/dcrd/blockchain/stake/v5" 19 blockchain "github.com/decred/dcrd/blockchain/standalone/v2" 20 "github.com/decred/dcrd/chaincfg/chainhash" 21 "github.com/decred/dcrd/dcrutil/v4" 22 gcs2 "github.com/decred/dcrd/gcs/v4" 23 "github.com/decred/dcrd/txscript/v4" 24 "github.com/decred/dcrd/txscript/v4/stdaddr" 25 "github.com/decred/dcrd/txscript/v4/stdscript" 26 "github.com/decred/dcrd/wire" 27 ) 28 29 func (w *Wallet) extendMainChain(ctx context.Context, op errors.Op, dbtx walletdb.ReadWriteTx, 30 header *wire.BlockHeader, f *gcs2.FilterV2, transactions []*wire.MsgTx) ([]wire.OutPoint, error) { 31 txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey) 32 33 blockHash := header.BlockHash() 34 35 // Enforce checkpoints 36 height := int32(header.Height) 37 ckpt := CheckpointHash(w.chainParams.Net, height) 38 if ckpt != nil && blockHash != *ckpt { 39 err := errors.Errorf("block hash %v does not satisify "+ 40 "checkpoint hash %v for height %v", blockHash, 41 ckpt, height) 42 return nil, errors.E(errors.Consensus, err) 43 } 44 45 // Propagate the error unless this block is already included in the main 46 // chain. 47 err := w.txStore.ExtendMainChain(txmgrNs, header, f) 48 if err != nil && !errors.Is(err, errors.Exist) { 49 return nil, errors.E(op, err) 50 } 51 52 // Notify interested clients of the connected block. 53 w.NtfnServer.notifyAttachedBlock(dbtx, header, &blockHash) 54 55 blockMeta, err := w.txStore.GetBlockMetaForHash(txmgrNs, &blockHash) 56 if err != nil { 57 return nil, errors.E(op, err) 58 } 59 60 var watch []wire.OutPoint 61 for _, tx := range transactions { 62 // In manual ticket mode, tickets are only ever added to the 63 // wallet using AddTransaction. Skip over any relevant tickets 64 // seen in this block unless they already exist in the wallet. 65 if w.manualTickets && stake.IsSStx(tx) { 66 txHash := tx.TxHash() 67 if !w.txStore.ExistsTx(txmgrNs, &txHash) { 68 continue 69 } 70 } 71 72 rec, err := udb.NewTxRecordFromMsgTx(tx, time.Now()) 73 if err != nil { 74 return nil, errors.E(op, err) 75 } 76 ops, err := w.processTransactionRecord(ctx, dbtx, rec, header, &blockMeta) 77 if err != nil { 78 return nil, errors.E(op, err) 79 } 80 watch = append(watch, ops...) 81 } 82 83 return watch, nil 84 } 85 86 // ChainSwitch updates the wallet's main chain, either by extending the chain 87 // with new blocks, or switching to a better sidechain. A sidechain for removed 88 // blocks (if any) is returned. If relevantTxs is non-nil, the block marker for 89 // the latest block with processed transactions is updated for the new tip 90 // block. 91 func (w *Wallet) ChainSwitch(ctx context.Context, forest *SidechainForest, chain []*BlockNode, 92 relevantTxs map[chainhash.Hash][]*wire.MsgTx) ([]*BlockNode, error) { 93 const op errors.Op = "wallet.ChainSwitch" 94 95 if len(chain) == 0 { 96 return nil, errors.E(op, errors.Invalid, "zero-length chain") 97 } 98 99 chainTipChanges := &MainTipChangedNotification{ 100 AttachedBlocks: make([]*chainhash.Hash, 0, len(chain)), 101 DetachedBlocks: nil, 102 NewHeight: int32(chain[len(chain)-1].Header.Height), 103 } 104 105 sideChainForkHeight := int32(chain[0].Header.Height) 106 var prevChain []*BlockNode 107 108 newWork := chain[len(chain)-1].workSum 109 oldWork := new(big.Int) 110 111 w.lockedOutpointMu.Lock() 112 113 var watchOutPoints []wire.OutPoint 114 err := walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 115 txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey) 116 117 tipHash, tipHeight := w.txStore.MainChainTip(dbtx) 118 119 if tipHash == *chain[len(chain)-1].Hash { 120 return nil 121 } 122 123 if sideChainForkHeight <= tipHeight { 124 chainTipChanges.DetachedBlocks = make([]*chainhash.Hash, tipHeight-sideChainForkHeight+1) 125 prevChain = make([]*BlockNode, tipHeight-sideChainForkHeight+1) 126 for i := tipHeight; i >= sideChainForkHeight; i-- { 127 hash, err := w.txStore.GetMainChainBlockHashForHeight(txmgrNs, i) 128 if err != nil { 129 return err 130 } 131 header, err := w.txStore.GetBlockHeader(dbtx, &hash) 132 if err != nil { 133 return err 134 } 135 _, filter, err := w.txStore.CFilterV2(dbtx, &hash) 136 if err != nil { 137 return err 138 } 139 140 // DetachedBlocks and prevChain are sorted in order of increasing heights. 141 chainTipChanges.DetachedBlocks[i-sideChainForkHeight] = &hash 142 prevChain[i-sideChainForkHeight] = NewBlockNode(header, &hash, filter) 143 144 // For transaction notifications, the blocks are notified in reverse 145 // height order. 146 w.NtfnServer.notifyDetachedBlock(header) 147 148 oldWork.Add(oldWork, blockchain.CalcWork(header.Bits)) 149 } 150 151 if newWork.Cmp(oldWork) != 1 { 152 return errors.Errorf("failed reorganize: sidechain ending at block %v has less total work "+ 153 "than the main chain tip block %v", chain[len(chain)-1].Hash, &tipHash) 154 } 155 156 // Remove blocks on the current main chain that are at or above the 157 // height of the block that begins the side chain. 158 err := w.txStore.Rollback(dbtx, sideChainForkHeight) 159 if err != nil { 160 return err 161 } 162 } 163 164 for _, n := range chain { 165 if voteVersion(w.chainParams) < n.Header.StakeVersion { 166 log.Warnf("Old vote version detected (v%v), please update your "+ 167 "wallet to the latest version.", voteVersion(w.chainParams)) 168 } 169 170 watch, err := w.extendMainChain(ctx, op, dbtx, n.Header, n.FilterV2, relevantTxs[*n.Hash]) 171 if err != nil { 172 return err 173 } 174 watchOutPoints = append(watchOutPoints, watch...) 175 176 // Add the block hash to the notification. 177 chainTipChanges.AttachedBlocks = append(chainTipChanges.AttachedBlocks, n.Hash) 178 } 179 180 if relevantTxs != nil { 181 // To avoid skipped blocks, the marker is not advanced if there is a 182 // gap between the existing rescan point (main chain fork point of 183 // the current marker) and the first block attached in this chain 184 // switch. 185 r, err := w.rescanPoint(dbtx) 186 if err != nil { 187 return err 188 } 189 rHeader, err := w.txStore.GetBlockHeader(dbtx, r) 190 if err != nil { 191 return err 192 } 193 if !(rHeader.Height+1 < chain[0].Header.Height) { 194 marker := chain[len(chain)-1].Hash 195 log.Debugf("Updating processed txs block marker to %v", marker) 196 err := w.txStore.UpdateProcessedTxsBlockMarker(dbtx, marker) 197 if err != nil { 198 return err 199 } 200 } 201 } 202 203 // Prune unmined transactions that don't belong on the extended chain. 204 // An error here is not fatal and should just be logged. 205 // 206 // TODO: The stake difficulty passed here is not correct. This must be 207 // the difficulty of the next block, not the tip block. 208 tip := chain[len(chain)-1] 209 hashes, err := w.txStore.PruneUnmined(dbtx, tip.Header.SBits) 210 if err != nil { 211 log.Errorf("Failed to prune unmined transactions when "+ 212 "connecting block height %v: %v", tip.Header.Height, err) 213 } 214 215 for _, hash := range hashes { 216 w.NtfnServer.notifyRemovedTransaction(*hash) 217 } 218 return nil 219 }) 220 w.lockedOutpointMu.Unlock() 221 if err != nil { 222 return nil, errors.E(op, err) 223 } 224 225 if len(chainTipChanges.AttachedBlocks) != 0 { 226 w.recentlyPublishedMu.Lock() 227 for _, node := range chain { 228 for _, tx := range relevantTxs[*node.Hash] { 229 txHash := tx.TxHash() 230 delete(w.recentlyPublished, txHash) 231 } 232 } 233 w.recentlyPublishedMu.Unlock() 234 } 235 236 if n, err := w.NetworkBackend(); err == nil { 237 _, err = w.watchHDAddrs(ctx, false, n) 238 if err != nil { 239 return nil, errors.E(op, err) 240 } 241 242 if len(watchOutPoints) > 0 { 243 err = n.LoadTxFilter(ctx, false, nil, watchOutPoints) 244 if err != nil { 245 log.Errorf("Failed to watch outpoints: %v", err) 246 } 247 } 248 } 249 250 forest.PruneTree(chain[0].Hash) 251 forest.Prune(int32(chain[len(chain)-1].Header.Height), w.chainParams) 252 253 w.NtfnServer.notifyMainChainTipChanged(chainTipChanges) 254 w.NtfnServer.sendAttachedBlockNotification(ctx) 255 256 return prevChain, nil 257 } 258 259 // evaluateStakePoolTicket evaluates a stake pool ticket to see if it's 260 // acceptable to the stake pool. The ticket must pay out to the stake 261 // pool cold wallet, and must have a sufficient fee. 262 func (w *Wallet) evaluateStakePoolTicket(rec *udb.TxRecord, blockHeight int32, poolUser stdaddr.Address) bool { 263 tx := rec.MsgTx 264 265 // Check the first commitment output (txOuts[1]) 266 // and ensure that the address found there exists 267 // in the list of approved addresses. Also ensure 268 // that the fee exists and is of the amount 269 // requested by the pool. 270 commitmentOut := tx.TxOut[1] 271 commitAddr, err := stake.AddrFromSStxPkScrCommitment( 272 commitmentOut.PkScript, w.chainParams) 273 if err != nil { 274 log.Warnf("Cannot parse commitment address from ticket %v: %v", 275 &rec.Hash, err) 276 return false 277 } 278 279 // Extract the fee from the ticket. 280 in := dcrutil.Amount(0) 281 for i := range tx.TxOut { 282 if i%2 != 0 { 283 commitAmt, err := stake.AmountFromSStxPkScrCommitment( 284 tx.TxOut[i].PkScript) 285 if err != nil { 286 log.Warnf("Cannot parse commitment amount for output %i from ticket %v: %v", 287 i, &rec.Hash, err) 288 return false 289 } 290 in += commitAmt 291 } 292 } 293 out := dcrutil.Amount(0) 294 for i := range tx.TxOut { 295 out += dcrutil.Amount(tx.TxOut[i].Value) 296 } 297 fees := in - out 298 299 _, exists := w.stakePoolColdAddrs[commitAddr.String()] 300 if exists { 301 commitAmt, err := stake.AmountFromSStxPkScrCommitment( 302 commitmentOut.PkScript) 303 if err != nil { 304 log.Warnf("Cannot parse commitment amount from ticket %v: %v", &rec.Hash, err) 305 return false 306 } 307 308 // Calculate the fee required based on the current 309 // height and the required amount from the pool. 310 feeNeeded := txrules.StakePoolTicketFee(dcrutil.Amount( 311 tx.TxOut[0].Value), fees, blockHeight, w.poolFees, 312 w.chainParams, false) 313 if commitAmt < feeNeeded { 314 log.Warnf("User %s submitted ticket %v which "+ 315 "has less fees than are required to use this "+ 316 "stake pool and is being skipped (required: %v"+ 317 ", found %v)", commitAddr, 318 tx.TxHash(), feeNeeded, commitAmt) 319 320 // Reject the entire transaction if it didn't 321 // pay the pool server fees. 322 return false 323 } 324 } else { 325 log.Warnf("Unknown pool commitment address %s for ticket %v", 326 commitAddr, tx.TxHash()) 327 return false 328 } 329 330 log.Debugf("Accepted valid stake pool ticket %v committing %v in fees", 331 tx.TxHash(), tx.TxOut[0].Value) 332 333 return true 334 } 335 336 // AddTransaction stores tx, marking it as mined in the block described by 337 // blockHash, or recording it to the wallet's mempool when nil. 338 // 339 // This method will always add ticket transactions to the wallet, even when 340 // configured in manual ticket mode. It is up to network syncers to avoid 341 // calling this method on unmined tickets. 342 func (w *Wallet) AddTransaction(ctx context.Context, tx *wire.MsgTx, blockHash *chainhash.Hash) error { 343 const op errors.Op = "wallet.AddTransaction" 344 345 w.recentlyPublishedMu.Lock() 346 _, recent := w.recentlyPublished[tx.TxHash()] 347 w.recentlyPublishedMu.Unlock() 348 if recent { 349 return nil 350 } 351 352 // Prevent recording unmined tspends since they need to go through 353 // voting for potentially a long time. 354 if isTreasurySpend(tx) && blockHash == nil { 355 log.Debugf("Ignoring unmined TSPend %s", tx.TxHash()) 356 return nil 357 } 358 359 w.lockedOutpointMu.Lock() 360 var watchOutPoints []wire.OutPoint 361 err := walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 362 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 363 364 rec, err := udb.NewTxRecordFromMsgTx(tx, time.Now()) 365 if err != nil { 366 return err 367 } 368 369 // Prevent orphan votes from entering the wallet's unmined transaction 370 // set. 371 if isVote(&rec.MsgTx) && blockHash == nil { 372 votedBlock, _ := stake.SSGenBlockVotedOn(&rec.MsgTx) 373 tipBlock, _ := w.txStore.MainChainTip(dbtx) 374 if votedBlock != tipBlock { 375 log.Debugf("Rejected unmined orphan vote %v which votes on block %v", 376 &rec.Hash, &votedBlock) 377 return nil 378 } 379 } 380 381 var header *wire.BlockHeader 382 var meta *udb.BlockMeta 383 switch { 384 case blockHash != nil: 385 inChain, _ := w.txStore.BlockInMainChain(dbtx, blockHash) 386 if !inChain { 387 break 388 } 389 header, err = w.txStore.GetBlockHeader(dbtx, blockHash) 390 if err != nil { 391 return err 392 } 393 meta = new(udb.BlockMeta) 394 *meta, err = w.txStore.GetBlockMetaForHash(txmgrNs, blockHash) 395 if err != nil { 396 return err 397 } 398 } 399 400 watchOutPoints, err = w.processTransactionRecord(ctx, dbtx, rec, header, meta) 401 return err 402 }) 403 w.lockedOutpointMu.Unlock() 404 if err != nil { 405 return errors.E(op, err) 406 } 407 if n, err := w.NetworkBackend(); err == nil && len(watchOutPoints) > 0 { 408 _, err := w.watchHDAddrs(ctx, false, n) 409 if err != nil { 410 return errors.E(op, err) 411 } 412 if len(watchOutPoints) > 0 { 413 err = n.LoadTxFilter(ctx, false, nil, watchOutPoints) 414 if err != nil { 415 log.Errorf("Failed to watch outpoints: %v", err) 416 } 417 } 418 } 419 return nil 420 } 421 422 func (w *Wallet) processTransactionRecord(ctx context.Context, dbtx walletdb.ReadWriteTx, rec *udb.TxRecord, 423 header *wire.BlockHeader, blockMeta *udb.BlockMeta) (watchOutPoints []wire.OutPoint, err error) { 424 425 const op errors.Op = "wallet.processTransactionRecord" 426 427 addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey) 428 stakemgrNs := dbtx.ReadWriteBucket(wstakemgrNamespaceKey) 429 txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey) 430 431 height := int32(-1) 432 if header != nil { 433 height = int32(header.Height) 434 } 435 436 // At the moment all notified transactions are assumed to actually be 437 // relevant. This assumption will not hold true when SPV support is 438 // added, but until then, simply insert the transaction because there 439 // should either be one or more relevant inputs or outputs. 440 if header == nil { 441 err = w.txStore.InsertMemPoolTx(dbtx, rec) 442 if errors.Is(err, errors.Exist) { 443 log.Warnf("Refusing to add unmined transaction %v since same "+ 444 "transaction already exists mined", &rec.Hash) 445 return nil, nil 446 } 447 } else { 448 err = w.txStore.InsertMinedTx(dbtx, rec, &blockMeta.Hash) 449 } 450 if err != nil { 451 return nil, errors.E(op, err) 452 } 453 454 // Handle incoming SStx; store them in the stake manager if we own 455 // the OP_SSTX tagged out, except if we're operating as a stake pool 456 // server. In that case, additionally consider the first commitment 457 // output as well. 458 if w.stakePoolEnabled && header != nil && rec.TxType == stake.TxTypeSStx { 459 // Errors don't matter here. If addrs is nil, the range below 460 // does nothing. 461 txOut := rec.MsgTx.TxOut[0] 462 _, addrs := stdscript.ExtractAddrs(txOut.Version, txOut.PkScript, w.chainParams) 463 insert := false 464 for _, addr := range addrs { 465 switch addr := addr.(type) { 466 case stdaddr.Hash160er: 467 if !w.manager.ExistsHash160(addrmgrNs, addr.Hash160()[:]) { 468 continue 469 } 470 default: 471 continue 472 } 473 474 // We are operating as a stake pool. The below 475 // function will ONLY add the ticket into the 476 // stake pool if it has been found within a 477 // block. 478 if header == nil { 479 break 480 } 481 482 if w.evaluateStakePoolTicket(rec, height, addr) { 483 // Be sure to insert this into the user's stake 484 // pool entry into the stake manager. 485 poolTicket := &udb.PoolTicket{ 486 Ticket: rec.Hash, 487 HeightTicket: uint32(height), 488 Status: udb.TSImmatureOrLive, 489 } 490 err := w.stakeMgr.UpdateStakePoolUserTickets( 491 stakemgrNs, addr, poolTicket) 492 if err != nil { 493 log.Warnf("Failed to insert stake pool "+ 494 "user ticket: %v", err) 495 } 496 log.Debugf("Inserted stake pool ticket %v for user %v "+ 497 "into the stake store database", &rec.Hash, addr) 498 499 insert = true 500 break 501 } 502 503 // At this point the ticket must be invalid, so insert it into the 504 // list of invalid user tickets. 505 err := w.stakeMgr.UpdateStakePoolUserInvalTickets( 506 stakemgrNs, addr, &rec.Hash) 507 if err != nil { 508 log.Warnf("Failed to update pool user %v with "+ 509 "invalid ticket %v", addr, rec.Hash) 510 } 511 } 512 513 if insert { 514 err := w.stakeMgr.InsertSStx(stakemgrNs, dcrutil.NewTx(&rec.MsgTx)) 515 if err != nil { 516 log.Errorf("Failed to insert SStx %v"+ 517 "into the stake store.", &rec.Hash) 518 } 519 } 520 } 521 522 // Handle incoming mined votes (only in stakepool mode) 523 if w.stakePoolEnabled && rec.TxType == stake.TxTypeSSGen && header != nil { 524 ticketHash := &rec.MsgTx.TxIn[1].PreviousOutPoint.Hash 525 txInHeight := rec.MsgTx.TxIn[1].BlockHeight 526 poolTicket := &udb.PoolTicket{ 527 Ticket: *ticketHash, 528 HeightTicket: txInHeight, 529 Status: udb.TSVoted, 530 SpentBy: rec.Hash, 531 HeightSpent: uint32(height), 532 } 533 534 poolUser, err := w.stakeMgr.SStxAddress(stakemgrNs, ticketHash) 535 if err != nil { 536 log.Warnf("Failed to fetch stake pool user for "+ 537 "ticket %v (voted ticket): %v", ticketHash, err) 538 } else { 539 err = w.stakeMgr.UpdateStakePoolUserTickets( 540 stakemgrNs, poolUser, poolTicket) 541 if err != nil { 542 log.Warnf("Failed to update stake pool ticket for "+ 543 "stake pool user %s after voting", 544 poolUser) 545 } else { 546 log.Debugf("Updated voted stake pool ticket %v "+ 547 "for user %v into the stake store database ("+ 548 "vote hash: %v)", ticketHash, poolUser, &rec.Hash) 549 } 550 } 551 } 552 553 // Handle incoming mined revocations (only in stakepool mode) 554 if w.stakePoolEnabled && rec.TxType == stake.TxTypeSSRtx && header != nil { 555 txInHash := &rec.MsgTx.TxIn[0].PreviousOutPoint.Hash 556 txInHeight := rec.MsgTx.TxIn[0].BlockHeight 557 poolTicket := &udb.PoolTicket{ 558 Ticket: *txInHash, 559 HeightTicket: txInHeight, 560 Status: udb.TSMissed, 561 SpentBy: rec.Hash, 562 HeightSpent: uint32(height), 563 } 564 565 poolUser, err := w.stakeMgr.SStxAddress(stakemgrNs, txInHash) 566 if err != nil { 567 log.Warnf("failed to fetch stake pool user for "+ 568 "ticket %v (missed ticket)", txInHash) 569 } else { 570 err = w.stakeMgr.UpdateStakePoolUserTickets( 571 stakemgrNs, poolUser, poolTicket) 572 if err != nil { 573 log.Warnf("failed to update stake pool ticket for "+ 574 "stake pool user %s after revoking", 575 poolUser) 576 } else { 577 log.Debugf("Updated missed stake pool ticket %v "+ 578 "for user %v into the stake store database ("+ 579 "revocation hash: %v)", txInHash, poolUser, &rec.Hash) 580 } 581 } 582 } 583 584 // Skip unlocking outpoints if the transaction is a vote or revocation as the lock 585 // is not held. 586 skipOutpoints := rec.TxType == stake.TxTypeSSGen || rec.TxType == stake.TxTypeSSRtx 587 588 // Handle input scripts that contain P2PKs that we care about. 589 for i, input := range rec.MsgTx.TxIn { 590 if !skipOutpoints { 591 prev := input.PreviousOutPoint 592 delete(w.lockedOutpoints, outpoint{prev.Hash, prev.Index}) 593 } 594 // TODO: the prevout's actual pkScript version is needed. 595 if stdscript.IsMultiSigSigScript(scriptVersionAssumed, input.SignatureScript) { 596 rs := stdscript.MultiSigRedeemScriptFromScriptSigV0(input.SignatureScript) 597 598 class, addrs := stdscript.ExtractAddrs(scriptVersionAssumed, rs, w.chainParams) 599 if class != stdscript.STMultiSig { 600 // This should never happen, but be paranoid. 601 continue 602 } 603 604 isRelevant := false 605 for _, addr := range addrs { 606 ma, err := w.manager.Address(addrmgrNs, addr) 607 if err != nil { 608 // Missing addresses are skipped. Other errors should be 609 // propagated. 610 if errors.Is(err, errors.NotExist) { 611 continue 612 } 613 return nil, errors.E(op, err) 614 } 615 isRelevant = true 616 err = w.markUsedAddress(op, dbtx, ma) 617 if err != nil { 618 return nil, err 619 } 620 log.Debugf("Marked address %v used", addr) 621 } 622 623 // Add the script to the script databases. 624 // TODO Markused script address? cj 625 if isRelevant { 626 n, _ := w.NetworkBackend() 627 addr, err := w.manager.ImportScript(addrmgrNs, rs) 628 switch { 629 case errors.Is(err, errors.Exist): 630 case err != nil: 631 return nil, errors.E(op, err) 632 case n != nil: 633 addrs := []stdaddr.Address{addr.Address()} 634 err := n.LoadTxFilter(ctx, false, addrs, nil) 635 if err != nil { 636 return nil, errors.E(op, err) 637 } 638 } 639 } 640 641 // If we're spending a multisig outpoint we know about, 642 // update the outpoint. Inefficient because you deserialize 643 // the entire multisig output info. Consider a specific 644 // exists function in udb. The error here is skipped 645 // because the absence of an multisignature output for 646 // some script can not always be considered an error. For 647 // example, the wallet might be rescanning as called from 648 // the above function and so does not have the output 649 // included yet. 650 mso, err := w.txStore.GetMultisigOutput(txmgrNs, &input.PreviousOutPoint) 651 if mso != nil && err == nil { 652 err = w.txStore.SpendMultisigOut(txmgrNs, &input.PreviousOutPoint, 653 rec.Hash, uint32(i)) 654 if err != nil { 655 return nil, errors.E(op, err) 656 } 657 } 658 } 659 } 660 661 // Check every output to determine whether it is controlled by a 662 // wallet key. If so, mark the output as a credit and mark 663 // outpoints to watch. 664 for i, output := range rec.MsgTx.TxOut { 665 class, addrs := stdscript.ExtractAddrs(output.Version, output.PkScript, w.chainParams) 666 if class == stdscript.STNonStandard { 667 // Non-standard outputs are skipped. 668 continue 669 } 670 subClass, isStakeType := txrules.StakeSubScriptType(class) 671 if isStakeType { 672 class = subClass 673 } 674 675 isTicketCommit := rec.TxType == stake.TxTypeSStx && i%2 == 1 676 watchOutPoint := true 677 if isTicketCommit { 678 // For ticket commitments, decode the address stored in the pkscript 679 // and evaluate ownership of that. 680 addr, err := stake.AddrFromSStxPkScrCommitment(output.PkScript, 681 w.chainParams) 682 if err != nil { 683 log.Warnf("failed to decode ticket commitment script of %s:%d", 684 rec.Hash, i) 685 continue 686 } 687 addrs = []stdaddr.Address{addr} 688 watchOutPoint = false 689 } else if output.Value == 0 { 690 // The only case of outputs with 0 value that we need to handle are 691 // ticket commitments. All other outputs can be ignored. 692 continue 693 } 694 695 var tree int8 696 if isStakeType { 697 tree = 1 698 } 699 outpoint := wire.OutPoint{Hash: rec.Hash, Tree: tree} 700 for _, addr := range addrs { 701 ma, err := w.manager.Address(addrmgrNs, addr) 702 // Missing addresses are skipped. Other errors should 703 // be propagated. 704 if errors.Is(err, errors.NotExist) { 705 continue 706 } 707 if err != nil { 708 return nil, errors.E(op, err) 709 } 710 if isTicketCommit { 711 err = w.txStore.AddTicketCommitment(txmgrNs, rec, uint32(i), 712 ma.Account()) 713 } else { 714 err = w.txStore.AddCredit(dbtx, rec, blockMeta, 715 uint32(i), ma.Internal(), ma.Account()) 716 } 717 if err != nil { 718 return nil, errors.E(op, err) 719 } 720 err = w.markUsedAddress(op, dbtx, ma) 721 if err != nil { 722 return nil, err 723 } 724 if watchOutPoint { 725 outpoint.Index = uint32(i) 726 watchOutPoints = append(watchOutPoints, outpoint) 727 } 728 log.Debugf("Marked address %v used", addr) 729 } 730 731 // Handle P2SH addresses that are multisignature scripts 732 // with keys that we own. 733 if class == stdscript.STScriptHash { 734 var expandedScript []byte 735 for _, addr := range addrs { 736 expandedScript, err = w.manager.RedeemScript(addrmgrNs, addr) 737 if err != nil { 738 log.Debugf("failed to find redeemscript for "+ 739 "address %v in address manager: %v", 740 addr, err) 741 continue 742 } 743 } 744 745 // Otherwise, extract the actual addresses and see if any are ours. 746 expClass, multisigAddrs := stdscript.ExtractAddrs(scriptVersionAssumed, expandedScript, w.chainParams) 747 748 // Skip non-multisig scripts. 749 if expClass != stdscript.STMultiSig { 750 continue 751 } 752 753 for _, maddr := range multisigAddrs { 754 _, err := w.manager.Address(addrmgrNs, maddr) 755 // An address we own; handle accordingly. 756 if err == nil { 757 err := w.txStore.AddMultisigOut( 758 dbtx, rec, blockMeta, uint32(i)) 759 if err != nil { 760 // This will throw if there are multiple private keys 761 // for this multisignature output owned by the wallet, 762 // so it's routed to debug. 763 log.Debugf("unable to add multisignature output: %v", err) 764 } 765 } 766 } 767 } 768 } 769 770 if (rec.TxType == stake.TxTypeSSGen) || (rec.TxType == stake.TxTypeSSRtx) { 771 err = w.txStore.RedeemTicketCommitments(txmgrNs, rec, blockMeta) 772 if err != nil { 773 log.Errorf("Error redeeming ticket commitments: %v", err) 774 } 775 } 776 777 // Send notification of mined or unmined transaction to any interested 778 // clients. 779 // 780 // TODO: Avoid the extra db hits. 781 if header == nil { 782 details, err := w.txStore.UniqueTxDetails(txmgrNs, &rec.Hash, nil) 783 if err != nil { 784 log.Errorf("Cannot query transaction details for notifiation: %v", err) 785 } else { 786 w.NtfnServer.notifyUnminedTransaction(dbtx, details) 787 } 788 } else { 789 details, err := w.txStore.UniqueTxDetails(txmgrNs, &rec.Hash, &blockMeta.Block) 790 if err != nil { 791 log.Errorf("Cannot query transaction details for notifiation: %v", err) 792 } else { 793 w.NtfnServer.notifyMinedTransaction(dbtx, details, blockMeta) 794 } 795 } 796 797 return watchOutPoints, nil 798 } 799 800 // selectOwnedTickets returns a slice of tickets hashes from the tickets 801 // argument that are owned by the wallet. 802 // 803 // Because votes must be created for tickets tracked by both the transaction 804 // manager and the stake manager, this function checks both. 805 func selectOwnedTickets(w *Wallet, dbtx walletdb.ReadTx, tickets []*chainhash.Hash) []*chainhash.Hash { 806 var owned []*chainhash.Hash 807 for _, ticketHash := range tickets { 808 if w.txStore.OwnTicket(dbtx, ticketHash) || w.stakeMgr.OwnTicket(ticketHash) { 809 owned = append(owned, ticketHash) 810 } 811 } 812 return owned 813 } 814 815 // VoteOnOwnedTickets creates and publishes vote transactions for all owned 816 // tickets in the winningTicketHashes slice if wallet voting is enabled. The 817 // vote is only valid when voting on the block described by the passed block 818 // hash and height. When a network backend is associated with the wallet, 819 // relevant commitment outputs are loaded as watched data. 820 func (w *Wallet) VoteOnOwnedTickets(ctx context.Context, winningTicketHashes []*chainhash.Hash, blockHash *chainhash.Hash, blockHeight int32) error { 821 const op errors.Op = "wallet.VoteOnOwnedTickets" 822 823 if !w.votingEnabled || blockHeight < int32(w.chainParams.StakeValidationHeight)-1 { 824 return nil 825 } 826 827 n, err := w.NetworkBackend() 828 if err != nil { 829 return errors.E(op, err) 830 } 831 dcp0010Active, err := deployments.DCP0010Active(ctx, blockHeight, 832 w.chainParams, n) 833 if err != nil { 834 return errors.E(op, err) 835 } 836 dcp0012Active, err := deployments.DCP0012Active(ctx, blockHeight, 837 w.chainParams, n) 838 if err != nil { 839 return errors.E(op, err) 840 } 841 842 // TODO The behavior of this is not quite right if tons of blocks 843 // are coming in quickly, because the transaction store will end up 844 // out of sync with the voting channel here. This should probably 845 // be fixed somehow, but this should be stable for networks that 846 // are voting at normal block speeds. 847 848 var ticketHashes []*chainhash.Hash 849 var votes []*wire.MsgTx 850 var usedVoteBits []stake.VoteBits 851 defaultVoteBits := w.VoteBits() 852 var watchOutPoints []wire.OutPoint 853 err = walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 854 txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) 855 856 // Only consider tickets owned by this wallet. 857 ticketHashes = selectOwnedTickets(w, dbtx, winningTicketHashes) 858 if len(ticketHashes) == 0 { 859 return nil 860 } 861 862 votes = make([]*wire.MsgTx, len(ticketHashes)) 863 usedVoteBits = make([]stake.VoteBits, len(ticketHashes)) 864 865 addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) 866 867 for i, ticketHash := range ticketHashes { 868 ticketPurchase, err := w.txStore.Tx(txmgrNs, ticketHash) 869 if err != nil && errors.Is(err, errors.NotExist) { 870 ticketPurchase, err = w.stakeMgr.TicketPurchase(dbtx, ticketHash) 871 } 872 if err != nil { 873 log.Errorf("Failed to read ticket purchase transaction for "+ 874 "owned winning ticket %v: %v", ticketHash, err) 875 continue 876 } 877 878 // Don't create votes when this wallet doesn't have voting 879 // authority or the private key to vote. 880 owned, haveKey, err := w.hasVotingAuthority(addrmgrNs, ticketPurchase) 881 if err != nil { 882 return err 883 } 884 if !(owned && haveKey) { 885 continue 886 } 887 888 ticketVoteBits := defaultVoteBits 889 // Check for and use per-ticket votebits if set for this ticket. 890 if tvb, found := w.readDBTicketVoteBits(dbtx, ticketHash); found { 891 ticketVoteBits = tvb 892 } 893 894 // When not on mainnet, randomly disapprove blocks based 895 // on the disapprove percent. 896 dp := w.DisapprovePercent() 897 if dp > 0 { 898 if w.chainParams.Net == wire.MainNet { 899 log.Warnf("block disapprove percent set on mainnet") 900 } else if int64(dp) > randInt63n(100) { 901 log.Infof("Disapproving block %v voted with ticket %v", 902 blockHash, ticketHash) 903 // Set the BlockValid bit to zero, 904 // disapproving the block. 905 const blockIsValidBit = uint16(0x01) 906 ticketVoteBits.Bits &= ^blockIsValidBit 907 } 908 } 909 910 // Deal with treasury votes 911 tspends := w.GetAllTSpends(ctx) 912 913 // Dealwith consensus votes 914 vote, err := createUnsignedVote(ticketHash, ticketPurchase, 915 blockHeight, blockHash, ticketVoteBits, w.subsidyCache, 916 w.chainParams, dcp0010Active, dcp0012Active) 917 if err != nil { 918 log.Errorf("Failed to create vote transaction for ticket "+ 919 "hash %v: %v", ticketHash, err) 920 continue 921 } 922 923 // Iterate over all tpends and determine if they are 924 // within the voting window. 925 tVotes := make([]byte, 0, 256) 926 tVotes = append(tVotes, 'T', 'V') 927 for _, v := range tspends { 928 if !blockchain.InsideTSpendWindow(int64(blockHeight), 929 v.Expiry, w.chainParams.TreasuryVoteInterval, 930 w.chainParams.TreasuryVoteIntervalMultiplier) { 931 continue 932 } 933 934 // Get policy for tspend, falling back to any 935 // policy for the Pi key. 936 tspendHash := v.TxHash() 937 tspendVote := w.TSpendPolicy(&tspendHash, ticketHash) 938 if tspendVote == stake.TreasuryVoteInvalid { 939 continue 940 } 941 942 // Append tspend hash and vote bits 943 tVotes = append(tVotes, tspendHash[:]...) 944 tVotes = append(tVotes, byte(tspendVote)) 945 } 946 if len(tVotes) > 2 { 947 // Vote was appended. Create output and flip 948 // script version. 949 var b txscript.ScriptBuilder 950 b.AddOp(txscript.OP_RETURN) 951 b.AddData(tVotes) 952 tspendVoteScript, err := b.Script() 953 if err != nil { 954 // Log error and continue. 955 log.Errorf("Failed to create treasury "+ 956 "vote for ticket hash %v: %v", 957 ticketHash, err) 958 } else { 959 // Success. 960 vote.AddTxOut(wire.NewTxOut(0, tspendVoteScript)) 961 vote.Version = 3 962 } 963 } 964 965 // Sign vote and sumit. 966 err = w.signVote(addrmgrNs, ticketPurchase, vote) 967 if err != nil { 968 log.Errorf("Failed to sign vote for ticket hash %v: %v", 969 ticketHash, err) 970 continue 971 } 972 votes[i] = vote 973 usedVoteBits[i] = ticketVoteBits 974 975 watchOutPoints = w.appendRelevantOutpoints(watchOutPoints, dbtx, vote) 976 } 977 return nil 978 }) 979 if err != nil { 980 log.Errorf("View failed: %v", errors.E(op, err)) 981 } 982 983 // Remove nil votes without preserving order. 984 for i := 0; i < len(votes); { 985 if votes[i] == nil { 986 votes[i], votes[len(votes)-1] = votes[len(votes)-1], votes[i] 987 votes = votes[:len(votes)-1] 988 continue 989 } 990 i++ 991 } 992 993 voteRecords := make([]*udb.TxRecord, 0, len(votes)) 994 for i := range votes { 995 rec, err := udb.NewTxRecordFromMsgTx(votes[i], time.Now()) 996 if err != nil { 997 log.Errorf("Failed to create transaction record: %v", err) 998 continue 999 } 1000 voteRecords = append(voteRecords, rec) 1001 } 1002 w.recentlyPublishedMu.Lock() 1003 for i := range voteRecords { 1004 w.recentlyPublished[voteRecords[i].Hash] = struct{}{} 1005 1006 log.Infof("Voting on block %v (height %v) using ticket %v "+ 1007 "(vote hash: %v bits: %v)", blockHash, blockHeight, 1008 ticketHashes[i], &voteRecords[i].Hash, usedVoteBits[i].Bits) 1009 } 1010 w.recentlyPublishedMu.Unlock() 1011 1012 // Publish before recording votes in database to slightly reduce latency. 1013 err = n.PublishTransactions(ctx, votes...) 1014 if err != nil { 1015 log.Errorf("Failed to send one or more votes: %v", err) 1016 } 1017 1018 if len(watchOutPoints) > 0 { 1019 err := n.LoadTxFilter(ctx, false, nil, watchOutPoints) 1020 if err != nil { 1021 log.Errorf("Failed to watch outpoints: %v", err) 1022 } 1023 } 1024 1025 // w.lockedOutpointMu is intentionally not locked. 1026 err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error { 1027 for i := range voteRecords { 1028 _, err := w.processTransactionRecord(ctx, dbtx, voteRecords[i], nil, nil) 1029 if err != nil { 1030 return err 1031 } 1032 } 1033 return nil 1034 }) 1035 if err != nil { 1036 return err 1037 } 1038 1039 if n, err := w.NetworkBackend(); err == nil { 1040 _, err := w.watchHDAddrs(ctx, false, n) 1041 if err != nil { 1042 return err 1043 } 1044 } 1045 return nil 1046 } 1047 1048 // RevokeOwnedTickets no longer revokes any tickets since revocations are now 1049 // automatically created per DCP0009. 1050 // 1051 // Deprecated: this method will be removed in the next major version. 1052 func (w *Wallet) RevokeOwnedTickets(ctx context.Context, missedTicketHashes []*chainhash.Hash) error { 1053 return nil 1054 }