github.com/decred/dcrlnd@v0.7.6/chainntnfs/interface.go (about) 1 package chainntnfs 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "errors" 7 "fmt" 8 "strings" 9 "sync" 10 11 "github.com/decred/dcrd/chaincfg/chainhash" 12 "github.com/decred/dcrd/dcrjson/v4" 13 jsontypes "github.com/decred/dcrd/rpc/jsonrpc/types/v4" 14 "github.com/decred/dcrd/wire" 15 ) 16 17 var ( 18 // ErrChainNotifierShuttingDown is used when we are trying to 19 // measure a spend notification when notifier is already stopped. 20 ErrChainNotifierShuttingDown = errors.New("chain notifier shutting down") 21 ) 22 23 // TxConfStatus denotes the status of a transaction's lookup. 24 type TxConfStatus uint8 25 26 const ( 27 // TxFoundMempool denotes that the transaction was found within the 28 // backend node's mempool. 29 TxFoundMempool TxConfStatus = iota 30 31 // TxFoundIndex denotes that the transaction was found within the 32 // backend node's txindex. 33 TxFoundIndex 34 35 // TxNotFoundIndex denotes that the transaction was not found within the 36 // backend node's txindex. 37 TxNotFoundIndex 38 39 // TxFoundManually denotes that the transaction was found within the 40 // chain by scanning for it manually. 41 TxFoundManually 42 43 // TxNotFoundManually denotes that the transaction was not found within 44 // the chain by scanning for it manually. 45 TxNotFoundManually 46 ) 47 48 // String returns the string representation of the TxConfStatus. 49 func (t TxConfStatus) String() string { 50 switch t { 51 case TxFoundMempool: 52 return "TxFoundMempool" 53 54 case TxFoundIndex: 55 return "TxFoundIndex" 56 57 case TxNotFoundIndex: 58 return "TxNotFoundIndex" 59 60 case TxFoundManually: 61 return "TxFoundManually" 62 63 case TxNotFoundManually: 64 return "TxNotFoundManually" 65 66 default: 67 return "unknown" 68 } 69 } 70 71 // ChainNotifier represents a trusted source to receive notifications concerning 72 // targeted events on the Decred blockchain. The interface specification is 73 // intentionally general in order to support a wide array of chain notification 74 // implementations such as: dcrd's websockets notifications, various Decred API 75 // services, Electrum servers, etc. 76 // 77 // Concrete implementations of ChainNotifier should be able to support multiple 78 // concurrent client requests, as well as multiple concurrent notification 79 // events. 80 type ChainNotifier interface { 81 // RegisterConfirmationsNtfn registers an intent to be notified once 82 // txid reaches numConfs confirmations. We also pass in the pkScript as 83 // the default light client instead needs to match on scripts created in 84 // the block. If a nil txid is passed in, then not only should we match 85 // on the script, but we should also dispatch once the transaction 86 // containing the script reaches numConfs confirmations. This can be 87 // useful in instances where we only know the script in advance, but not 88 // the transaction containing it. 89 // 90 // The returned ConfirmationEvent should properly notify the client once 91 // the specified number of confirmations has been reached for the txid, 92 // as well as if the original tx gets re-org'd out of the mainchain. The 93 // heightHint parameter is provided as a convenience to light clients. 94 // It heightHint denotes the earliest height in the blockchain in which 95 // the target txid _could_ have been included in the chain. This can be 96 // used to bound the search space when checking to see if a notification 97 // can immediately be dispatched due to historical data. 98 // 99 // NOTE: Dispatching notifications to multiple clients subscribed to 100 // the same (txid, numConfs) tuple MUST be supported. 101 RegisterConfirmationsNtfn(txid *chainhash.Hash, pkScript []byte, 102 numConfs, heightHint uint32) (*ConfirmationEvent, error) 103 104 // RegisterSpendNtfn registers an intent to be notified once the target 105 // outpoint is successfully spent within a transaction. The script that 106 // the outpoint creates must also be specified. This allows this 107 // interface to be implemented by BIP 158-like filtering. If a nil 108 // outpoint is passed in, then not only should we match on the script, 109 // but we should also dispatch once a transaction spends the output 110 // containing said script. This can be useful in instances where we only 111 // know the script in advance, but not the outpoint itself. 112 // 113 // The returned SpendEvent will receive a send on the 'Spend' 114 // transaction once a transaction spending the input is detected on the 115 // blockchain. The heightHint parameter is provided as a convenience to 116 // light clients. It denotes the earliest height in the blockchain in 117 // which the target output could have been spent. 118 // 119 // NOTE: The notification should only be triggered when the spending 120 // transaction receives a single confirmation. 121 // 122 // NOTE: Dispatching notifications to multiple clients subscribed to a 123 // spend of the same outpoint MUST be supported. 124 RegisterSpendNtfn(outpoint *wire.OutPoint, pkScript []byte, 125 heightHint uint32) (*SpendEvent, error) 126 127 // RegisterBlockEpochNtfn registers an intent to be notified of each 128 // new block connected to the tip of the main chain. The returned 129 // BlockEpochEvent struct contains a channel which will be sent upon 130 // for each new block discovered. 131 // 132 // Clients have the option of passing in their best known block. 133 // If they specify a block, the ChainNotifier checks whether the client 134 // is behind on blocks. If they are, the ChainNotifier sends a backlog 135 // of block notifications for the missed blocks. If they do not provide 136 // one, then a notification will be dispatched immediately for the 137 // current tip of the chain upon a successful registration. 138 RegisterBlockEpochNtfn(*BlockEpoch) (*BlockEpochEvent, error) 139 140 // Start the ChainNotifier. Once started, the implementation should be 141 // ready, and able to receive notification registrations from clients. 142 Start() error 143 144 // Started returns true if this instance has been started, and false otherwise. 145 Started() bool 146 147 // Stops the concrete ChainNotifier. Once stopped, the ChainNotifier 148 // should disallow any future requests from potential clients. 149 // Additionally, all pending client notifications will be canceled 150 // by closing the related channels on the *Event's. 151 Stop() error 152 } 153 154 // TxConfirmation carries some additional block-level details of the exact 155 // block that specified transactions was confirmed within. 156 type TxConfirmation struct { 157 // BlockHash is the hash of the block that confirmed the original 158 // transition. 159 BlockHash *chainhash.Hash 160 161 // BlockHeight is the height of the block in which the transaction was 162 // confirmed within. 163 BlockHeight uint32 164 165 // TxIndex is the index within the block of the ultimate confirmed 166 // transaction. 167 TxIndex uint32 168 169 // Tx is the transaction for which the notification was requested for. 170 Tx *wire.MsgTx 171 } 172 173 // ConfirmationEvent encapsulates a confirmation notification. With this struct, 174 // callers can be notified of: the instance the target txid reaches the targeted 175 // number of confirmations, how many confirmations are left for the target txid 176 // to be fully confirmed at every new block height, and also in the event that 177 // the original txid becomes disconnected from the blockchain as a result of a 178 // re-org. 179 // 180 // Once the txid reaches the specified number of confirmations, the 'Confirmed' 181 // channel will be sent upon fulfilling the notification. 182 // 183 // If the event that the original transaction becomes re-org'd out of the main 184 // chain, the 'NegativeConf' will be sent upon with a value representing the 185 // depth of the re-org. 186 // 187 // NOTE: If the caller wishes to cancel their registered spend notification, 188 // the Cancel closure MUST be called. 189 type ConfirmationEvent struct { 190 // Confirmed is a channel that will be sent upon once the transaction 191 // has been fully confirmed. The struct sent will contain all the 192 // details of the channel's confirmation. 193 // 194 // NOTE: This channel must be buffered. 195 Confirmed chan *TxConfirmation 196 197 // Updates is a channel that will sent upon, at every incremental 198 // confirmation, how many confirmations are left to declare the 199 // transaction as fully confirmed. 200 // 201 // NOTE: This channel must be buffered with the number of required 202 // confirmations. 203 Updates chan uint32 204 205 // NegativeConf is a channel that will be sent upon if the transaction 206 // confirms, but is later reorged out of the chain. The integer sent 207 // through the channel represents the reorg depth. 208 // 209 // NOTE: This channel must be buffered. 210 NegativeConf chan int32 211 212 // Done is a channel that gets sent upon once the confirmation request 213 // is no longer under the risk of being reorged out of the chain. 214 // 215 // NOTE: This channel must be buffered. 216 Done chan struct{} 217 218 // Cancel is a closure that should be executed by the caller in the case 219 // that they wish to prematurely abandon their registered confirmation 220 // notification. 221 Cancel func() 222 } 223 224 // NewConfirmationEvent constructs a new ConfirmationEvent with newly opened 225 // channels. 226 func NewConfirmationEvent(numConfs uint32, cancel func()) *ConfirmationEvent { 227 return &ConfirmationEvent{ 228 Confirmed: make(chan *TxConfirmation, 1), 229 Updates: make(chan uint32, numConfs), 230 NegativeConf: make(chan int32, 1), 231 Done: make(chan struct{}, 1), 232 Cancel: cancel, 233 } 234 } 235 236 // SpendDetail contains details pertaining to a spent output. This struct itself 237 // is the spentness notification. It includes the original outpoint which 238 // triggered the notification, the hash of the transaction spending the output, 239 // the spending transaction itself, the height of the spending transaction, and 240 // finally the input index which spent the target output. 241 type SpendDetail struct { 242 SpentOutPoint *wire.OutPoint 243 SpenderTxHash *chainhash.Hash 244 SpendingTx *wire.MsgTx 245 SpenderInputIndex uint32 246 SpendingHeight int32 247 } 248 249 // String returns a string representation of SpendDetail. 250 func (s *SpendDetail) String() string { 251 return fmt.Sprintf("%v[%d] spending %v at height=%v", s.SpenderTxHash, 252 s.SpenderInputIndex, s.SpentOutPoint, s.SpendingHeight) 253 } 254 255 // SpendEvent encapsulates a spentness notification. Its only field 'Spend' will 256 // be sent upon once the target output passed into RegisterSpendNtfn has been 257 // spent on the blockchain. 258 // 259 // NOTE: If the caller wishes to cancel their registered spend notification, 260 // the Cancel closure MUST be called. 261 type SpendEvent struct { 262 // Spend is a receive only channel which will be sent upon once the 263 // target outpoint has been spent. 264 // 265 // NOTE: This channel must be buffered. 266 Spend chan *SpendDetail 267 268 // Reorg is a channel that will be sent upon once we detect the spending 269 // transaction of the outpoint in question has been reorged out of the 270 // chain. 271 // 272 // NOTE: This channel must be buffered. 273 Reorg chan struct{} 274 275 // Done is a channel that gets sent upon once the confirmation request 276 // is no longer under the risk of being reorged out of the chain. 277 // 278 // NOTE: This channel must be buffered. 279 Done chan struct{} 280 281 // Cancel is a closure that should be executed by the caller in the case 282 // that they wish to prematurely abandon their registered spend 283 // notification. 284 Cancel func() 285 } 286 287 // NewSpendEvent constructs a new SpendEvent with newly opened channels. 288 func NewSpendEvent(cancel func()) *SpendEvent { 289 return &SpendEvent{ 290 Spend: make(chan *SpendDetail, 1), 291 Reorg: make(chan struct{}, 1), 292 Done: make(chan struct{}, 1), 293 Cancel: cancel, 294 } 295 } 296 297 // BlockEpoch represents metadata concerning each new block connected to the 298 // main chain. 299 type BlockEpoch struct { 300 // Hash is the block hash of the latest block to be added to the tip of 301 // the main chain. 302 Hash *chainhash.Hash 303 304 // Height is the height of the latest block to be added to the tip of 305 // the main chain. 306 Height int32 307 308 // BlockHeader is the block header of this new height. 309 BlockHeader *wire.BlockHeader 310 } 311 312 // BlockEpochEvent encapsulates an on-going stream of block epoch 313 // notifications. Its only field 'Epochs' will be sent upon for each new block 314 // connected to the main-chain. 315 // 316 // NOTE: If the caller wishes to cancel their registered block epoch 317 // notification, the Cancel closure MUST be called. 318 type BlockEpochEvent struct { 319 // Epochs is a receive only channel that will be sent upon each time a 320 // new block is connected to the end of the main chain. 321 // 322 // NOTE: This channel must be buffered. 323 Epochs <-chan *BlockEpoch 324 325 // Cancel is a closure that should be executed by the caller in the case 326 // that they wish to abandon their registered block epochs notification. 327 Cancel func() 328 } 329 330 // NotifierDriver represents a "driver" for a particular interface. A driver is 331 // identified by a globally unique string identifier along with a 'New()' 332 // method which is responsible for initializing a particular ChainNotifier 333 // concrete implementation. 334 type NotifierDriver struct { 335 // NotifierType is a string which uniquely identifies the ChainNotifier 336 // that this driver, drives. 337 NotifierType string 338 339 // New creates a new instance of a concrete ChainNotifier 340 // implementation given a variadic set up arguments. The function takes 341 // a variadic number of interface parameters in order to provide 342 // initialization flexibility, thereby accommodating several potential 343 // ChainNotifier implementations. 344 New func(args ...interface{}) (ChainNotifier, error) 345 } 346 347 var ( 348 notifiers = make(map[string]*NotifierDriver) 349 registerMtx sync.Mutex 350 ) 351 352 // RegisteredNotifiers returns a slice of all currently registered notifiers. 353 // 354 // NOTE: This function is safe for concurrent access. 355 func RegisteredNotifiers() []*NotifierDriver { 356 registerMtx.Lock() 357 defer registerMtx.Unlock() 358 359 drivers := make([]*NotifierDriver, 0, len(notifiers)) 360 for _, driver := range notifiers { 361 drivers = append(drivers, driver) 362 } 363 364 return drivers 365 } 366 367 // RegisterNotifier registers a NotifierDriver which is capable of driving a 368 // concrete ChainNotifier interface. In the case that this driver has already 369 // been registered, an error is returned. 370 // 371 // NOTE: This function is safe for concurrent access. 372 func RegisterNotifier(driver *NotifierDriver) error { 373 registerMtx.Lock() 374 defer registerMtx.Unlock() 375 376 if _, ok := notifiers[driver.NotifierType]; ok { 377 return fmt.Errorf("notifier already registered") 378 } 379 380 notifiers[driver.NotifierType] = driver 381 382 return nil 383 } 384 385 // SupportedNotifiers returns a slice of strings that represent the database 386 // drivers that have been registered and are therefore supported. 387 // 388 // NOTE: This function is safe for concurrent access. 389 func SupportedNotifiers() []string { 390 registerMtx.Lock() 391 defer registerMtx.Unlock() 392 393 supportedNotifiers := make([]string, 0, len(notifiers)) 394 for driverName := range notifiers { 395 supportedNotifiers = append(supportedNotifiers, driverName) 396 } 397 398 return supportedNotifiers 399 } 400 401 // NotifierByName returns the notifier with the given name, or nil if it is not 402 // registered. 403 func NotifierByName(name string) *NotifierDriver { 404 registerMtx.Lock() 405 defer registerMtx.Unlock() 406 407 for driverName, driver := range notifiers { 408 if driverName == name { 409 return driver 410 } 411 } 412 413 return nil 414 } 415 416 // ChainConn enables notifiers to pass in their chain backend to interface 417 // functions that require it. 418 type ChainConn interface { 419 // GetBlockHeader returns the block header for a hash. 420 GetBlockHeader(blockHash *chainhash.Hash) (*wire.BlockHeader, error) 421 422 // GetBlockHash returns the hash from a block height. 423 GetBlockHash(blockHeight int64) (*chainhash.Hash, error) 424 } 425 426 // GetCommonBlockAncestorHeight takes in: 427 // (1) the hash of a block that has been reorged out of the main chain 428 // (2) the hash of the block of the same height from the main chain 429 // It returns the height of the nearest common ancestor between the two hashes, 430 // or an error 431 func GetCommonBlockAncestorHeight(chainConn ChainConn, reorgHash, 432 chainHash chainhash.Hash) (int32, error) { 433 434 for reorgHash != chainHash { 435 reorgHeader, err := chainConn.GetBlockHeader(&reorgHash) 436 if err != nil { 437 return 0, fmt.Errorf("unable to get header for hash=%v: %v", 438 reorgHash, err) 439 } 440 chainHeader, err := chainConn.GetBlockHeader(&chainHash) 441 if err != nil { 442 return 0, fmt.Errorf("unable to get header for hash=%v: %v", 443 chainHash, err) 444 } 445 reorgHash = reorgHeader.PrevBlock 446 chainHash = chainHeader.PrevBlock 447 } 448 449 header, err := chainConn.GetBlockHeader(&chainHash) 450 if err != nil { 451 return 0, fmt.Errorf("unable to get header for hash=%v: %v", 452 chainHash, err) 453 } 454 455 // TODO(decred): Deal with block height differences 456 return int32(header.Height), nil 457 } 458 459 // GetClientMissedBlocks uses a client's best block to determine what blocks 460 // it missed being notified about, and returns them in a slice. The 461 // backendStoresReorgs parameter tells it whether or not the notifier's 462 // chainConn stores information about blocks that have been reorged out of the 463 // chain, which allows GetClientMissedBlocks to find out whether the client's 464 // best block has been reorged out of the chain, rewind to the common ancestor 465 // and return blocks starting right after the common ancestor. 466 func GetClientMissedBlocks(chainConn ChainConn, clientBestBlock *BlockEpoch, 467 notifierBestHeight int32, backendStoresReorgs bool) ([]BlockEpoch, error) { 468 469 startingHeight := clientBestBlock.Height 470 if backendStoresReorgs { 471 // If a reorg causes the client's best hash to be incorrect, 472 // retrieve the closest common ancestor and dispatch 473 // notifications from there. 474 hashAtBestHeight, err := chainConn.GetBlockHash( 475 int64(clientBestBlock.Height)) 476 if err != nil { 477 return nil, fmt.Errorf("unable to find blockhash for "+ 478 "height=%d: %v", clientBestBlock.Height, err) 479 } 480 481 startingHeight, err = GetCommonBlockAncestorHeight( 482 chainConn, *clientBestBlock.Hash, *hashAtBestHeight, 483 ) 484 if err != nil { 485 return nil, fmt.Errorf("unable to find common ancestor: "+ 486 "%v", err) 487 } 488 } 489 490 // We want to start dispatching historical notifications from the block 491 // right after the client's best block, to avoid a redundant notification. 492 missedBlocks, err := getMissedBlocks( 493 chainConn, startingHeight+1, notifierBestHeight+1, 494 ) 495 if err != nil { 496 return nil, fmt.Errorf("unable to get missed blocks: %v", err) 497 } 498 499 return missedBlocks, nil 500 } 501 502 // RewindChain handles internal state updates for the notifier's TxNotifier. It 503 // has no effect if given a height greater than or equal to our current best 504 // known height. It returns the new best block for the notifier. 505 func RewindChain(chainConn ChainConn, txNotifier *TxNotifier, 506 currBestBlock BlockEpoch, targetHeight int32) (BlockEpoch, error) { 507 508 newBestBlock := BlockEpoch{ 509 Height: currBestBlock.Height, 510 Hash: currBestBlock.Hash, 511 BlockHeader: currBestBlock.BlockHeader, 512 } 513 514 for height := currBestBlock.Height; height > targetHeight; height-- { 515 hash, err := chainConn.GetBlockHash(int64(height - 1)) 516 if err != nil { 517 return newBestBlock, fmt.Errorf("unable to "+ 518 "find blockhash for disconnected height=%d: %v", 519 height, err) 520 } 521 header, err := chainConn.GetBlockHeader(hash) 522 if err != nil { 523 return newBestBlock, fmt.Errorf("unable to get block "+ 524 "header for height=%v", height-1) 525 } 526 527 Log.Infof("Block disconnected from main chain: "+ 528 "height=%v, sha=%v", height, newBestBlock.Hash) 529 530 err = txNotifier.DisconnectTip(uint32(height)) 531 if err != nil { 532 return newBestBlock, fmt.Errorf("unable to "+ 533 " disconnect tip for height=%d: %v", 534 height, err) 535 } 536 newBestBlock.Height = height - 1 537 newBestBlock.Hash = hash 538 newBestBlock.BlockHeader = header 539 } 540 541 return newBestBlock, nil 542 } 543 544 // HandleMissedBlocks is called when the chain backend for a notifier misses a 545 // series of blocks, handling a reorg if necessary. Its backendStoresReorgs 546 // parameter tells it whether or not the notifier's chainConn stores 547 // information about blocks that have been reorged out of the chain, which allows 548 // HandleMissedBlocks to check whether the notifier's best block has been 549 // reorged out, and rewind the chain accordingly. It returns the best block for 550 // the notifier and a slice of the missed blocks. The new best block needs to be 551 // returned in case a chain rewind occurs and partially completes before 552 // erroring. In the case where there is no rewind, the notifier's 553 // current best block is returned. 554 func HandleMissedBlocks(chainConn ChainConn, txNotifier *TxNotifier, 555 currBestBlock BlockEpoch, newHeight int32, 556 backendStoresReorgs bool) (BlockEpoch, []BlockEpoch, error) { 557 558 startingHeight := currBestBlock.Height 559 560 if backendStoresReorgs { 561 // If a reorg causes our best hash to be incorrect, rewind the 562 // chain so our best block is set to the closest common 563 // ancestor, then dispatch notifications from there. 564 hashAtBestHeight, err := chainConn.GetBlockHash( 565 int64(currBestBlock.Height), 566 ) 567 if err != nil { 568 return currBestBlock, nil, fmt.Errorf("unable to find "+ 569 "blockhash for height=%d: %v", 570 currBestBlock.Height, err) 571 } 572 573 startingHeight, err = GetCommonBlockAncestorHeight( 574 chainConn, *currBestBlock.Hash, *hashAtBestHeight, 575 ) 576 if err != nil { 577 return currBestBlock, nil, fmt.Errorf("unable to find "+ 578 "common ancestor: %v", err) 579 } 580 581 currBestBlock, err = RewindChain( 582 chainConn, txNotifier, currBestBlock, startingHeight, 583 ) 584 if err != nil { 585 return currBestBlock, nil, fmt.Errorf("unable to "+ 586 "rewind chain: %v", err) 587 } 588 } 589 590 // We want to start dispatching historical notifications from the block 591 // right after our best block, to avoid a redundant notification. 592 missedBlocks, err := getMissedBlocks(chainConn, startingHeight+1, newHeight) 593 if err != nil { 594 return currBestBlock, nil, fmt.Errorf("unable to get missed "+ 595 "blocks: %v", err) 596 } 597 598 return currBestBlock, missedBlocks, nil 599 } 600 601 // getMissedBlocks returns a slice of blocks: [startingHeight, endingHeight) 602 // fetched from the chain. 603 func getMissedBlocks(chainConn ChainConn, startingHeight, 604 endingHeight int32) ([]BlockEpoch, error) { 605 606 numMissedBlocks := endingHeight - startingHeight 607 if numMissedBlocks < 0 { 608 return nil, fmt.Errorf("starting height %d is greater than "+ 609 "ending height %d", startingHeight, endingHeight) 610 } 611 612 missedBlocks := make([]BlockEpoch, 0, numMissedBlocks) 613 for height := startingHeight; height < endingHeight; height++ { 614 hash, err := chainConn.GetBlockHash(int64(height)) 615 if err != nil { 616 return nil, fmt.Errorf("unable to find blockhash for "+ 617 "height=%d: %v", height, err) 618 } 619 header, err := chainConn.GetBlockHeader(hash) 620 if err != nil { 621 return nil, fmt.Errorf("unable to find block header "+ 622 "for height=%d: %v", height, err) 623 } 624 625 missedBlocks = append( 626 missedBlocks, 627 BlockEpoch{ 628 Hash: hash, 629 Height: height, 630 BlockHeader: header, 631 }, 632 ) 633 } 634 635 return missedBlocks, nil 636 } 637 638 // TxIndexConn abstracts an RPC backend with txindex enabled. 639 type TxIndexConn interface { 640 // GetRawTransactionVerbose returns the transaction identified by the 641 // passed chain hash, and returns additional information such as the 642 // block that the transaction confirmed. 643 GetRawTransactionVerbose(*chainhash.Hash) (*jsontypes.TxRawResult, error) 644 645 // GetBlockVerbose returns the block identified by the chain hash along 646 // with additional information such as the block's height in the chain. 647 GetBlockVerbose(*chainhash.Hash, bool) (*jsontypes.GetBlockVerboseResult, error) 648 } 649 650 // IsTxIndexDisabledError returns true if the provided error means the 651 // transaction index has been disabled. 652 func IsTxIndexDisabledError(err error) bool { 653 if err == nil { 654 return false 655 } 656 657 errNoTxIndexMsg := "the transaction index must be enabled" 658 return strings.Contains(err.Error(), errNoTxIndexMsg) 659 } 660 661 // ConfDetailsFromTxIndex looks up whether a transaction is already included in 662 // a block in the active chain by using the backend node's transaction index. 663 // If the transaction is found its TxConfStatus is returned. If it was found in 664 // the mempool this will be TxFoundMempool, if it is found in a block this will 665 // be TxFoundIndex. Otherwise TxNotFoundIndex is returned. If the tx is found 666 // in a block its confirmation details are also returned. 667 func ConfDetailsFromTxIndex(chainConn TxIndexConn, r ConfRequest, 668 txNotFoundErr string) (*TxConfirmation, TxConfStatus, error) { 669 670 // If the transaction has some or all of its confirmations required, 671 // then we may be able to dispatch it immediately. 672 rawTxRes, err := chainConn.GetRawTransactionVerbose(&r.TxID) 673 if err != nil { 674 // If the transaction lookup was successful, but it wasn't 675 // found within the index itself, then we can exit early. We'll 676 // also need to look at the error message returned as the error 677 // code is used for multiple errors. 678 jsonErr, ok := err.(*dcrjson.RPCError) 679 if ok && jsonErr.Code == dcrjson.ErrRPCNoTxInfo && 680 strings.Contains(jsonErr.Message, txNotFoundErr) { 681 682 return nil, TxNotFoundIndex, nil 683 } 684 685 return nil, TxNotFoundIndex, 686 fmt.Errorf("unable to query for txid %v: %v", 687 r.TxID, err) 688 } 689 690 // Deserialize the hex-encoded transaction to include it in the 691 // confirmation details. 692 rawTx, err := hex.DecodeString(rawTxRes.Hex) 693 if err != nil { 694 return nil, TxNotFoundIndex, 695 fmt.Errorf("unable to deserialize tx %v: %v", 696 r.TxID, err) 697 } 698 var tx wire.MsgTx 699 if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil { 700 return nil, TxNotFoundIndex, 701 fmt.Errorf("unable to deserialize tx %v: %v", 702 r.TxID, err) 703 } 704 705 // Ensure the transaction matches our confirmation request in terms of 706 // txid and pkscript. 707 if !r.MatchesTx(&tx) { 708 return nil, TxNotFoundIndex, 709 fmt.Errorf("unable to locate tx %v", r.TxID) 710 } 711 712 // Make sure we actually retrieved a transaction that is included in a 713 // block. If not, the transaction must be unconfirmed (in the mempool), 714 // and we'll return TxFoundMempool together with a nil TxConfirmation. 715 if rawTxRes.BlockHash == "" { 716 return nil, TxFoundMempool, nil 717 } 718 719 // As we need to fully populate the returned TxConfirmation struct, 720 // grab the block in which the transaction was confirmed so we can 721 // locate its exact index within the block. 722 blockHash, err := chainhash.NewHashFromStr(rawTxRes.BlockHash) 723 if err != nil { 724 return nil, TxNotFoundIndex, 725 fmt.Errorf("unable to get block hash %v for "+ 726 "historical dispatch: %v", rawTxRes.BlockHash, err) 727 } 728 block, err := chainConn.GetBlockVerbose(blockHash, false) 729 if err != nil { 730 return nil, TxNotFoundIndex, 731 fmt.Errorf("unable to get block with hash %v for "+ 732 "historical dispatch: %v", blockHash, err) 733 } 734 735 // If the block was obtained, locate the transaction's index within the 736 // block so we can give the subscriber full confirmation details. 737 txidStr := r.TxID.String() 738 for txIndex, txHash := range block.Tx { 739 if txHash != txidStr { 740 continue 741 } 742 743 return &TxConfirmation{ 744 Tx: &tx, 745 BlockHash: blockHash, 746 BlockHeight: uint32(block.Height), 747 TxIndex: uint32(txIndex), 748 }, TxFoundIndex, nil 749 } 750 751 // We return an error because we should have found the transaction 752 // within the block, but didn't. 753 return nil, TxNotFoundIndex, fmt.Errorf("unable to locate "+ 754 "tx %v in block %v", r.TxID, blockHash) 755 }