github.com/decred/dcrlnd@v0.7.6/chainntnfs/txnotifier.go (about) 1 package chainntnfs 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "sync" 8 "sync/atomic" 9 10 "github.com/decred/dcrd/chaincfg/chainhash" 11 "github.com/decred/dcrd/chaincfg/v3" 12 "github.com/decred/dcrd/dcrutil/v4" 13 "github.com/decred/dcrd/wire" 14 "github.com/decred/dcrlnd/chainscan" 15 "github.com/decred/dcrlnd/channeldb" 16 ) 17 18 const ( 19 // ReorgSafetyLimit is the chain depth beyond which it is assumed a 20 // block will not be reorganized out of the chain. This is used to 21 // determine when to prune old confirmation requests so that reorgs are 22 // handled correctly. The average number of blocks in a day is a 23 // reasonable value to use. 24 ReorgSafetyLimit = 256 25 26 // MaxNumConfs is the maximum number of confirmations that can be 27 // requested on a transaction. 28 MaxNumConfs = ReorgSafetyLimit 29 ) 30 31 var ( 32 // ZeroHash is the value that should be used as the txid when 33 // registering for the confirmation of a script on-chain. This allows 34 // the notifier to match _and_ dispatch upon the inclusion of the script 35 // on-chain, rather than the txid. 36 ZeroHash chainhash.Hash 37 38 // ZeroOutPoint is the value that should be used as the outpoint when 39 // registering for the spend of a script on-chain. This allows the 40 // notifier to match _and_ dispatch upon detecting the spend of the 41 // script on-chain, rather than the outpoint. 42 ZeroOutPoint wire.OutPoint 43 ) 44 45 var ( 46 // ErrTxNotifierExiting is an error returned when attempting to interact 47 // with the TxNotifier but it been shut down. 48 ErrTxNotifierExiting = errors.New("TxNotifier is exiting") 49 50 // ErrNoScript is an error returned when a confirmation/spend 51 // registration is attempted without providing an accompanying output 52 // script. 53 ErrNoScript = errors.New("an output script must be provided") 54 55 // ErrNoHeightHint is an error returned when a confirmation/spend 56 // registration is attempted without providing an accompanying height 57 // hint. 58 ErrNoHeightHint = errors.New("a height hint greater than 0 must be " + 59 "provided") 60 61 // ErrNumConfsOutOfRange is an error returned when a confirmation/spend 62 // registration is attempted and the number of confirmations provided is 63 // out of range. 64 ErrNumConfsOutOfRange = fmt.Errorf("number of confirmations must be "+ 65 "between %d and %d", 1, MaxNumConfs) 66 ) 67 68 // rescanState indicates the progression of a registration before the notifier 69 // can begin dispatching confirmations at tip. 70 type rescanState byte 71 72 const ( 73 // rescanNotStarted is the initial state, denoting that a historical 74 // dispatch may be required. 75 rescanNotStarted rescanState = iota 76 77 // rescanPending indicates that a dispatch has already been made, and we 78 // are waiting for its completion. No other rescans should be dispatched 79 // while in this state. 80 rescanPending 81 82 // rescanComplete signals either that a rescan was dispatched and has 83 // completed, or that we began watching at tip immediately. In either 84 // case, the notifier can only dispatch notifications from tip when in 85 // this state. 86 rescanComplete 87 ) 88 89 // confNtfnSet holds all known, registered confirmation notifications for a 90 // txid/output script. If duplicates notifications are requested, only one 91 // historical dispatch will be spawned to ensure redundant scans are not 92 // permitted. A single conf detail will be constructed and dispatched to all 93 // interested clients. 94 type confNtfnSet struct { 95 // ntfns keeps tracks of all the active client notification requests for 96 // a transaction/output script 97 ntfns map[uint64]*ConfNtfn 98 99 // rescanStatus represents the current rescan state for the 100 // transaction/output script. 101 rescanStatus rescanState 102 103 // details serves as a cache of the confirmation details of a 104 // transaction that we'll use to determine if a transaction/output 105 // script has already confirmed at the time of registration. 106 // details is also used to make sure that in case of an address reuse 107 // (funds sent to a previously confirmed script) no additional 108 // notification is registered which would lead to an inconsistent state. 109 details *TxConfirmation 110 } 111 112 // newConfNtfnSet constructs a fresh confNtfnSet for a group of clients 113 // interested in a notification for a particular txid. 114 func newConfNtfnSet() *confNtfnSet { 115 return &confNtfnSet{ 116 ntfns: make(map[uint64]*ConfNtfn), 117 rescanStatus: rescanNotStarted, 118 } 119 } 120 121 // spendNtfnSet holds all known, registered spend notifications for a spend 122 // request (outpoint/output script). If duplicate notifications are requested, 123 // only one historical dispatch will be spawned to ensure redundant scans are 124 // not permitted. 125 type spendNtfnSet struct { 126 // ntfns keeps tracks of all the active client notification requests for 127 // an outpoint/output script. 128 ntfns map[uint64]*SpendNtfn 129 130 // rescanStatus represents the current rescan state for the spend 131 // request (outpoint/output script). 132 rescanStatus rescanState 133 134 // details serves as a cache of the spend details for an outpoint/output 135 // script that we'll use to determine if it has already been spent at 136 // the time of registration. 137 details *SpendDetail 138 } 139 140 // newSpendNtfnSet constructs a new spend notification set. 141 func newSpendNtfnSet() *spendNtfnSet { 142 return &spendNtfnSet{ 143 ntfns: make(map[uint64]*SpendNtfn), 144 rescanStatus: rescanNotStarted, 145 } 146 } 147 148 // ConfRequest encapsulates a request for a confirmation notification of either 149 // a txid or output script. 150 type ConfRequest struct { 151 // TxID is the hash of the transaction for which confirmation 152 // notifications are requested. If set to a zero hash, then a 153 // confirmation notification will be dispatched upon inclusion of the 154 // _script_, rather than the txid. 155 TxID chainhash.Hash 156 157 // PkScript is the public key script of an outpoint created in this 158 // transaction. 159 PkScript chainscan.PkScript 160 } 161 162 // NewConfRequest creates a request for a confirmation notification of either a 163 // txid or output script. A nil txid or an allocated ZeroHash can be used to 164 // dispatch the confirmation notification on the script. 165 func NewConfRequest(txid *chainhash.Hash, pkScript []byte) (ConfRequest, error) { 166 var r ConfRequest 167 scriptVersion := uint16(0) 168 outputScript, err := chainscan.ParsePkScript(scriptVersion, pkScript) 169 if err != nil { 170 return r, err 171 } 172 173 // We'll only set a txid for which we'll dispatch a confirmation 174 // notification on this request if one was provided. Otherwise, we'll 175 // default to dispatching on the confirmation of the script instead. 176 if txid != nil { 177 r.TxID = *txid 178 } 179 r.PkScript = outputScript 180 181 return r, nil 182 } 183 184 // String returns the string representation of the ConfRequest. 185 func (r ConfRequest) String() string { 186 if r.TxID != ZeroHash { 187 return fmt.Sprintf("txid=%v", r.TxID) 188 } 189 return fmt.Sprintf("script=%v", r.PkScript) 190 } 191 192 // ConfHintKey returns the key that will be used to index the confirmation 193 // request's hint within the height hint cache. 194 func (r ConfRequest) ConfHintKey() ([]byte, error) { 195 if r.TxID == ZeroHash { 196 return r.PkScript.Script(), nil 197 } 198 199 var txid bytes.Buffer 200 if err := channeldb.WriteElement(&txid, r.TxID); err != nil { 201 return nil, err 202 } 203 204 return txid.Bytes(), nil 205 } 206 207 // MatchesTx determines whether the given transaction satisfies the confirmation 208 // request. If the confirmation request is for a script, then we'll check all of 209 // the outputs of the transaction to determine if it matches. Otherwise, we'll 210 // match on the txid. 211 func (r ConfRequest) MatchesTx(tx *wire.MsgTx) bool { 212 scriptMatches := func() bool { 213 pkScript := r.PkScript.Script() 214 for _, txOut := range tx.TxOut { 215 if bytes.Equal(txOut.PkScript, pkScript) { 216 return true 217 } 218 } 219 220 return false 221 } 222 223 if r.TxID != ZeroHash { 224 return r.TxID == tx.TxHash() && scriptMatches() 225 } 226 227 return scriptMatches() 228 } 229 230 // ConfNtfn represents a notifier client's request to receive a notification 231 // once the target transaction/output script gets sufficient confirmations. The 232 // client is asynchronously notified via the ConfirmationEvent channels. 233 type ConfNtfn struct { 234 // ConfID uniquely identifies the confirmation notification request for 235 // the specified transaction/output script. 236 ConfID uint64 237 238 // ConfRequest represents either the txid or script we should detect 239 // inclusion of within the chain. 240 ConfRequest 241 242 // NumConfirmations is the number of confirmations after which the 243 // notification is to be sent. 244 NumConfirmations uint32 245 246 // Event contains references to the channels that the notifications are to 247 // be sent over. 248 Event *ConfirmationEvent 249 250 // HeightHint is the minimum height in the chain that we expect to find 251 // this txid. 252 HeightHint uint32 253 254 // dispatched is false if the confirmed notification has not been sent yet. 255 dispatched bool 256 } 257 258 // HistoricalConfDispatch parameterizes a manual rescan for a particular 259 // transaction/output script. The parameters include the start and end block 260 // heights specifying the range of blocks to scan. 261 type HistoricalConfDispatch struct { 262 // ConfRequest represents either the txid or script we should detect 263 // inclusion of within the chain. 264 ConfRequest 265 266 // StartHeight specifies the block height at which to begin the 267 // historical rescan. 268 StartHeight uint32 269 270 // EndHeight specifies the last block height (inclusive) that the 271 // historical scan should consider. 272 EndHeight uint32 273 } 274 275 // ConfRegistration encompasses all of the information required for callers to 276 // retrieve details about a confirmation event. 277 type ConfRegistration struct { 278 // Event contains references to the channels that the notifications are 279 // to be sent over. 280 Event *ConfirmationEvent 281 282 // HistoricalDispatch, if non-nil, signals to the client who registered 283 // the notification that they are responsible for attempting to manually 284 // rescan blocks for the txid/output script between the start and end 285 // heights. 286 HistoricalDispatch *HistoricalConfDispatch 287 288 // Height is the height of the TxNotifier at the time the confirmation 289 // notification was registered. This can be used so that backends can 290 // request to be notified of confirmations from this point forwards. 291 Height uint32 292 } 293 294 // SpendRequest encapsulates a request for a spend notification of either an 295 // outpoint or output script. 296 type SpendRequest struct { 297 // OutPoint is the outpoint for which a client has requested a spend 298 // notification for. If set to a zero outpoint, then a spend 299 // notification will be dispatched upon detecting the spend of the 300 // _script_, rather than the outpoint. 301 OutPoint wire.OutPoint 302 303 // PkScript is the script of the outpoint. If a zero outpoint is set, 304 // then this can be an arbitrary script. 305 PkScript chainscan.PkScript 306 } 307 308 // NewSpendRequest creates a request for a spend notification of either an 309 // outpoint or output script. A nil outpoint or an allocated ZeroOutPoint can be 310 // used to dispatch the confirmation notification on the script. 311 func NewSpendRequest(op *wire.OutPoint, pkScript []byte) (SpendRequest, error) { 312 var r SpendRequest 313 scriptVersion := uint16(0) 314 outputScript, err := chainscan.ParsePkScript(scriptVersion, pkScript) 315 if err != nil { 316 return r, err 317 } 318 319 // We'll only set an outpoint for which we'll dispatch a spend 320 // notification on this request if one was provided. Otherwise, we'll 321 // default to dispatching on the spend of the script instead. 322 if op != nil { 323 r.OutPoint = *op 324 } 325 r.PkScript = outputScript 326 327 return r, nil 328 } 329 330 // String returns the string representation of the SpendRequest. 331 func (r SpendRequest) String() string { 332 if r.OutPoint != ZeroOutPoint { 333 return fmt.Sprintf("outpoint=%v, script=%v", r.OutPoint, 334 r.PkScript) 335 } 336 return fmt.Sprintf("outpoint=<zero>, script=%v", r.PkScript) 337 } 338 339 // SpendHintKey returns the key that will be used to index the spend request's 340 // hint within the height hint cache. 341 func (r SpendRequest) SpendHintKey() ([]byte, error) { 342 if r.OutPoint == ZeroOutPoint { 343 return r.PkScript.Script(), nil 344 } 345 346 var outpoint bytes.Buffer 347 err := channeldb.WriteElement(&outpoint, r.OutPoint) 348 if err != nil { 349 return nil, err 350 } 351 352 return outpoint.Bytes(), nil 353 } 354 355 // SpendNtfn represents a client's request to receive a notification once an 356 // outpoint/output script has been spent on-chain. The client is asynchronously 357 // notified via the SpendEvent channels. 358 type SpendNtfn struct { 359 // SpendID uniquely identies the spend notification request for the 360 // specified outpoint/output script. 361 SpendID uint64 362 363 // SpendRequest represents either the outpoint or script we should 364 // detect the spend of. 365 SpendRequest 366 367 // Event contains references to the channels that the notifications are 368 // to be sent over. 369 Event *SpendEvent 370 371 // HeightHint is the earliest height in the chain that we expect to find 372 // the spending transaction of the specified outpoint/output script. 373 // This value will be overridden by the spend hint cache if it contains 374 // an entry for it. 375 HeightHint uint32 376 377 // dispatched signals whether a spend notification has been disptached 378 // to the client. 379 dispatched bool 380 } 381 382 // HistoricalSpendDispatch parameterizes a manual rescan to determine the 383 // spending details (if any) of an outpoint/output script. The parameters 384 // include the start and end block heights specifying the range of blocks to 385 // scan. 386 type HistoricalSpendDispatch struct { 387 // SpendRequest represents either the outpoint or script we should 388 // detect the spend of. 389 SpendRequest 390 391 // StartHeight specified the block height at which to begin the 392 // historical rescan. 393 StartHeight uint32 394 395 // EndHeight specifies the last block height (inclusive) that the 396 // historical rescan should consider. 397 EndHeight uint32 398 } 399 400 // SpendRegistration encompasses all of the information required for callers to 401 // retrieve details about a spend event. 402 type SpendRegistration struct { 403 // Event contains references to the channels that the notifications are 404 // to be sent over. 405 Event *SpendEvent 406 407 // HistoricalDispatch, if non-nil, signals to the client who registered 408 // the notification that they are responsible for attempting to manually 409 // rescan blocks for the txid/output script between the start and end 410 // heights. 411 HistoricalDispatch *HistoricalSpendDispatch 412 413 // Height is the height of the TxNotifier at the time the spend 414 // notification was registered. This can be used so that backends can 415 // request to be notified of spends from this point forwards. 416 Height uint32 417 } 418 419 // TxNotifier is a struct responsible for delivering transaction notifications 420 // to subscribers. These notifications can be of two different types: 421 // transaction/output script confirmations and/or outpoint/output script spends. 422 // The TxNotifier will watch the blockchain as new blocks come in, in order to 423 // satisfy its client requests. 424 type TxNotifier struct { 425 confClientCounter uint64 // To be used atomically. 426 spendClientCounter uint64 // To be used atomically. 427 428 // chainParams is the network parameters where the notifier runs. 429 chainParams *chaincfg.Params 430 431 // currentHeight is the height of the tracked blockchain. It is used to 432 // determine the number of confirmations a tx has and ensure blocks are 433 // connected and disconnected in order. 434 currentHeight uint32 435 436 // reorgSafetyLimit is the chain depth beyond which it is assumed a 437 // block will not be reorganized out of the chain. This is used to 438 // determine when to prune old notification requests so that reorgs are 439 // handled correctly. The coinbase maturity period is a reasonable value 440 // to use. 441 reorgSafetyLimit uint32 442 443 // reorgDepth is the depth of a chain organization that this system is 444 // being informed of. This is incremented as long as a sequence of 445 // blocks are disconnected without being interrupted by a new block. 446 reorgDepth uint32 447 448 // confNotifications is an index of confirmation notification requests 449 // by transaction hash/output script. 450 confNotifications map[ConfRequest]*confNtfnSet 451 452 // confsByInitialHeight is an index of watched transactions/output 453 // scripts by the height that they are included at in the chain. This 454 // is tracked so that incorrect notifications are not sent if a 455 // transaction/output script is reorged out of the chain and so that 456 // negative confirmations can be recognized. 457 confsByInitialHeight map[uint32]map[ConfRequest]struct{} 458 459 // ntfnsByConfirmHeight is an index of notification requests by the 460 // height at which the transaction/output script will have sufficient 461 // confirmations. 462 ntfnsByConfirmHeight map[uint32]map[*ConfNtfn]struct{} 463 464 // spendNotifications is an index of all active notification requests 465 // per outpoint/output script. 466 spendNotifications map[SpendRequest]*spendNtfnSet 467 468 // spendsByHeight is an index that keeps tracks of the spending height 469 // of outpoints/output scripts we are currently tracking notifications 470 // for. This is used in order to recover from spending transactions 471 // being reorged out of the chain. 472 spendsByHeight map[uint32]map[SpendRequest]struct{} 473 474 // confirmHintCache is a cache used to maintain the latest height hints 475 // for transactions/output scripts. Each height hint represents the 476 // earliest height at which they scripts could have been confirmed 477 // within the chain. 478 confirmHintCache ConfirmHintCache 479 480 // spendHintCache is a cache used to maintain the latest height hints 481 // for outpoints/output scripts. Each height hint represents the 482 // earliest height at which they could have been spent within the chain. 483 spendHintCache SpendHintCache 484 485 // quit is closed in order to signal that the notifier is gracefully 486 // exiting. 487 quit chan struct{} 488 489 sync.Mutex 490 } 491 492 // NewTxNotifier creates a TxNotifier. The current height of the blockchain is 493 // accepted as a parameter. The different hint caches (confirm and spend) are 494 // used as an optimization in order to retrieve a better starting point when 495 // dispatching a recan for a historical event in the chain. 496 func NewTxNotifier(startHeight uint32, reorgSafetyLimit uint32, 497 confirmHintCache ConfirmHintCache, 498 spendHintCache SpendHintCache, chainParams *chaincfg.Params) *TxNotifier { 499 500 return &TxNotifier{ 501 chainParams: chainParams, 502 currentHeight: startHeight, 503 reorgSafetyLimit: reorgSafetyLimit, 504 confNotifications: make(map[ConfRequest]*confNtfnSet), 505 confsByInitialHeight: make(map[uint32]map[ConfRequest]struct{}), 506 ntfnsByConfirmHeight: make(map[uint32]map[*ConfNtfn]struct{}), 507 spendNotifications: make(map[SpendRequest]*spendNtfnSet), 508 spendsByHeight: make(map[uint32]map[SpendRequest]struct{}), 509 confirmHintCache: confirmHintCache, 510 spendHintCache: spendHintCache, 511 quit: make(chan struct{}), 512 } 513 } 514 515 // newConfNtfn validates all of the parameters required to successfully create 516 // and register a confirmation notification. 517 func (n *TxNotifier) newConfNtfn(txid *chainhash.Hash, 518 pkScript []byte, numConfs, heightHint uint32) (*ConfNtfn, error) { 519 520 // An accompanying output script must always be provided. 521 if len(pkScript) == 0 { 522 return nil, ErrNoScript 523 } 524 525 // Enforce that we will not dispatch confirmations beyond the reorg 526 // safety limit. 527 if numConfs == 0 || numConfs > n.reorgSafetyLimit { 528 return nil, ErrNumConfsOutOfRange 529 } 530 531 // A height hint must be provided to prevent scanning from the genesis 532 // block. 533 if heightHint == 0 { 534 return nil, ErrNoHeightHint 535 } 536 537 // Ensure the output script is of a supported type. 538 confRequest, err := NewConfRequest(txid, pkScript) 539 if err != nil { 540 return nil, err 541 } 542 543 confID := atomic.AddUint64(&n.confClientCounter, 1) 544 return &ConfNtfn{ 545 ConfID: confID, 546 ConfRequest: confRequest, 547 NumConfirmations: numConfs, 548 Event: NewConfirmationEvent(numConfs, func() { 549 n.CancelConf(confRequest, confID) 550 }), 551 HeightHint: heightHint, 552 }, nil 553 } 554 555 // RegisterConf handles a new confirmation notification request. The client will 556 // be notified when the transaction/output script gets a sufficient number of 557 // confirmations in the blockchain. 558 // 559 // NOTE: If the transaction/output script has already been included in a block 560 // on the chain, the confirmation details must be provided with the 561 // UpdateConfDetails method, otherwise we will wait for the transaction/output 562 // script to confirm even though it already has. 563 func (n *TxNotifier) RegisterConf(txid *chainhash.Hash, pkScript []byte, 564 numConfs, heightHint uint32) (*ConfRegistration, error) { 565 566 select { 567 case <-n.quit: 568 return nil, ErrTxNotifierExiting 569 default: 570 } 571 572 // We'll start by performing a series of validation checks. 573 ntfn, err := n.newConfNtfn(txid, pkScript, numConfs, heightHint) 574 if err != nil { 575 return nil, err 576 } 577 578 // Before proceeding to register the notification, we'll query our 579 // height hint cache to determine whether a better one exists. 580 // 581 // TODO(conner): verify that all submitted height hints are identical. 582 startHeight := ntfn.HeightHint 583 hint, err := n.confirmHintCache.QueryConfirmHint(ntfn.ConfRequest) 584 if err == nil { 585 if hint > startHeight { 586 Log.Debugf("Using height hint %d retrieved from cache "+ 587 "for %v instead of %d for conf subscription", 588 hint, ntfn.ConfRequest, startHeight) 589 startHeight = hint 590 } 591 } else if err != ErrConfirmHintNotFound { 592 Log.Errorf("Unable to query confirm hint for %v: %v", 593 ntfn.ConfRequest, err) 594 } 595 596 Log.Infof("New confirmation subscription: conf_id=%d, %v, "+ 597 "num_confs=%v height_hint=%d", ntfn.ConfID, ntfn.ConfRequest, 598 numConfs, startHeight) 599 600 n.Lock() 601 defer n.Unlock() 602 603 confSet, ok := n.confNotifications[ntfn.ConfRequest] 604 if !ok { 605 // If this is the first registration for this request, construct 606 // a confSet to coalesce all notifications for the same request. 607 confSet = newConfNtfnSet() 608 n.confNotifications[ntfn.ConfRequest] = confSet 609 } 610 confSet.ntfns[ntfn.ConfID] = ntfn 611 612 switch confSet.rescanStatus { 613 614 // A prior rescan has already completed and we are actively watching at 615 // tip for this request. 616 case rescanComplete: 617 // If the confirmation details for this set of notifications has 618 // already been found, we'll attempt to deliver them immediately 619 // to this client. 620 Log.Debugf("Attempting to dispatch confirmation for %v on "+ 621 "registration since rescan has finished", 622 ntfn.ConfRequest) 623 624 err := n.dispatchConfDetails(ntfn, confSet.details) 625 if err != nil { 626 return nil, err 627 } 628 629 return &ConfRegistration{ 630 Event: ntfn.Event, 631 HistoricalDispatch: nil, 632 Height: n.currentHeight, 633 }, nil 634 635 // A rescan is already in progress, return here to prevent dispatching 636 // another. When the rescan returns, this notification's details will be 637 // updated as well. 638 case rescanPending: 639 Log.Debugf("Waiting for pending rescan to finish before "+ 640 "notifying %v at tip", ntfn.ConfRequest) 641 642 return &ConfRegistration{ 643 Event: ntfn.Event, 644 HistoricalDispatch: nil, 645 Height: n.currentHeight, 646 }, nil 647 648 // If no rescan has been dispatched, attempt to do so now. 649 case rescanNotStarted: 650 } 651 652 // If the provided or cached height hint indicates that the 653 // transaction with the given txid/output script is to be confirmed at a 654 // height greater than the notifier's current height, we'll refrain from 655 // spawning a historical dispatch. 656 if startHeight > n.currentHeight { 657 Log.Debugf("Height hint is above current height, not "+ 658 "dispatching historical confirmation rescan for %v", 659 ntfn.ConfRequest) 660 661 // Set the rescan status to complete, which will allow the 662 // notifier to start delivering messages for this set 663 // immediately. 664 confSet.rescanStatus = rescanComplete 665 return &ConfRegistration{ 666 Event: ntfn.Event, 667 HistoricalDispatch: nil, 668 Height: n.currentHeight, 669 }, nil 670 } 671 672 Log.Debugf("Dispatching historical confirmation rescan for %v", 673 ntfn.ConfRequest) 674 675 // Construct the parameters for historical dispatch, scanning the range 676 // of blocks between our best known height hint and the notifier's 677 // current height. The notifier will begin also watching for 678 // confirmations at tip starting with the next block. 679 dispatch := &HistoricalConfDispatch{ 680 ConfRequest: ntfn.ConfRequest, 681 StartHeight: startHeight, 682 EndHeight: n.currentHeight, 683 } 684 685 // Set this confSet's status to pending, ensuring subsequent 686 // registrations don't also attempt a dispatch. 687 confSet.rescanStatus = rescanPending 688 689 return &ConfRegistration{ 690 Event: ntfn.Event, 691 HistoricalDispatch: dispatch, 692 Height: n.currentHeight, 693 }, nil 694 } 695 696 // CancelConf cancels an existing request for a spend notification of an 697 // outpoint/output script. The request is identified by its spend ID. 698 func (n *TxNotifier) CancelConf(confRequest ConfRequest, confID uint64) { 699 select { 700 case <-n.quit: 701 return 702 default: 703 } 704 705 n.Lock() 706 defer n.Unlock() 707 708 confSet, ok := n.confNotifications[confRequest] 709 if !ok { 710 return 711 } 712 ntfn, ok := confSet.ntfns[confID] 713 if !ok { 714 return 715 } 716 717 Log.Infof("Canceling confirmation notification: conf_id=%d, %v", confID, 718 confRequest) 719 720 // We'll close all the notification channels to let the client know 721 // their cancel request has been fulfilled. 722 close(ntfn.Event.Confirmed) 723 close(ntfn.Event.Updates) 724 close(ntfn.Event.NegativeConf) 725 726 // Finally, we'll clean up any lingering references to this 727 // notification. 728 delete(confSet.ntfns, confID) 729 730 // Remove the queued confirmation notification if the transaction has 731 // already confirmed, but hasn't met its required number of 732 // confirmations. 733 if confSet.details != nil { 734 confHeight := confSet.details.BlockHeight + 735 ntfn.NumConfirmations - 1 736 delete(n.ntfnsByConfirmHeight[confHeight], ntfn) 737 } 738 } 739 740 // UpdateConfDetails attempts to update the confirmation details for an active 741 // notification within the notifier. This should only be used in the case of a 742 // transaction/output script that has confirmed before the notifier's current 743 // height. 744 // 745 // NOTE: The notification should be registered first to ensure notifications are 746 // dispatched correctly. 747 func (n *TxNotifier) UpdateConfDetails(confRequest ConfRequest, 748 details *TxConfirmation) error { 749 750 select { 751 case <-n.quit: 752 return ErrTxNotifierExiting 753 default: 754 } 755 756 // Ensure we hold the lock throughout handling the notification to 757 // prevent the notifier from advancing its height underneath us. 758 n.Lock() 759 defer n.Unlock() 760 761 // First, we'll determine whether we have an active confirmation 762 // notification for the given txid/script. 763 confSet, ok := n.confNotifications[confRequest] 764 if !ok { 765 return fmt.Errorf("confirmation notification for %v not found", 766 confRequest) 767 } 768 769 // If the confirmation details were already found at tip, all existing 770 // notifications will have been dispatched or queued for dispatch. We 771 // can exit early to avoid sending too many notifications on the 772 // buffered channels. 773 if confSet.details != nil { 774 return nil 775 } 776 777 // The historical dispatch has been completed for this confSet. We'll 778 // update the rescan status and cache any details that were found. If 779 // the details are nil, that implies we did not find them and will 780 // continue to watch for them at tip. 781 confSet.rescanStatus = rescanComplete 782 783 // The notifier has yet to reach the height at which the 784 // transaction/output script was included in a block, so we should defer 785 // until handling it then within ConnectTip. 786 if details == nil { 787 Log.Debugf("Confirmation details for %v not found during "+ 788 "historical dispatch, waiting to dispatch at tip", 789 confRequest) 790 791 // We'll commit the current height as the confirm hint to 792 // prevent another potentially long rescan if we restart before 793 // a new block comes in. 794 err := n.confirmHintCache.CommitConfirmHint( 795 n.currentHeight, confRequest, 796 ) 797 if err != nil { 798 // The error is not fatal as this is an optimistic 799 // optimization, so we'll avoid returning an error. 800 Log.Debugf("Unable to update confirm hint to %d for "+ 801 "%v: %v", n.currentHeight, confRequest, err) 802 } 803 804 return nil 805 } 806 807 if details.BlockHeight > n.currentHeight { 808 Log.Debugf("Confirmation details for %v found above current "+ 809 "height, waiting to dispatch at tip", confRequest) 810 811 return nil 812 } 813 814 Log.Debugf("Updating confirmation details for %v", confRequest) 815 816 err := n.confirmHintCache.CommitConfirmHint( 817 details.BlockHeight, confRequest, 818 ) 819 if err != nil { 820 // The error is not fatal, so we should not return an error to 821 // the caller. 822 Log.Errorf("Unable to update confirm hint to %d for %v: %v", 823 details.BlockHeight, confRequest, err) 824 } 825 826 // Cache the details found in the rescan and attempt to dispatch any 827 // notifications that have not yet been delivered. 828 confSet.details = details 829 for _, ntfn := range confSet.ntfns { 830 err = n.dispatchConfDetails(ntfn, details) 831 if err != nil { 832 return err 833 } 834 } 835 836 return nil 837 } 838 839 // dispatchConfDetails attempts to cache and dispatch details to a particular 840 // client if the transaction/output script has sufficiently confirmed. If the 841 // provided details are nil, this method will be a no-op. 842 func (n *TxNotifier) dispatchConfDetails( 843 ntfn *ConfNtfn, details *TxConfirmation) error { 844 845 // If there are no conf details to dispatch or if the notification has 846 // already been dispatched, then we can skip dispatching to this 847 // client. 848 if details == nil || ntfn.dispatched { 849 Log.Debugf("Skipping dispatch of conf details(%v) for "+ 850 "request %v, dispatched=%v", details, ntfn.ConfRequest, 851 ntfn.dispatched) 852 853 return nil 854 } 855 856 // Now, we'll examine whether the transaction/output script of this 857 // request has reached its required number of confirmations. If it has, 858 // we'll dispatch a confirmation notification to the caller. 859 confHeight := details.BlockHeight + ntfn.NumConfirmations - 1 860 if confHeight <= n.currentHeight { 861 Log.Infof("Dispatching %v confirmation notification for %v", 862 ntfn.NumConfirmations, ntfn.ConfRequest) 863 864 // We'll send a 0 value to the Updates channel, 865 // indicating that the transaction/output script has already 866 // been confirmed. 867 select { 868 case ntfn.Event.Updates <- 0: 869 case <-n.quit: 870 return ErrTxNotifierExiting 871 } 872 873 select { 874 case ntfn.Event.Confirmed <- details: 875 ntfn.dispatched = true 876 case <-n.quit: 877 return ErrTxNotifierExiting 878 } 879 } else { 880 Log.Debugf("Queueing %v confirmation notification for %v at tip ", 881 ntfn.NumConfirmations, ntfn.ConfRequest) 882 883 // Otherwise, we'll keep track of the notification 884 // request by the height at which we should dispatch the 885 // confirmation notification. 886 ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight] 887 if !exists { 888 ntfnSet = make(map[*ConfNtfn]struct{}) 889 n.ntfnsByConfirmHeight[confHeight] = ntfnSet 890 } 891 ntfnSet[ntfn] = struct{}{} 892 893 // We'll also send an update to the client of how many 894 // confirmations are left for the transaction/output script to 895 // be confirmed. 896 numConfsLeft := confHeight - n.currentHeight 897 select { 898 case ntfn.Event.Updates <- numConfsLeft: 899 case <-n.quit: 900 return ErrTxNotifierExiting 901 } 902 } 903 904 // As a final check, we'll also watch the transaction/output script if 905 // it's still possible for it to get reorged out of the chain. 906 reorgSafeHeight := details.BlockHeight + n.reorgSafetyLimit 907 if reorgSafeHeight > n.currentHeight { 908 txSet, exists := n.confsByInitialHeight[details.BlockHeight] 909 if !exists { 910 txSet = make(map[ConfRequest]struct{}) 911 n.confsByInitialHeight[details.BlockHeight] = txSet 912 } 913 txSet[ntfn.ConfRequest] = struct{}{} 914 } 915 916 return nil 917 } 918 919 // newSpendNtfn validates all of the parameters required to successfully create 920 // and register a spend notification. 921 func (n *TxNotifier) newSpendNtfn(outpoint *wire.OutPoint, 922 pkScript []byte, heightHint uint32) (*SpendNtfn, error) { 923 924 // An accompanying output script must always be provided. 925 if len(pkScript) == 0 { 926 return nil, ErrNoScript 927 } 928 929 // A height hint must be provided to prevent scanning from the genesis 930 // block. 931 if heightHint == 0 { 932 return nil, ErrNoHeightHint 933 } 934 935 // Ensure the output script is of a supported type. 936 spendRequest, err := NewSpendRequest(outpoint, pkScript) 937 if err != nil { 938 return nil, err 939 } 940 941 spendID := atomic.AddUint64(&n.spendClientCounter, 1) 942 return &SpendNtfn{ 943 SpendID: spendID, 944 SpendRequest: spendRequest, 945 Event: NewSpendEvent(func() { 946 n.CancelSpend(spendRequest, spendID) 947 }), 948 HeightHint: heightHint, 949 }, nil 950 } 951 952 // RegisterSpend handles a new spend notification request. The client will be 953 // notified once the outpoint/output script is detected as spent within the 954 // chain. 955 // 956 // NOTE: If the outpoint/output script has already been spent within the chain 957 // before the notifier's current tip, the spend details must be provided with 958 // the UpdateSpendDetails method, otherwise we will wait for the outpoint/output 959 // script to be spent at tip, even though it already has. 960 func (n *TxNotifier) RegisterSpend(outpoint *wire.OutPoint, pkScript []byte, 961 heightHint uint32) (*SpendRegistration, error) { 962 963 select { 964 case <-n.quit: 965 return nil, ErrTxNotifierExiting 966 default: 967 } 968 969 // We'll start by performing a series of validation checks. 970 ntfn, err := n.newSpendNtfn(outpoint, pkScript, heightHint) 971 if err != nil { 972 return nil, err 973 } 974 975 // Before proceeding to register the notification, we'll query our spend 976 // hint cache to determine whether a better one exists. 977 startHeight := ntfn.HeightHint 978 hint, err := n.spendHintCache.QuerySpendHint(ntfn.SpendRequest) 979 if err == nil { 980 if hint > startHeight { 981 Log.Debugf("Using height hint %d retrieved from cache "+ 982 "for %v instead of %d for spend subscription", 983 hint, ntfn.SpendRequest, startHeight) 984 startHeight = hint 985 } 986 } else if err != ErrSpendHintNotFound { 987 Log.Errorf("Unable to query spend hint for %v: %v", 988 ntfn.SpendRequest, err) 989 } 990 991 n.Lock() 992 defer n.Unlock() 993 994 Log.Infof("New spend subscription: spend_id=%d, %v, height_hint=%d", 995 ntfn.SpendID, ntfn.SpendRequest, startHeight) 996 997 // Keep track of the notification request so that we can properly 998 // dispatch a spend notification later on. 999 spendSet, ok := n.spendNotifications[ntfn.SpendRequest] 1000 if !ok { 1001 // If this is the first registration for the request, we'll 1002 // construct a spendNtfnSet to coalesce all notifications. 1003 spendSet = newSpendNtfnSet() 1004 n.spendNotifications[ntfn.SpendRequest] = spendSet 1005 } 1006 spendSet.ntfns[ntfn.SpendID] = ntfn 1007 1008 // We'll now let the caller know whether a historical rescan is needed 1009 // depending on the current rescan status. 1010 switch spendSet.rescanStatus { 1011 1012 // If the spending details for this request have already been determined 1013 // and cached, then we can use them to immediately dispatch the spend 1014 // notification to the client. 1015 case rescanComplete: 1016 Log.Debugf("Attempting to dispatch spend for %v on "+ 1017 "registration since rescan has finished", 1018 ntfn.SpendRequest) 1019 1020 err := n.dispatchSpendDetails(ntfn, spendSet.details) 1021 if err != nil { 1022 return nil, err 1023 } 1024 1025 return &SpendRegistration{ 1026 Event: ntfn.Event, 1027 HistoricalDispatch: nil, 1028 Height: n.currentHeight, 1029 }, nil 1030 1031 // If there is an active rescan to determine whether the request has 1032 // been spent, then we won't trigger another one. 1033 case rescanPending: 1034 Log.Debugf("Waiting for pending rescan to finish before "+ 1035 "notifying %v at tip", ntfn.SpendRequest) 1036 1037 return &SpendRegistration{ 1038 Event: ntfn.Event, 1039 HistoricalDispatch: nil, 1040 Height: n.currentHeight, 1041 }, nil 1042 1043 // Otherwise, we'll fall through and let the caller know that a rescan 1044 // should be dispatched to determine whether the request has already 1045 // been spent. 1046 case rescanNotStarted: 1047 } 1048 1049 // However, if the spend hint, either provided by the caller or 1050 // retrieved from the cache, is found to be at a later height than the 1051 // TxNotifier is aware of, then we'll refrain from dispatching a 1052 // historical rescan and wait for the spend to come in at tip. 1053 if startHeight > n.currentHeight { 1054 Log.Debugf("Spend hint of %d for %v is above current height %d", 1055 startHeight, ntfn.SpendRequest, n.currentHeight) 1056 1057 // We'll also set the rescan status as complete to ensure that 1058 // spend hints for this request get updated upon 1059 // connected/disconnected blocks. 1060 spendSet.rescanStatus = rescanComplete 1061 return &SpendRegistration{ 1062 Event: ntfn.Event, 1063 HistoricalDispatch: nil, 1064 Height: n.currentHeight, 1065 }, nil 1066 } 1067 1068 // We'll set the rescan status to pending to ensure subsequent 1069 // notifications don't also attempt a historical dispatch. 1070 spendSet.rescanStatus = rescanPending 1071 1072 Log.Infof("Dispatching historical spend rescan for %v, start=%d, "+ 1073 "end=%d", ntfn.SpendRequest, startHeight, n.currentHeight) 1074 1075 return &SpendRegistration{ 1076 Event: ntfn.Event, 1077 HistoricalDispatch: &HistoricalSpendDispatch{ 1078 SpendRequest: ntfn.SpendRequest, 1079 StartHeight: startHeight, 1080 EndHeight: n.currentHeight, 1081 }, 1082 Height: n.currentHeight, 1083 }, nil 1084 } 1085 1086 // CancelSpend cancels an existing request for a spend notification of an 1087 // outpoint/output script. The request is identified by its spend ID. 1088 func (n *TxNotifier) CancelSpend(spendRequest SpendRequest, spendID uint64) { 1089 select { 1090 case <-n.quit: 1091 return 1092 default: 1093 } 1094 1095 n.Lock() 1096 defer n.Unlock() 1097 1098 spendSet, ok := n.spendNotifications[spendRequest] 1099 if !ok { 1100 return 1101 } 1102 ntfn, ok := spendSet.ntfns[spendID] 1103 if !ok { 1104 return 1105 } 1106 1107 Log.Infof("Canceling spend notification: spend_id=%d, %v", spendID, 1108 spendRequest) 1109 1110 // We'll close all the notification channels to let the client know 1111 // their cancel request has been fulfilled. 1112 close(ntfn.Event.Spend) 1113 close(ntfn.Event.Reorg) 1114 close(ntfn.Event.Done) 1115 delete(spendSet.ntfns, spendID) 1116 } 1117 1118 // ProcessRelevantSpendTx processes a transaction provided externally. This will 1119 // check whether the transaction is relevant to the notifier if it spends any 1120 // outpoints/output scripts for which we currently have registered notifications 1121 // for. If it is relevant, spend notifications will be dispatched to the caller. 1122 func (n *TxNotifier) ProcessRelevantSpendTx(tx *dcrutil.Tx, 1123 blockHeight uint32) error { 1124 1125 select { 1126 case <-n.quit: 1127 return ErrTxNotifierExiting 1128 default: 1129 } 1130 1131 // Ensure we hold the lock throughout handling the notification to 1132 // prevent the notifier from advancing its height underneath us. 1133 n.Lock() 1134 defer n.Unlock() 1135 1136 // We'll use a channel to coalesce all the spend requests that this 1137 // transaction fulfills. 1138 type spend struct { 1139 request *SpendRequest 1140 details *SpendDetail 1141 } 1142 1143 // We'll set up the onSpend filter callback to gather all the fulfilled 1144 // spends requests within this transaction. 1145 var spends []spend 1146 onSpend := func(request SpendRequest, details *SpendDetail) { 1147 spends = append(spends, spend{&request, details}) 1148 } 1149 n.filterTx(tx, nil, blockHeight, nil, onSpend) 1150 1151 // After the transaction has been filtered, we can finally dispatch 1152 // notifications for each request. 1153 for _, spend := range spends { 1154 err := n.updateSpendDetails(*spend.request, spend.details) 1155 if err != nil { 1156 return err 1157 } 1158 } 1159 1160 return nil 1161 } 1162 1163 // UpdateSpendDetails attempts to update the spend details for all active spend 1164 // notification requests for an outpoint/output script. This method should be 1165 // used once a historical scan of the chain has finished. If the historical scan 1166 // did not find a spending transaction for it, the spend details may be nil. 1167 // 1168 // NOTE: A notification request for the outpoint/output script must be 1169 // registered first to ensure notifications are delivered. 1170 func (n *TxNotifier) UpdateSpendDetails(spendRequest SpendRequest, 1171 details *SpendDetail) error { 1172 1173 select { 1174 case <-n.quit: 1175 return ErrTxNotifierExiting 1176 default: 1177 } 1178 1179 // Ensure we hold the lock throughout handling the notification to 1180 // prevent the notifier from advancing its height underneath us. 1181 n.Lock() 1182 defer n.Unlock() 1183 1184 return n.updateSpendDetails(spendRequest, details) 1185 } 1186 1187 // updateSpendDetails attempts to update the spend details for all active spend 1188 // notification requests for an outpoint/output script. This method should be 1189 // used once a historical scan of the chain has finished. If the historical scan 1190 // did not find a spending transaction for it, the spend details may be nil. 1191 // 1192 // NOTE: This method must be called with the TxNotifier's lock held. 1193 func (n *TxNotifier) updateSpendDetails(spendRequest SpendRequest, 1194 details *SpendDetail) error { 1195 1196 // Mark the ongoing historical rescan for this request as finished. This 1197 // will allow us to update the spend hints for it at tip. 1198 spendSet, ok := n.spendNotifications[spendRequest] 1199 if !ok { 1200 return fmt.Errorf("spend notification for %v not found", 1201 spendRequest) 1202 } 1203 1204 // If the spend details have already been found either at tip, then the 1205 // notifications should have already been dispatched, so we can exit 1206 // early to prevent sending duplicate notifications. 1207 if spendSet.details != nil { 1208 return nil 1209 } 1210 1211 // Since the historical rescan has completed for this request, we'll 1212 // mark its rescan status as complete in order to ensure that the 1213 // TxNotifier can properly update its spend hints upon 1214 // connected/disconnected blocks. 1215 spendSet.rescanStatus = rescanComplete 1216 1217 // If the historical rescan was not able to find a spending transaction 1218 // for this request, then we can track the spend at tip. 1219 if details == nil { 1220 // We'll commit the current height as the spend hint to prevent 1221 // another potentially long rescan if we restart before a new 1222 // block comes in. 1223 err := n.spendHintCache.CommitSpendHint( 1224 n.currentHeight, spendRequest, 1225 ) 1226 if err != nil { 1227 // The error is not fatal as this is an optimistic 1228 // optimization, so we'll avoid returning an error. 1229 Log.Debugf("Unable to update spend hint to %d for %v: %v", 1230 n.currentHeight, spendRequest, err) 1231 } 1232 1233 Log.Debugf("Updated spend hint to height=%v for unconfirmed "+ 1234 "spend request %v", n.currentHeight, spendRequest) 1235 return nil 1236 } 1237 1238 // If the historical rescan found the spending transaction for this 1239 // request, but it's at a later height than the notifier (this can 1240 // happen due to latency with the backend during a reorg), then we'll 1241 // defer handling the notification until the notifier has caught up to 1242 // such height. 1243 if uint32(details.SpendingHeight) > n.currentHeight { 1244 return nil 1245 } 1246 1247 // Now that we've determined the request has been spent, we'll commit 1248 // its spending height as its hint in the cache and dispatch 1249 // notifications to all of its respective clients. 1250 err := n.spendHintCache.CommitSpendHint( 1251 uint32(details.SpendingHeight), spendRequest, 1252 ) 1253 if err != nil { 1254 // The error is not fatal as this is an optimistic optimization, 1255 // so we'll avoid returning an error. 1256 Log.Debugf("Unable to update spend hint to %d for %v: %v", 1257 details.SpendingHeight, spendRequest, err) 1258 } 1259 1260 Log.Debugf("Updated spend hint to height=%v for confirmed spend "+ 1261 "request %v", details.SpendingHeight, spendRequest) 1262 1263 spendSet.details = details 1264 for _, ntfn := range spendSet.ntfns { 1265 err := n.dispatchSpendDetails(ntfn, spendSet.details) 1266 if err != nil { 1267 return err 1268 } 1269 } 1270 1271 return nil 1272 } 1273 1274 // dispatchSpendDetails dispatches a spend notification to the client. 1275 // 1276 // NOTE: This must be called with the TxNotifier's lock held. 1277 func (n *TxNotifier) dispatchSpendDetails(ntfn *SpendNtfn, details *SpendDetail) error { 1278 // If there are no spend details to dispatch or if the notification has 1279 // already been dispatched, then we can skip dispatching to this client. 1280 if details == nil || ntfn.dispatched { 1281 Log.Debugf("Skipping dispatch of spend details(%v) for "+ 1282 "request %v, dispatched=%v", details, ntfn.SpendRequest, 1283 ntfn.dispatched) 1284 return nil 1285 } 1286 1287 Log.Infof("Dispatching confirmed spend notification for %v at "+ 1288 "current height=%d: %v", ntfn.SpendRequest, n.currentHeight, 1289 details) 1290 1291 select { 1292 case ntfn.Event.Spend <- details: 1293 ntfn.dispatched = true 1294 case <-n.quit: 1295 return ErrTxNotifierExiting 1296 } 1297 1298 spendHeight := uint32(details.SpendingHeight) 1299 1300 // We also add to spendsByHeight to notify on chain reorgs. 1301 reorgSafeHeight := spendHeight + n.reorgSafetyLimit 1302 if reorgSafeHeight > n.currentHeight { 1303 txSet, exists := n.spendsByHeight[spendHeight] 1304 if !exists { 1305 txSet = make(map[SpendRequest]struct{}) 1306 n.spendsByHeight[spendHeight] = txSet 1307 } 1308 txSet[ntfn.SpendRequest] = struct{}{} 1309 } 1310 1311 return nil 1312 } 1313 1314 // ConnectTip handles a new block extending the current chain. It will go 1315 // through every transaction and determine if it is relevant to any of its 1316 // clients. A transaction can be relevant in either of the following two ways: 1317 // 1318 // 1. One of the inputs in the transaction spends an outpoint/output script 1319 // for which we currently have an active spend registration for. 1320 // 1321 // 2. The transaction has a txid or output script for which we currently have 1322 // an active confirmation registration for. 1323 // 1324 // In the event that the transaction is relevant, a confirmation/spend 1325 // notification will be queued for dispatch to the relevant clients. 1326 // Confirmation notifications will only be dispatched for transactions/output 1327 // scripts that have met the required number of confirmations required by the 1328 // client. 1329 // 1330 // NOTE: In order to actually dispatch the relevant transaction notifications to 1331 // clients, NotifyHeight must be called with the same block height in order to 1332 // maintain correctness. 1333 func (n *TxNotifier) ConnectTip(blockHash *chainhash.Hash, blockHeight uint32, 1334 txns []*dcrutil.Tx) error { 1335 1336 select { 1337 case <-n.quit: 1338 return ErrTxNotifierExiting 1339 default: 1340 } 1341 1342 n.Lock() 1343 defer n.Unlock() 1344 1345 if blockHeight != n.currentHeight+1 { 1346 return fmt.Errorf("received blocks out of order: "+ 1347 "current height=%d, new height=%d", 1348 n.currentHeight, blockHeight) 1349 } 1350 n.currentHeight++ 1351 n.reorgDepth = 0 1352 1353 // First, we'll iterate over all the transactions found in this block to 1354 // determine if it includes any relevant transactions to the TxNotifier. 1355 Log.Debugf("Filtering %d txns for %d spend requests at height %d", 1356 len(txns), len(n.spendNotifications), blockHeight) 1357 for _, tx := range txns { 1358 n.filterTx( 1359 tx, blockHash, blockHeight, n.handleConfDetailsAtTip, 1360 n.handleSpendDetailsAtTip, 1361 ) 1362 } 1363 1364 // Now that we've determined which requests were confirmed and spent 1365 // within the new block, we can update their entries in their respective 1366 // caches, along with all of our unconfirmed and unspent requests. 1367 n.updateHints(blockHeight) 1368 1369 // Finally, we'll clear the entries from our set of notifications for 1370 // requests that are no longer under the risk of being reorged out of 1371 // the chain. 1372 if blockHeight >= n.reorgSafetyLimit { 1373 matureBlockHeight := blockHeight - n.reorgSafetyLimit 1374 for confRequest := range n.confsByInitialHeight[matureBlockHeight] { 1375 confSet := n.confNotifications[confRequest] 1376 for _, ntfn := range confSet.ntfns { 1377 select { 1378 case ntfn.Event.Done <- struct{}{}: 1379 case <-n.quit: 1380 return ErrTxNotifierExiting 1381 } 1382 } 1383 1384 delete(n.confNotifications, confRequest) 1385 } 1386 delete(n.confsByInitialHeight, matureBlockHeight) 1387 1388 for spendRequest := range n.spendsByHeight[matureBlockHeight] { 1389 spendSet := n.spendNotifications[spendRequest] 1390 for _, ntfn := range spendSet.ntfns { 1391 select { 1392 case ntfn.Event.Done <- struct{}{}: 1393 case <-n.quit: 1394 return ErrTxNotifierExiting 1395 } 1396 } 1397 1398 Log.Debugf("Deleting mature spend request %v at "+ 1399 "height=%d", spendRequest, blockHeight) 1400 delete(n.spendNotifications, spendRequest) 1401 } 1402 delete(n.spendsByHeight, matureBlockHeight) 1403 } 1404 1405 return nil 1406 } 1407 1408 // filterTx determines whether the transaction spends or confirms any 1409 // outstanding pending requests. The onConf and onSpend callbacks can be used to 1410 // retrieve all the requests fulfilled by this transaction as they occur. 1411 func (n *TxNotifier) filterTx(tx *dcrutil.Tx, blockHash *chainhash.Hash, 1412 blockHeight uint32, onConf func(ConfRequest, *TxConfirmation), 1413 onSpend func(SpendRequest, *SpendDetail)) { 1414 1415 // In order to determine if this transaction is relevant to the 1416 // notifier, we'll check its inputs for any outstanding spend 1417 // requests. 1418 txHash := tx.Hash() 1419 if onSpend != nil { 1420 // notifyDetails is a helper closure that will construct the 1421 // spend details of a request and hand them off to the onSpend 1422 // callback. 1423 notifyDetails := func(spendRequest SpendRequest, 1424 prevOut wire.OutPoint, inputIdx uint32) { 1425 1426 Log.Debugf("Found spend of %v: spend_tx=%v, "+ 1427 "block_height=%d", spendRequest, txHash, 1428 blockHeight) 1429 1430 onSpend(spendRequest, &SpendDetail{ 1431 SpentOutPoint: &prevOut, 1432 SpenderTxHash: txHash, 1433 SpendingTx: tx.MsgTx(), 1434 SpenderInputIndex: inputIdx, 1435 SpendingHeight: int32(blockHeight), 1436 }) 1437 } 1438 1439 for i, txIn := range tx.MsgTx().TxIn { 1440 // We'll re-derive the script of the output being spent 1441 // to determine if the inputs spends any registered 1442 // requests. 1443 prevOut := txIn.PreviousOutPoint 1444 pkScript, err := chainscan.ComputePkScript( 1445 0, txIn.SignatureScript, 1446 ) 1447 if err != nil { 1448 continue 1449 } 1450 spendRequest := SpendRequest{ 1451 OutPoint: prevOut, 1452 PkScript: pkScript, 1453 } 1454 1455 // If we have any, we'll record their spend height so 1456 // that notifications get dispatched to the respective 1457 // clients. 1458 if _, ok := n.spendNotifications[spendRequest]; ok { 1459 notifyDetails(spendRequest, prevOut, uint32(i)) 1460 } 1461 spendRequest.OutPoint = ZeroOutPoint 1462 if _, ok := n.spendNotifications[spendRequest]; ok { 1463 notifyDetails(spendRequest, prevOut, uint32(i)) 1464 } 1465 } 1466 } 1467 1468 // We'll also check its outputs to determine if there are any 1469 // outstanding confirmation requests. 1470 if onConf != nil { 1471 // notifyDetails is a helper closure that will construct the 1472 // confirmation details of a request and hand them off to the 1473 // onConf callback. 1474 notifyDetails := func(confRequest ConfRequest) { 1475 Log.Debugf("Found initial confirmation of %v: "+ 1476 "height=%d, hash=%v", confRequest, 1477 blockHeight, blockHash) 1478 1479 details := &TxConfirmation{ 1480 Tx: tx.MsgTx(), 1481 BlockHash: blockHash, 1482 BlockHeight: blockHeight, 1483 TxIndex: uint32(tx.Index()), 1484 } 1485 1486 onConf(confRequest, details) 1487 } 1488 1489 for _, txOut := range tx.MsgTx().TxOut { 1490 // We'll parse the script of the output to determine if 1491 // we have any registered requests for it or the 1492 // transaction itself. 1493 pkScript, err := chainscan.ParsePkScript(0, txOut.PkScript) 1494 if err != nil { 1495 continue 1496 } 1497 confRequest := ConfRequest{ 1498 TxID: *txHash, 1499 PkScript: pkScript, 1500 } 1501 1502 // If we have any, we'll record their confirmed height 1503 // so that notifications get dispatched when they 1504 // reaches the clients' desired number of confirmations. 1505 if _, ok := n.confNotifications[confRequest]; ok { 1506 notifyDetails(confRequest) 1507 } 1508 confRequest.TxID = ZeroHash 1509 if _, ok := n.confNotifications[confRequest]; ok { 1510 notifyDetails(confRequest) 1511 } 1512 } 1513 } 1514 } 1515 1516 // handleConfDetailsAtTip tracks the confirmation height of the txid/output 1517 // script in order to properly dispatch a confirmation notification after 1518 // meeting each request's desired number of confirmations for all current and 1519 // future registered clients. 1520 func (n *TxNotifier) handleConfDetailsAtTip(confRequest ConfRequest, 1521 details *TxConfirmation) { 1522 1523 // TODO(wilmer): cancel pending historical rescans if any? 1524 confSet := n.confNotifications[confRequest] 1525 1526 // If we already have details for this request, we don't want to add it 1527 // again since we have already dispatched notifications for it. 1528 if confSet.details != nil { 1529 Log.Warnf("Ignoring address reuse for %s at height %d.", 1530 confRequest, details.BlockHeight) 1531 return 1532 } 1533 1534 confSet.rescanStatus = rescanComplete 1535 confSet.details = details 1536 1537 for _, ntfn := range confSet.ntfns { 1538 // In the event that this notification was aware that the 1539 // transaction/output script was reorged out of the chain, we'll 1540 // consume the reorg notification if it hasn't been done yet 1541 // already. 1542 select { 1543 case <-ntfn.Event.NegativeConf: 1544 default: 1545 } 1546 1547 // We'll note this client's required number of confirmations so 1548 // that we can notify them when expected. 1549 confHeight := details.BlockHeight + ntfn.NumConfirmations - 1 1550 ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight] 1551 if !exists { 1552 ntfnSet = make(map[*ConfNtfn]struct{}) 1553 n.ntfnsByConfirmHeight[confHeight] = ntfnSet 1554 } 1555 ntfnSet[ntfn] = struct{}{} 1556 } 1557 1558 // We'll also note the initial confirmation height in order to correctly 1559 // handle dispatching notifications when the transaction/output script 1560 // gets reorged out of the chain. 1561 txSet, exists := n.confsByInitialHeight[details.BlockHeight] 1562 if !exists { 1563 txSet = make(map[ConfRequest]struct{}) 1564 n.confsByInitialHeight[details.BlockHeight] = txSet 1565 } 1566 txSet[confRequest] = struct{}{} 1567 } 1568 1569 // handleSpendDetailsAtTip tracks the spend height of the outpoint/output script 1570 // in order to properly dispatch a spend notification for all current and future 1571 // registered clients. 1572 func (n *TxNotifier) handleSpendDetailsAtTip(spendRequest SpendRequest, 1573 details *SpendDetail) { 1574 1575 // TODO(wilmer): cancel pending historical rescans if any? 1576 spendSet := n.spendNotifications[spendRequest] 1577 spendSet.rescanStatus = rescanComplete 1578 spendSet.details = details 1579 1580 for _, ntfn := range spendSet.ntfns { 1581 // In the event that this notification was aware that the 1582 // spending transaction of its outpoint/output script was 1583 // reorged out of the chain, we'll consume the reorg 1584 // notification if it hasn't been done yet already. 1585 select { 1586 case <-ntfn.Event.Reorg: 1587 default: 1588 } 1589 } 1590 1591 // We'll note the spending height of the request in order to correctly 1592 // handle dispatching notifications when the spending transactions gets 1593 // reorged out of the chain. 1594 spendHeight := uint32(details.SpendingHeight) 1595 opSet, exists := n.spendsByHeight[spendHeight] 1596 if !exists { 1597 opSet = make(map[SpendRequest]struct{}) 1598 n.spendsByHeight[spendHeight] = opSet 1599 } 1600 opSet[spendRequest] = struct{}{} 1601 1602 Log.Debugf("Spend request %v spent at tip=%d", spendRequest, 1603 spendHeight) 1604 } 1605 1606 // NotifyHeight dispatches confirmation and spend notifications to the clients 1607 // who registered for a notification which has been fulfilled at the passed 1608 // height. 1609 func (n *TxNotifier) NotifyHeight(height uint32) error { 1610 n.Lock() 1611 defer n.Unlock() 1612 1613 // First, we'll dispatch an update to all of the notification clients 1614 // for our watched requests with the number of confirmations left at 1615 // this new height. 1616 for _, confRequests := range n.confsByInitialHeight { 1617 for confRequest := range confRequests { 1618 confSet := n.confNotifications[confRequest] 1619 for _, ntfn := range confSet.ntfns { 1620 txConfHeight := confSet.details.BlockHeight + 1621 ntfn.NumConfirmations - 1 1622 numConfsLeft := txConfHeight - height 1623 1624 // Since we don't clear notifications until 1625 // transactions/output scripts are no longer 1626 // under the risk of being reorganized out of 1627 // the chain, we'll skip sending updates for 1628 // those that have already been confirmed. 1629 if int32(numConfsLeft) < 0 { 1630 continue 1631 } 1632 1633 // ConnectTip() and NotifyHeight are not atomic 1634 // and may race with historical searches, if a 1635 // search is slow. So it could happen that the 1636 // sema conf is found during the historical 1637 // search and the tip. Therefore, prevent sending 1638 // the updated if the conf has already been sent 1639 // or there are pending updates to be fetched. 1640 // This is triggered during the 1641 // historical_conf_dispatch_with_script_dispatch 1642 // test for the chainscan driver. 1643 if ntfn.dispatched || len(ntfn.Event.Updates) == cap(ntfn.Event.Updates) { 1644 continue 1645 } 1646 1647 select { 1648 case ntfn.Event.Updates <- numConfsLeft: 1649 case <-n.quit: 1650 return ErrTxNotifierExiting 1651 } 1652 } 1653 } 1654 } 1655 1656 // Then, we'll dispatch notifications for all the requests that have 1657 // become confirmed at this new block height. 1658 for ntfn := range n.ntfnsByConfirmHeight[height] { 1659 confSet := n.confNotifications[ntfn.ConfRequest] 1660 1661 Log.Infof("Dispatching %v confirmation notification for %v", 1662 ntfn.NumConfirmations, ntfn.ConfRequest) 1663 1664 select { 1665 case ntfn.Event.Confirmed <- confSet.details: 1666 ntfn.dispatched = true 1667 case <-n.quit: 1668 return ErrTxNotifierExiting 1669 } 1670 } 1671 delete(n.ntfnsByConfirmHeight, height) 1672 1673 // Finally, we'll dispatch spend notifications for all the requests that 1674 // were spent at this new block height. 1675 for spendRequest := range n.spendsByHeight[height] { 1676 spendSet := n.spendNotifications[spendRequest] 1677 for _, ntfn := range spendSet.ntfns { 1678 err := n.dispatchSpendDetails(ntfn, spendSet.details) 1679 if err != nil { 1680 return err 1681 } 1682 } 1683 } 1684 1685 return nil 1686 } 1687 1688 // DisconnectTip handles the tip of the current chain being disconnected during 1689 // a chain reorganization. If any watched requests were included in this block, 1690 // internal structures are updated to ensure confirmation/spend notifications 1691 // are consumed (if not already), and reorg notifications are dispatched 1692 // instead. Confirmation/spend notifications will be dispatched again upon block 1693 // inclusion. 1694 func (n *TxNotifier) DisconnectTip(blockHeight uint32) error { 1695 select { 1696 case <-n.quit: 1697 return ErrTxNotifierExiting 1698 default: 1699 } 1700 1701 n.Lock() 1702 defer n.Unlock() 1703 1704 if blockHeight != n.currentHeight { 1705 return fmt.Errorf("received blocks out of order: "+ 1706 "current height=%d, disconnected height=%d", 1707 n.currentHeight, blockHeight) 1708 } 1709 n.currentHeight-- 1710 n.reorgDepth++ 1711 1712 // With the block disconnected, we'll update the confirm and spend hints 1713 // for our notification requests to reflect the new height, except for 1714 // those that have confirmed/spent at previous heights. 1715 n.updateHints(blockHeight) 1716 1717 // We'll go through all of our watched confirmation requests and attempt 1718 // to drain their notification channels to ensure sending notifications 1719 // to the clients is always non-blocking. 1720 for initialHeight, txHashes := range n.confsByInitialHeight { 1721 for txHash := range txHashes { 1722 // If the transaction/output script has been reorged out 1723 // of the chain, we'll make sure to remove the cached 1724 // confirmation details to prevent notifying clients 1725 // with old information. 1726 confSet := n.confNotifications[txHash] 1727 if initialHeight == blockHeight { 1728 confSet.details = nil 1729 } 1730 1731 for _, ntfn := range confSet.ntfns { 1732 // First, we'll attempt to drain an update 1733 // from each notification to ensure sends to the 1734 // Updates channel are always non-blocking. 1735 select { 1736 case <-ntfn.Event.Updates: 1737 case <-n.quit: 1738 return ErrTxNotifierExiting 1739 default: 1740 } 1741 1742 // Then, we'll check if the current 1743 // transaction/output script was included in the 1744 // block currently being disconnected. If it 1745 // was, we'll need to dispatch a reorg 1746 // notification to the client. 1747 if initialHeight == blockHeight { 1748 err := n.dispatchConfReorg( 1749 ntfn, blockHeight, 1750 ) 1751 if err != nil { 1752 return err 1753 } 1754 } 1755 } 1756 } 1757 } 1758 1759 // We'll also go through our watched spend requests and attempt to drain 1760 // their dispatched notifications to ensure dispatching notifications to 1761 // clients later on is always non-blocking. We're only interested in 1762 // requests whose spending transaction was included at the height being 1763 // disconnected. 1764 for op := range n.spendsByHeight[blockHeight] { 1765 // Since the spending transaction is being reorged out of the 1766 // chain, we'll need to clear out the spending details of the 1767 // request. 1768 spendSet := n.spendNotifications[op] 1769 spendSet.details = nil 1770 1771 // For all requests which have had a spend notification 1772 // dispatched, we'll attempt to drain it and send a reorg 1773 // notification instead. 1774 for _, ntfn := range spendSet.ntfns { 1775 if err := n.dispatchSpendReorg(ntfn); err != nil { 1776 return err 1777 } 1778 } 1779 } 1780 1781 // Finally, we can remove the requests that were confirmed and/or spent 1782 // at the height being disconnected. We'll still continue to track them 1783 // until they have been confirmed/spent and are no longer under the risk 1784 // of being reorged out of the chain again. 1785 delete(n.confsByInitialHeight, blockHeight) 1786 delete(n.spendsByHeight, blockHeight) 1787 1788 return nil 1789 } 1790 1791 // updateHints attempts to update the confirm and spend hints for all relevant 1792 // requests respectively. The height parameter is used to determine which 1793 // requests we should update based on whether a new block is being 1794 // connected/disconnected. 1795 // 1796 // NOTE: This must be called with the TxNotifier's lock held and after its 1797 // height has already been reflected by a block being connected/disconnected. 1798 func (n *TxNotifier) updateHints(height uint32) { 1799 // TODO(wilmer): update under one database transaction. 1800 // 1801 // To update the height hint for all the required confirmation requests 1802 // under one database transaction, we'll gather the set of unconfirmed 1803 // requests along with the ones that confirmed at the height being 1804 // connected/disconnected. 1805 confRequests := n.unconfirmedRequests() 1806 for confRequest := range n.confsByInitialHeight[height] { 1807 confRequests = append(confRequests, confRequest) 1808 } 1809 err := n.confirmHintCache.CommitConfirmHint( 1810 n.currentHeight, confRequests..., 1811 ) 1812 if err != nil { 1813 // The error is not fatal as this is an optimistic optimization, 1814 // so we'll avoid returning an error. 1815 Log.Debugf("Unable to update confirm hints to %d for "+ 1816 "%v: %v", n.currentHeight, confRequests, err) 1817 } 1818 1819 // Similarly, to update the height hint for all the required spend 1820 // requests under one database transaction, we'll gather the set of 1821 // unspent requests along with the ones that were spent at the height 1822 // being connected/disconnected. 1823 spendRequests := n.unspentRequests() 1824 for spendRequest := range n.spendsByHeight[height] { 1825 spendRequests = append(spendRequests, spendRequest) 1826 } 1827 err = n.spendHintCache.CommitSpendHint(n.currentHeight, spendRequests...) 1828 if err != nil { 1829 // The error is not fatal as this is an optimistic optimization, 1830 // so we'll avoid returning an error. 1831 Log.Debugf("Unable to update spend hints to %d for "+ 1832 "%v: %v", n.currentHeight, spendRequests, err) 1833 } 1834 } 1835 1836 // unconfirmedRequests returns the set of confirmation requests that are 1837 // still seen as unconfirmed by the TxNotifier. 1838 // 1839 // NOTE: This method must be called with the TxNotifier's lock held. 1840 func (n *TxNotifier) unconfirmedRequests() []ConfRequest { 1841 var unconfirmed []ConfRequest 1842 for confRequest, confNtfnSet := range n.confNotifications { 1843 // If the notification is already aware of its confirmation 1844 // details, or it's in the process of learning them, we'll skip 1845 // it as we can't yet determine if it's confirmed or not. 1846 if confNtfnSet.rescanStatus != rescanComplete || 1847 confNtfnSet.details != nil { 1848 continue 1849 } 1850 1851 unconfirmed = append(unconfirmed, confRequest) 1852 } 1853 1854 return unconfirmed 1855 } 1856 1857 // unspentRequests returns the set of spend requests that are still seen as 1858 // unspent by the TxNotifier. 1859 // 1860 // NOTE: This method must be called with the TxNotifier's lock held. 1861 func (n *TxNotifier) unspentRequests() []SpendRequest { 1862 var unspent []SpendRequest 1863 for spendRequest, spendNtfnSet := range n.spendNotifications { 1864 // If the notification is already aware of its spend details, or 1865 // it's in the process of learning them, we'll skip it as we 1866 // can't yet determine if it's unspent or not. 1867 if spendNtfnSet.rescanStatus != rescanComplete || 1868 spendNtfnSet.details != nil { 1869 continue 1870 } 1871 1872 unspent = append(unspent, spendRequest) 1873 } 1874 1875 return unspent 1876 } 1877 1878 // dispatchConfReorg dispatches a reorg notification to the client if the 1879 // confirmation notification was already delivered. 1880 // 1881 // NOTE: This must be called with the TxNotifier's lock held. 1882 func (n *TxNotifier) dispatchConfReorg(ntfn *ConfNtfn, 1883 heightDisconnected uint32) error { 1884 1885 // If the request's confirmation notification has yet to be dispatched, 1886 // we'll need to clear its entry within the ntfnsByConfirmHeight index 1887 // to prevent from notifying the client once the notifier reaches the 1888 // confirmation height. 1889 if !ntfn.dispatched { 1890 confHeight := heightDisconnected + ntfn.NumConfirmations - 1 1891 ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight] 1892 if exists { 1893 delete(ntfnSet, ntfn) 1894 } 1895 return nil 1896 } 1897 1898 // Otherwise, the entry within the ntfnsByConfirmHeight has already been 1899 // deleted, so we'll attempt to drain the confirmation notification to 1900 // ensure sends to the Confirmed channel are always non-blocking. 1901 select { 1902 case <-ntfn.Event.Confirmed: 1903 case <-n.quit: 1904 return ErrTxNotifierExiting 1905 default: 1906 } 1907 1908 ntfn.dispatched = false 1909 1910 // Send a negative confirmation notification to the client indicating 1911 // how many blocks have been disconnected successively. 1912 select { 1913 case ntfn.Event.NegativeConf <- int32(n.reorgDepth): 1914 case <-n.quit: 1915 return ErrTxNotifierExiting 1916 } 1917 1918 return nil 1919 } 1920 1921 // dispatchSpendReorg dispatches a reorg notification to the client if a spend 1922 // notiification was already delivered. 1923 // 1924 // NOTE: This must be called with the TxNotifier's lock held. 1925 func (n *TxNotifier) dispatchSpendReorg(ntfn *SpendNtfn) error { 1926 if !ntfn.dispatched { 1927 return nil 1928 } 1929 1930 // Attempt to drain the spend notification to ensure sends to the Spend 1931 // channel are always non-blocking. 1932 select { 1933 case <-ntfn.Event.Spend: 1934 default: 1935 } 1936 1937 // Send a reorg notification to the client in order for them to 1938 // correctly handle reorgs. 1939 select { 1940 case ntfn.Event.Reorg <- struct{}{}: 1941 case <-n.quit: 1942 return ErrTxNotifierExiting 1943 } 1944 1945 ntfn.dispatched = false 1946 1947 return nil 1948 } 1949 1950 // TearDown is to be called when the owner of the TxNotifier is exiting. This 1951 // closes the event channels of all registered notifications that have not been 1952 // dispatched yet. 1953 func (n *TxNotifier) TearDown() { 1954 close(n.quit) 1955 1956 n.Lock() 1957 defer n.Unlock() 1958 1959 for _, confSet := range n.confNotifications { 1960 for confID, ntfn := range confSet.ntfns { 1961 close(ntfn.Event.Confirmed) 1962 close(ntfn.Event.Updates) 1963 close(ntfn.Event.NegativeConf) 1964 close(ntfn.Event.Done) 1965 delete(confSet.ntfns, confID) 1966 } 1967 } 1968 1969 for _, spendSet := range n.spendNotifications { 1970 for spendID, ntfn := range spendSet.ntfns { 1971 close(ntfn.Event.Spend) 1972 close(ntfn.Event.Reorg) 1973 close(ntfn.Event.Done) 1974 delete(spendSet.ntfns, spendID) 1975 } 1976 } 1977 }