2017-11-10 03:30:20 +03:00
|
|
|
package bitcoindnotify
|
|
|
|
|
|
|
|
import (
|
2018-12-11 05:24:04 +03:00
|
|
|
"bytes"
|
|
|
|
"encoding/hex"
|
2017-11-10 03:30:20 +03:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2018-07-19 00:01:28 +03:00
|
|
|
"strings"
|
2017-11-10 03:30:20 +03:00
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
|
|
|
|
2018-06-05 04:34:16 +03:00
|
|
|
"github.com/btcsuite/btcd/btcjson"
|
2018-12-07 08:14:04 +03:00
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
2018-06-05 04:34:16 +03:00
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
|
|
"github.com/btcsuite/btcutil"
|
|
|
|
"github.com/btcsuite/btcwallet/chain"
|
2018-07-17 10:13:06 +03:00
|
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
2018-10-12 18:08:14 +03:00
|
|
|
"github.com/lightningnetwork/lnd/queue"
|
2017-11-10 03:30:20 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// notifierType uniquely identifies this concrete implementation of the
|
|
|
|
// ChainNotifier interface.
|
|
|
|
notifierType = "bitcoind"
|
|
|
|
)
|
|
|
|
|
|
|
|
// chainUpdate encapsulates an update to the current main chain. This struct is
|
|
|
|
// used as an element within an unbounded queue in order to avoid blocking the
|
|
|
|
// main rpc dispatch rule.
|
|
|
|
type chainUpdate struct {
|
|
|
|
blockHash *chainhash.Hash
|
|
|
|
blockHeight int32
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(roasbeef): generalize struct below:
|
2018-02-26 21:37:58 +03:00
|
|
|
// * move chans to config
|
|
|
|
// * extract common code
|
|
|
|
// * allow outside callers to handle send conditions
|
2017-11-10 03:30:20 +03:00
|
|
|
|
|
|
|
// BitcoindNotifier implements the ChainNotifier interface using a bitcoind
|
|
|
|
// chain client. Multiple concurrent clients are supported. All notifications
|
|
|
|
// are achieved via non-blocking sends on client channels.
|
|
|
|
type BitcoindNotifier struct {
|
2018-07-27 07:30:15 +03:00
|
|
|
confClientCounter uint64 // To be used atomically.
|
2017-11-10 03:30:20 +03:00
|
|
|
spendClientCounter uint64 // To be used atomically.
|
|
|
|
epochClientCounter uint64 // To be used atomically.
|
|
|
|
|
|
|
|
started int32 // To be used atomically.
|
|
|
|
stopped int32 // To be used atomically.
|
|
|
|
|
2018-12-07 08:14:04 +03:00
|
|
|
chainConn *chain.BitcoindClient
|
|
|
|
chainParams *chaincfg.Params
|
2017-11-10 03:30:20 +03:00
|
|
|
|
|
|
|
notificationCancels chan interface{}
|
|
|
|
notificationRegistry chan interface{}
|
|
|
|
|
2018-10-05 12:07:55 +03:00
|
|
|
txNotifier *chainntnfs.TxNotifier
|
2017-11-10 03:30:20 +03:00
|
|
|
|
|
|
|
blockEpochClients map[uint64]*blockEpochRegistration
|
|
|
|
|
2018-08-09 10:05:28 +03:00
|
|
|
bestBlock chainntnfs.BlockEpoch
|
|
|
|
|
2018-08-15 03:53:34 +03:00
|
|
|
// spendHintCache is a cache used to query and update the latest height
|
|
|
|
// hints for an outpoint. Each height hint represents the earliest
|
|
|
|
// height at which the outpoint could have been spent within the chain.
|
|
|
|
spendHintCache chainntnfs.SpendHintCache
|
|
|
|
|
|
|
|
// confirmHintCache is a cache used to query the latest height hints for
|
|
|
|
// a transaction. Each height hint represents the earliest height at
|
|
|
|
// which the transaction could have confirmed within the chain.
|
|
|
|
confirmHintCache chainntnfs.ConfirmHintCache
|
|
|
|
|
2017-11-10 03:30:20 +03:00
|
|
|
wg sync.WaitGroup
|
|
|
|
quit chan struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure BitcoindNotifier implements the ChainNotifier interface at compile
|
|
|
|
// time.
|
|
|
|
var _ chainntnfs.ChainNotifier = (*BitcoindNotifier)(nil)
|
|
|
|
|
|
|
|
// New returns a new BitcoindNotifier instance. This function assumes the
|
2018-12-07 08:14:04 +03:00
|
|
|
// bitcoind node detailed in the passed configuration is already running, and
|
2017-11-10 03:30:20 +03:00
|
|
|
// willing to accept RPC requests and new zmq clients.
|
2018-12-07 08:14:04 +03:00
|
|
|
func New(chainConn *chain.BitcoindConn, chainParams *chaincfg.Params,
|
|
|
|
spendHintCache chainntnfs.SpendHintCache,
|
2018-08-15 03:53:34 +03:00
|
|
|
confirmHintCache chainntnfs.ConfirmHintCache) *BitcoindNotifier {
|
|
|
|
|
2017-11-10 03:30:20 +03:00
|
|
|
notifier := &BitcoindNotifier{
|
2018-12-07 08:14:04 +03:00
|
|
|
chainParams: chainParams,
|
|
|
|
|
2017-11-10 03:30:20 +03:00
|
|
|
notificationCancels: make(chan interface{}),
|
|
|
|
notificationRegistry: make(chan interface{}),
|
|
|
|
|
|
|
|
blockEpochClients: make(map[uint64]*blockEpochRegistration),
|
|
|
|
|
2018-08-15 03:53:34 +03:00
|
|
|
spendHintCache: spendHintCache,
|
|
|
|
confirmHintCache: confirmHintCache,
|
|
|
|
|
2017-11-10 03:30:20 +03:00
|
|
|
quit: make(chan struct{}),
|
|
|
|
}
|
|
|
|
|
2018-09-13 01:57:05 +03:00
|
|
|
notifier.chainConn = chainConn.NewBitcoindClient()
|
2017-11-10 03:30:20 +03:00
|
|
|
|
2018-07-17 02:50:47 +03:00
|
|
|
return notifier
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Start connects to the running bitcoind node over websockets, registers for
|
|
|
|
// block notifications, and finally launches all related helper goroutines.
|
|
|
|
func (b *BitcoindNotifier) Start() error {
|
|
|
|
// Already started?
|
|
|
|
if atomic.AddInt32(&b.started, 1) != 1 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Connect to bitcoind, and register for notifications on connected,
|
|
|
|
// and disconnected blocks.
|
|
|
|
if err := b.chainConn.Start(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := b.chainConn.NotifyBlocks(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-08-09 10:05:28 +03:00
|
|
|
currentHash, currentHeight, err := b.chainConn.GetBestBlock()
|
2017-11-10 03:30:20 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-10-05 12:07:55 +03:00
|
|
|
b.txNotifier = chainntnfs.NewTxNotifier(
|
2018-12-05 03:58:27 +03:00
|
|
|
uint32(currentHeight), chainntnfs.ReorgSafetyLimit,
|
|
|
|
b.confirmHintCache, b.spendHintCache,
|
2018-08-15 03:53:34 +03:00
|
|
|
)
|
2017-11-10 03:30:20 +03:00
|
|
|
|
2018-08-09 10:05:28 +03:00
|
|
|
b.bestBlock = chainntnfs.BlockEpoch{
|
|
|
|
Height: currentHeight,
|
|
|
|
Hash: currentHash,
|
|
|
|
}
|
|
|
|
|
2017-11-10 03:30:20 +03:00
|
|
|
b.wg.Add(1)
|
2018-08-09 10:05:28 +03:00
|
|
|
go b.notificationDispatcher()
|
2017-11-10 03:30:20 +03:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop shutsdown the BitcoindNotifier.
|
|
|
|
func (b *BitcoindNotifier) Stop() error {
|
|
|
|
// Already shutting down?
|
|
|
|
if atomic.AddInt32(&b.stopped, 1) != 1 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shutdown the rpc client, this gracefully disconnects from bitcoind,
|
|
|
|
// and cleans up all related resources.
|
|
|
|
b.chainConn.Stop()
|
|
|
|
|
|
|
|
close(b.quit)
|
|
|
|
b.wg.Wait()
|
|
|
|
|
|
|
|
// Notify all pending clients of our shutdown by closing the related
|
|
|
|
// notification channels.
|
|
|
|
for _, epochClient := range b.blockEpochClients {
|
chainntnfs: ensure all block epoch notifications are sent *in order*
In this commit, we fix a lingering bug related to the way that we
deliver block epoch notifications to end users. Before this commit, we
would launch a new goroutine for *each block*. This was done in order
to ensure that the notification dispatch wouldn’t block the main
goroutine that was dispatching the notifications. This method archived
the goal, but had a nasty side effect that the goroutines could be
re-ordered during scheduling, meaning that in the case of fast
successive blocks, then notifications would be delivered out of order.
Receiving out of order notifications is either disallowed, or can cause
sub-systems that rely on these notifications to get into weird states.
In order to fix this issue, we’ll no longer launch a new goroutine to
deliver each notification to an awaiting client. Instead, each client
will now gain a concurrent in-order queue for notification delivery.
Due to the internal design of chainntnfs.ConcurrentQueue, the caller
should never block, yet the receivers will receive notifications in
order. This change solves the re-ordering issue and also minimizes the
number of goroutines that we’ll create in order to deliver block epoch
notifications.
2018-02-10 03:13:21 +03:00
|
|
|
close(epochClient.cancelChan)
|
|
|
|
epochClient.wg.Wait()
|
|
|
|
|
2017-11-10 03:30:20 +03:00
|
|
|
close(epochClient.epochChan)
|
|
|
|
}
|
2018-10-05 12:07:55 +03:00
|
|
|
b.txNotifier.TearDown()
|
2017-11-10 03:30:20 +03:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// blockNtfn packages a notification of a connected/disconnected block along
|
|
|
|
// with its height at the time.
|
|
|
|
type blockNtfn struct {
|
|
|
|
sha *chainhash.Hash
|
|
|
|
height int32
|
|
|
|
}
|
|
|
|
|
|
|
|
// notificationDispatcher is the primary goroutine which handles client
|
|
|
|
// notification registrations, as well as notification dispatches.
|
2018-08-09 10:05:28 +03:00
|
|
|
func (b *BitcoindNotifier) notificationDispatcher() {
|
2017-11-10 03:30:20 +03:00
|
|
|
out:
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case cancelMsg := <-b.notificationCancels:
|
|
|
|
switch msg := cancelMsg.(type) {
|
|
|
|
case *epochCancel:
|
|
|
|
chainntnfs.Log.Infof("Cancelling epoch "+
|
|
|
|
"notification, epoch_id=%v", msg.epochID)
|
|
|
|
|
chainntnfs: ensure all block epoch notifications are sent *in order*
In this commit, we fix a lingering bug related to the way that we
deliver block epoch notifications to end users. Before this commit, we
would launch a new goroutine for *each block*. This was done in order
to ensure that the notification dispatch wouldn’t block the main
goroutine that was dispatching the notifications. This method archived
the goal, but had a nasty side effect that the goroutines could be
re-ordered during scheduling, meaning that in the case of fast
successive blocks, then notifications would be delivered out of order.
Receiving out of order notifications is either disallowed, or can cause
sub-systems that rely on these notifications to get into weird states.
In order to fix this issue, we’ll no longer launch a new goroutine to
deliver each notification to an awaiting client. Instead, each client
will now gain a concurrent in-order queue for notification delivery.
Due to the internal design of chainntnfs.ConcurrentQueue, the caller
should never block, yet the receivers will receive notifications in
order. This change solves the re-ordering issue and also minimizes the
number of goroutines that we’ll create in order to deliver block epoch
notifications.
2018-02-10 03:13:21 +03:00
|
|
|
// First, we'll lookup the original
|
|
|
|
// registration in order to stop the active
|
|
|
|
// queue goroutine.
|
|
|
|
reg := b.blockEpochClients[msg.epochID]
|
|
|
|
reg.epochQueue.Stop()
|
|
|
|
|
|
|
|
// Next, close the cancel channel for this
|
2017-11-10 03:30:20 +03:00
|
|
|
// specific client, and wait for the client to
|
|
|
|
// exit.
|
|
|
|
close(b.blockEpochClients[msg.epochID].cancelChan)
|
|
|
|
b.blockEpochClients[msg.epochID].wg.Wait()
|
|
|
|
|
|
|
|
// Once the client has exited, we can then
|
|
|
|
// safely close the channel used to send epoch
|
|
|
|
// notifications, in order to notify any
|
|
|
|
// listeners that the intent has been
|
|
|
|
// cancelled.
|
|
|
|
close(b.blockEpochClients[msg.epochID].epochChan)
|
|
|
|
delete(b.blockEpochClients, msg.epochID)
|
|
|
|
|
|
|
|
}
|
|
|
|
case registerMsg := <-b.notificationRegistry:
|
|
|
|
switch msg := registerMsg.(type) {
|
2018-08-27 07:36:17 +03:00
|
|
|
case *chainntnfs.HistoricalConfDispatch:
|
2018-07-27 07:32:55 +03:00
|
|
|
// Look up whether the transaction is already
|
|
|
|
// included in the active chain. We'll do this
|
|
|
|
// in a goroutine to prevent blocking
|
|
|
|
// potentially long rescans.
|
2018-10-05 12:07:55 +03:00
|
|
|
//
|
|
|
|
// TODO(wilmer): add retry logic if rescan fails?
|
2018-07-27 07:32:55 +03:00
|
|
|
b.wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer b.wg.Done()
|
|
|
|
|
2018-07-19 00:01:28 +03:00
|
|
|
confDetails, _, err := b.historicalConfDetails(
|
2018-12-07 08:14:19 +03:00
|
|
|
msg.ConfRequest,
|
|
|
|
msg.StartHeight, msg.EndHeight,
|
2018-07-27 07:32:55 +03:00
|
|
|
)
|
|
|
|
if err != nil {
|
2019-04-02 04:21:07 +03:00
|
|
|
chainntnfs.Log.Errorf("Rescan to "+
|
|
|
|
"determine the conf "+
|
|
|
|
"details of %v within "+
|
|
|
|
"range %d-%d failed: %v",
|
|
|
|
msg.ConfRequest,
|
|
|
|
msg.StartHeight,
|
|
|
|
msg.EndHeight, err)
|
2018-07-27 07:32:55 +03:00
|
|
|
return
|
|
|
|
}
|
2018-02-26 21:37:58 +03:00
|
|
|
|
2018-08-25 05:35:17 +03:00
|
|
|
// If the historical dispatch finished
|
|
|
|
// without error, we will invoke
|
|
|
|
// UpdateConfDetails even if none were
|
|
|
|
// found. This allows the notifier to
|
|
|
|
// begin safely updating the height hint
|
|
|
|
// cache at tip, since any pending
|
|
|
|
// rescans have now completed.
|
2018-10-05 12:07:55 +03:00
|
|
|
err = b.txNotifier.UpdateConfDetails(
|
2018-12-07 08:14:19 +03:00
|
|
|
msg.ConfRequest, confDetails,
|
2018-08-25 05:35:17 +03:00
|
|
|
)
|
|
|
|
if err != nil {
|
2019-04-02 04:21:07 +03:00
|
|
|
chainntnfs.Log.Errorf("Unable "+
|
|
|
|
"to update conf "+
|
|
|
|
"details of %v: %v",
|
|
|
|
msg.ConfRequest, err)
|
2018-07-27 07:32:55 +03:00
|
|
|
}
|
|
|
|
}()
|
2018-02-26 21:37:58 +03:00
|
|
|
|
2018-10-05 12:07:55 +03:00
|
|
|
case *chainntnfs.HistoricalSpendDispatch:
|
|
|
|
// In order to ensure we don't block the caller
|
|
|
|
// on what may be a long rescan, we'll launch a
|
|
|
|
// goroutine to do so in the background.
|
|
|
|
//
|
|
|
|
// TODO(wilmer): add retry logic if rescan fails?
|
|
|
|
b.wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer b.wg.Done()
|
|
|
|
|
2019-04-02 04:20:52 +03:00
|
|
|
spendDetails, err := b.historicalSpendDetails(
|
|
|
|
msg.SpendRequest,
|
|
|
|
msg.StartHeight, msg.EndHeight,
|
|
|
|
)
|
2018-10-05 12:07:55 +03:00
|
|
|
if err != nil {
|
|
|
|
chainntnfs.Log.Errorf("Rescan to "+
|
|
|
|
"determine the spend "+
|
2018-12-07 08:14:28 +03:00
|
|
|
"details of %v within "+
|
|
|
|
"range %d-%d failed: %v",
|
|
|
|
msg.SpendRequest,
|
|
|
|
msg.StartHeight,
|
|
|
|
msg.EndHeight, err)
|
2019-04-02 04:20:52 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the historical dispatch finished
|
|
|
|
// without error, we will invoke
|
|
|
|
// UpdateSpendDetails even if none were
|
|
|
|
// found. This allows the notifier to
|
|
|
|
// begin safely updating the height hint
|
|
|
|
// cache at tip, since any pending
|
|
|
|
// rescans have now completed.
|
|
|
|
err = b.txNotifier.UpdateSpendDetails(
|
|
|
|
msg.SpendRequest, spendDetails,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
chainntnfs.Log.Errorf("Unable "+
|
|
|
|
"to update spend "+
|
|
|
|
"details of %v: %v",
|
|
|
|
msg.SpendRequest, err)
|
2018-10-05 12:07:55 +03:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2017-11-10 03:30:20 +03:00
|
|
|
case *blockEpochRegistration:
|
|
|
|
chainntnfs.Log.Infof("New block epoch subscription")
|
2018-12-11 05:29:28 +03:00
|
|
|
|
2017-11-10 03:30:20 +03:00
|
|
|
b.blockEpochClients[msg.epochID] = msg
|
2018-12-11 05:29:28 +03:00
|
|
|
|
|
|
|
// If the client did not provide their best
|
|
|
|
// known block, then we'll immediately dispatch
|
|
|
|
// a notification for the current tip.
|
|
|
|
if msg.bestBlock == nil {
|
|
|
|
b.notifyBlockEpochClient(
|
|
|
|
msg, b.bestBlock.Height,
|
|
|
|
b.bestBlock.Hash,
|
|
|
|
)
|
|
|
|
|
|
|
|
msg.errorChan <- nil
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, we'll attempt to deliver the
|
|
|
|
// backlog of notifications from their best
|
|
|
|
// known block.
|
|
|
|
missedBlocks, err := chainntnfs.GetClientMissedBlocks(
|
|
|
|
b.chainConn, msg.bestBlock,
|
|
|
|
b.bestBlock.Height, true,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
msg.errorChan <- err
|
|
|
|
continue
|
2018-08-09 10:05:29 +03:00
|
|
|
}
|
2018-12-11 05:29:28 +03:00
|
|
|
|
|
|
|
for _, block := range missedBlocks {
|
|
|
|
b.notifyBlockEpochClient(
|
|
|
|
msg, block.Height, block.Hash,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2018-08-09 10:05:29 +03:00
|
|
|
msg.errorChan <- nil
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
case ntfn := <-b.chainConn.Notifications():
|
|
|
|
switch item := ntfn.(type) {
|
|
|
|
case chain.BlockConnected:
|
2018-08-09 10:05:30 +03:00
|
|
|
blockHeader, err :=
|
|
|
|
b.chainConn.GetBlockHeader(&item.Hash)
|
2017-11-10 03:30:20 +03:00
|
|
|
if err != nil {
|
2018-08-09 10:05:30 +03:00
|
|
|
chainntnfs.Log.Errorf("Unable to fetch "+
|
|
|
|
"block header: %v", err)
|
2017-11-10 03:30:20 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-08-09 10:05:30 +03:00
|
|
|
if blockHeader.PrevBlock != *b.bestBlock.Hash {
|
|
|
|
// Handle the case where the notifier
|
|
|
|
// missed some blocks from its chain
|
|
|
|
// backend.
|
|
|
|
chainntnfs.Log.Infof("Missed blocks, " +
|
|
|
|
"attempting to catch up")
|
|
|
|
newBestBlock, missedBlocks, err :=
|
|
|
|
chainntnfs.HandleMissedBlocks(
|
|
|
|
b.chainConn,
|
2018-10-05 12:07:55 +03:00
|
|
|
b.txNotifier,
|
2018-08-09 10:05:30 +03:00
|
|
|
b.bestBlock, item.Height,
|
|
|
|
true,
|
|
|
|
)
|
2017-11-10 03:30:20 +03:00
|
|
|
|
2018-08-09 10:05:30 +03:00
|
|
|
if err != nil {
|
|
|
|
// Set the bestBlock here in case
|
|
|
|
// a catch up partially completed.
|
|
|
|
b.bestBlock = newBestBlock
|
|
|
|
chainntnfs.Log.Error(err)
|
|
|
|
continue
|
|
|
|
}
|
2017-11-10 03:30:20 +03:00
|
|
|
|
2018-08-09 10:05:30 +03:00
|
|
|
for _, block := range missedBlocks {
|
|
|
|
err := b.handleBlockConnected(block)
|
|
|
|
if err != nil {
|
|
|
|
chainntnfs.Log.Error(err)
|
|
|
|
continue out
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
newBlock := chainntnfs.BlockEpoch{
|
|
|
|
Height: item.Height,
|
|
|
|
Hash: &item.Hash,
|
|
|
|
}
|
|
|
|
if err := b.handleBlockConnected(newBlock); err != nil {
|
2017-11-10 03:30:20 +03:00
|
|
|
chainntnfs.Log.Error(err)
|
|
|
|
}
|
2018-08-09 10:05:30 +03:00
|
|
|
|
2017-11-10 03:30:20 +03:00
|
|
|
continue
|
|
|
|
|
|
|
|
case chain.BlockDisconnected:
|
2018-08-09 10:05:28 +03:00
|
|
|
if item.Height != b.bestBlock.Height {
|
2018-08-09 10:05:29 +03:00
|
|
|
chainntnfs.Log.Infof("Missed disconnected" +
|
|
|
|
"blocks, attempting to catch up")
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
|
2018-08-09 10:05:29 +03:00
|
|
|
newBestBlock, err := chainntnfs.RewindChain(
|
2018-10-05 12:07:55 +03:00
|
|
|
b.chainConn, b.txNotifier,
|
2018-08-09 10:05:29 +03:00
|
|
|
b.bestBlock, item.Height-1,
|
|
|
|
)
|
2017-11-10 03:30:20 +03:00
|
|
|
if err != nil {
|
2018-08-09 10:05:29 +03:00
|
|
|
chainntnfs.Log.Errorf("Unable to rewind chain "+
|
|
|
|
"from height %d to height %d: %v",
|
|
|
|
b.bestBlock.Height, item.Height-1, err)
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
|
2018-08-09 10:05:29 +03:00
|
|
|
// Set the bestBlock here in case a chain
|
|
|
|
// rewind partially completed.
|
|
|
|
b.bestBlock = newBestBlock
|
|
|
|
|
2017-11-10 03:30:20 +03:00
|
|
|
case chain.RelevantTx:
|
2018-10-05 12:07:55 +03:00
|
|
|
// We only care about notifying on confirmed
|
|
|
|
// spends, so if this is a mempool spend, we can
|
|
|
|
// ignore it and wait for the spend to appear in
|
|
|
|
// on-chain.
|
|
|
|
if item.Block == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-12-07 08:14:28 +03:00
|
|
|
tx := btcutil.NewTx(&item.TxRecord.MsgTx)
|
2018-10-05 12:07:55 +03:00
|
|
|
err := b.txNotifier.ProcessRelevantSpendTx(
|
2018-12-07 08:14:28 +03:00
|
|
|
tx, uint32(item.Block.Height),
|
2018-10-05 12:07:55 +03:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
chainntnfs.Log.Errorf("Unable to "+
|
|
|
|
"process transaction %v: %v",
|
2018-12-07 08:14:28 +03:00
|
|
|
tx.Hash(), err)
|
2018-10-05 12:07:55 +03:00
|
|
|
}
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
case <-b.quit:
|
|
|
|
break out
|
|
|
|
}
|
|
|
|
}
|
|
|
|
b.wg.Done()
|
|
|
|
}
|
|
|
|
|
2018-12-07 08:14:19 +03:00
|
|
|
// historicalConfDetails looks up whether a confirmation request (txid/output
|
|
|
|
// script) has already been included in a block in the active chain and, if so,
|
|
|
|
// returns details about said block.
|
|
|
|
func (b *BitcoindNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequest,
|
2018-08-27 07:36:17 +03:00
|
|
|
startHeight, endHeight uint32) (*chainntnfs.TxConfirmation,
|
2018-08-24 15:13:28 +03:00
|
|
|
chainntnfs.TxConfStatus, error) {
|
2018-02-26 21:37:58 +03:00
|
|
|
|
2018-12-07 08:14:19 +03:00
|
|
|
// If a txid was not provided, then we should dispatch upon seeing the
|
|
|
|
// script on-chain, so we'll short-circuit straight to scanning manually
|
|
|
|
// as there doesn't exist a script index to query.
|
|
|
|
if confRequest.TxID == chainntnfs.ZeroHash {
|
|
|
|
return b.confDetailsManually(
|
|
|
|
confRequest, startHeight, endHeight,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, we'll dispatch upon seeing a transaction on-chain with the
|
|
|
|
// given hash.
|
|
|
|
//
|
2018-07-19 00:01:28 +03:00
|
|
|
// We'll first attempt to retrieve the transaction using the node's
|
|
|
|
// txindex.
|
2018-12-07 08:14:19 +03:00
|
|
|
txConf, txStatus, err := b.confDetailsFromTxIndex(&confRequest.TxID)
|
2018-07-19 00:01:28 +03:00
|
|
|
|
|
|
|
// We'll then check the status of the transaction lookup returned to
|
|
|
|
// determine whether we should proceed with any fallback methods.
|
|
|
|
switch {
|
2018-08-24 15:45:12 +03:00
|
|
|
|
|
|
|
// We failed querying the index for the transaction, fall back to
|
|
|
|
// scanning manually.
|
|
|
|
case err != nil:
|
|
|
|
chainntnfs.Log.Debugf("Failed getting conf details from "+
|
|
|
|
"index (%v), scanning manually", err)
|
2018-12-07 08:14:19 +03:00
|
|
|
return b.confDetailsManually(confRequest, startHeight, endHeight)
|
2018-08-24 15:45:12 +03:00
|
|
|
|
2018-07-19 00:01:28 +03:00
|
|
|
// The transaction was found within the node's mempool.
|
2018-08-24 15:13:28 +03:00
|
|
|
case txStatus == chainntnfs.TxFoundMempool:
|
2018-07-19 00:01:28 +03:00
|
|
|
|
|
|
|
// The transaction was found within the node's txindex.
|
2018-08-24 15:13:28 +03:00
|
|
|
case txStatus == chainntnfs.TxFoundIndex:
|
2018-07-19 00:01:28 +03:00
|
|
|
|
|
|
|
// The transaction was not found within the node's mempool or txindex.
|
2018-08-24 15:45:12 +03:00
|
|
|
case txStatus == chainntnfs.TxNotFoundIndex:
|
2018-02-26 21:37:58 +03:00
|
|
|
|
2018-08-24 15:45:12 +03:00
|
|
|
// Unexpected txStatus returned.
|
2018-07-19 00:01:28 +03:00
|
|
|
default:
|
2018-08-24 15:45:12 +03:00
|
|
|
return nil, txStatus,
|
|
|
|
fmt.Errorf("Got unexpected txConfStatus: %v", txStatus)
|
2018-02-26 21:37:58 +03:00
|
|
|
}
|
|
|
|
|
2018-07-19 00:01:28 +03:00
|
|
|
return txConf, txStatus, nil
|
2018-02-26 21:37:58 +03:00
|
|
|
}
|
|
|
|
|
2018-08-24 14:54:54 +03:00
|
|
|
// confDetailsFromTxIndex looks up whether a transaction is already included in
|
|
|
|
// a block in the active chain by using the backend node's transaction index.
|
|
|
|
// If the transaction is found its TxConfStatus is returned. If it was found in
|
|
|
|
// the mempool this will be TxFoundMempool, if it is found in a block this will
|
|
|
|
// be TxFoundIndex. Otherwise TxNotFoundIndex is returned. If the tx is found
|
|
|
|
// in a block its confirmation details are also returned.
|
2018-02-26 21:37:58 +03:00
|
|
|
func (b *BitcoindNotifier) confDetailsFromTxIndex(txid *chainhash.Hash,
|
2018-08-24 15:13:28 +03:00
|
|
|
) (*chainntnfs.TxConfirmation, chainntnfs.TxConfStatus, error) {
|
2017-11-10 03:30:20 +03:00
|
|
|
|
2018-02-26 21:37:58 +03:00
|
|
|
// If the transaction has some or all of its confirmations required,
|
2017-11-10 03:30:20 +03:00
|
|
|
// then we may be able to dispatch it immediately.
|
2018-12-11 05:24:04 +03:00
|
|
|
rawTxRes, err := b.chainConn.GetRawTransactionVerbose(txid)
|
2018-02-26 21:37:58 +03:00
|
|
|
if err != nil {
|
2018-09-06 11:48:46 +03:00
|
|
|
// If the transaction lookup was successful, but it wasn't found
|
2018-07-19 00:01:28 +03:00
|
|
|
// within the index itself, then we can exit early. We'll also
|
|
|
|
// need to look at the error message returned as the error code
|
|
|
|
// is used for multiple errors.
|
|
|
|
txNotFoundErr := "No such mempool or blockchain transaction"
|
2018-02-26 21:37:58 +03:00
|
|
|
jsonErr, ok := err.(*btcjson.RPCError)
|
2018-07-19 00:01:28 +03:00
|
|
|
if ok && jsonErr.Code == btcjson.ErrRPCNoTxInfo &&
|
|
|
|
strings.Contains(jsonErr.Message, txNotFoundErr) {
|
|
|
|
|
2018-08-24 15:13:28 +03:00
|
|
|
return nil, chainntnfs.TxNotFoundIndex, nil
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
2018-07-19 00:01:28 +03:00
|
|
|
|
2018-08-24 15:13:28 +03:00
|
|
|
return nil, chainntnfs.TxNotFoundIndex,
|
|
|
|
fmt.Errorf("unable to query for txid %v: %v", txid, err)
|
2018-02-26 21:37:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure we actually retrieved a transaction that is included in a
|
2018-08-24 14:54:54 +03:00
|
|
|
// block. If not, the transaction must be unconfirmed (in the mempool),
|
|
|
|
// and we'll return TxFoundMempool together with a nil TxConfirmation.
|
2018-12-11 05:24:04 +03:00
|
|
|
if rawTxRes.BlockHash == "" {
|
2018-08-24 15:13:28 +03:00
|
|
|
return nil, chainntnfs.TxFoundMempool, nil
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// As we need to fully populate the returned TxConfirmation struct,
|
|
|
|
// grab the block in which the transaction was confirmed so we can
|
|
|
|
// locate its exact index within the block.
|
2018-12-11 05:24:04 +03:00
|
|
|
blockHash, err := chainhash.NewHashFromStr(rawTxRes.BlockHash)
|
2017-11-10 03:30:20 +03:00
|
|
|
if err != nil {
|
2018-08-24 15:13:28 +03:00
|
|
|
return nil, chainntnfs.TxNotFoundIndex,
|
|
|
|
fmt.Errorf("unable to get block hash %v for "+
|
2018-12-11 05:24:04 +03:00
|
|
|
"historical dispatch: %v", rawTxRes.BlockHash, err)
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
block, err := b.chainConn.GetBlockVerbose(blockHash)
|
|
|
|
if err != nil {
|
2018-08-24 15:13:28 +03:00
|
|
|
return nil, chainntnfs.TxNotFoundIndex,
|
|
|
|
fmt.Errorf("unable to get block with hash %v for "+
|
|
|
|
"historical dispatch: %v", blockHash, err)
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
|
2018-02-26 21:37:58 +03:00
|
|
|
// If the block was obtained, locate the transaction's index within the
|
2017-11-10 03:30:20 +03:00
|
|
|
// block so we can give the subscriber full confirmation details.
|
2018-12-11 05:24:04 +03:00
|
|
|
txidStr := txid.String()
|
2018-02-26 21:37:58 +03:00
|
|
|
for txIndex, txHash := range block.Tx {
|
2018-12-11 05:24:04 +03:00
|
|
|
if txHash != txidStr {
|
|
|
|
continue
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
2018-12-11 05:24:04 +03:00
|
|
|
|
|
|
|
// Deserialize the hex-encoded transaction to include it in the
|
|
|
|
// confirmation details.
|
|
|
|
rawTx, err := hex.DecodeString(rawTxRes.Hex)
|
|
|
|
if err != nil {
|
|
|
|
return nil, chainntnfs.TxFoundIndex,
|
|
|
|
fmt.Errorf("unable to deserialize tx %v: %v",
|
|
|
|
txHash, err)
|
|
|
|
}
|
|
|
|
var tx wire.MsgTx
|
|
|
|
if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil {
|
|
|
|
return nil, chainntnfs.TxFoundIndex,
|
|
|
|
fmt.Errorf("unable to deserialize tx %v: %v",
|
|
|
|
txHash, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &chainntnfs.TxConfirmation{
|
|
|
|
Tx: &tx,
|
|
|
|
BlockHash: blockHash,
|
|
|
|
BlockHeight: uint32(block.Height),
|
|
|
|
TxIndex: uint32(txIndex),
|
|
|
|
}, chainntnfs.TxFoundIndex, nil
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
|
2018-02-26 21:37:58 +03:00
|
|
|
// We return an error because we should have found the transaction
|
|
|
|
// within the block, but didn't.
|
2018-12-11 05:24:04 +03:00
|
|
|
return nil, chainntnfs.TxNotFoundIndex, fmt.Errorf("unable to locate "+
|
|
|
|
"tx %v in block %v", txid, blockHash)
|
2018-02-26 21:37:58 +03:00
|
|
|
}
|
2017-11-10 03:30:20 +03:00
|
|
|
|
2018-12-07 08:14:19 +03:00
|
|
|
// confDetailsManually looks up whether a transaction/output script has already
|
|
|
|
// been included in a block in the active chain by scanning the chain's blocks
|
|
|
|
// within the given range. If the transaction/output script is found, its
|
|
|
|
// confirmation details are returned. Otherwise, nil is returned.
|
|
|
|
func (b *BitcoindNotifier) confDetailsManually(confRequest chainntnfs.ConfRequest,
|
2018-08-24 15:13:28 +03:00
|
|
|
heightHint, currentHeight uint32) (*chainntnfs.TxConfirmation,
|
|
|
|
chainntnfs.TxConfStatus, error) {
|
2018-02-26 21:37:58 +03:00
|
|
|
|
|
|
|
// Begin scanning blocks at every height to determine where the
|
|
|
|
// transaction was included in.
|
2018-11-23 00:58:58 +03:00
|
|
|
for height := currentHeight; height >= heightHint && height > 0; height-- {
|
2018-07-27 07:32:55 +03:00
|
|
|
// Ensure we haven't been requested to shut down before
|
|
|
|
// processing the next height.
|
|
|
|
select {
|
|
|
|
case <-b.quit:
|
2018-08-24 15:13:28 +03:00
|
|
|
return nil, chainntnfs.TxNotFoundManually,
|
2018-12-11 05:25:41 +03:00
|
|
|
chainntnfs.ErrChainNotifierShuttingDown
|
2018-07-27 07:32:55 +03:00
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
2018-02-26 21:37:58 +03:00
|
|
|
blockHash, err := b.chainConn.GetBlockHash(int64(height))
|
|
|
|
if err != nil {
|
2018-08-24 15:13:28 +03:00
|
|
|
return nil, chainntnfs.TxNotFoundManually,
|
|
|
|
fmt.Errorf("unable to get hash from block "+
|
|
|
|
"with height %d", height)
|
2018-02-26 21:37:58 +03:00
|
|
|
}
|
|
|
|
|
2018-12-07 08:14:19 +03:00
|
|
|
block, err := b.chainConn.GetBlock(blockHash)
|
2018-02-26 21:37:58 +03:00
|
|
|
if err != nil {
|
2018-08-24 15:13:28 +03:00
|
|
|
return nil, chainntnfs.TxNotFoundManually,
|
|
|
|
fmt.Errorf("unable to get block with hash "+
|
|
|
|
"%v: %v", blockHash, err)
|
2018-02-26 21:37:58 +03:00
|
|
|
}
|
|
|
|
|
2018-12-07 08:14:19 +03:00
|
|
|
// For every transaction in the block, check which one matches
|
|
|
|
// our request. If we find one that does, we can dispatch its
|
|
|
|
// confirmation details.
|
|
|
|
for txIndex, tx := range block.Transactions {
|
|
|
|
if !confRequest.MatchesTx(tx) {
|
|
|
|
continue
|
2018-02-26 21:37:58 +03:00
|
|
|
}
|
2018-12-07 08:14:19 +03:00
|
|
|
|
|
|
|
return &chainntnfs.TxConfirmation{
|
2018-12-11 05:24:04 +03:00
|
|
|
Tx: tx,
|
2018-12-07 08:14:19 +03:00
|
|
|
BlockHash: blockHash,
|
|
|
|
BlockHeight: height,
|
|
|
|
TxIndex: uint32(txIndex),
|
|
|
|
}, chainntnfs.TxFoundManually, nil
|
2018-02-26 21:37:58 +03:00
|
|
|
}
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
2018-02-26 21:37:58 +03:00
|
|
|
|
|
|
|
// If we reach here, then we were not able to find the transaction
|
|
|
|
// within a block, so we avoid returning an error.
|
2018-08-24 15:13:28 +03:00
|
|
|
return nil, chainntnfs.TxNotFoundManually, nil
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
|
2018-08-09 10:05:29 +03:00
|
|
|
// handleBlockConnected applies a chain update for a new block. Any watched
|
|
|
|
// transactions included this block will processed to either send notifications
|
|
|
|
// now or after numConfirmations confs.
|
|
|
|
func (b *BitcoindNotifier) handleBlockConnected(block chainntnfs.BlockEpoch) error {
|
2018-10-12 03:30:40 +03:00
|
|
|
// First, we'll fetch the raw block as we'll need to gather all the
|
|
|
|
// transactions to determine whether any are relevant to our registered
|
|
|
|
// clients.
|
2018-08-09 10:05:29 +03:00
|
|
|
rawBlock, err := b.chainConn.GetBlock(block.Hash)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to get block: %v", err)
|
|
|
|
}
|
|
|
|
txns := btcutil.NewBlock(rawBlock).Transactions()
|
2018-10-12 03:30:40 +03:00
|
|
|
|
|
|
|
// We'll then extend the txNotifier's height with the information of
|
|
|
|
// this new block, which will handle all of the notification logic for
|
|
|
|
// us.
|
|
|
|
err = b.txNotifier.ConnectTip(block.Hash, uint32(block.Height), txns)
|
2018-08-09 10:05:29 +03:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to connect tip: %v", err)
|
|
|
|
}
|
|
|
|
|
2018-08-15 03:55:29 +03:00
|
|
|
chainntnfs.Log.Infof("New block: height=%v, sha=%v", block.Height,
|
|
|
|
block.Hash)
|
|
|
|
|
2018-10-12 03:30:40 +03:00
|
|
|
// Now that we've guaranteed the new block extends the txNotifier's
|
|
|
|
// current tip, we'll proceed to dispatch notifications to all of our
|
|
|
|
// registered clients whom have had notifications fulfilled. Before
|
|
|
|
// doing so, we'll make sure update our in memory state in order to
|
|
|
|
// satisfy any client requests based upon the new block.
|
2018-08-25 05:45:45 +03:00
|
|
|
b.bestBlock = block
|
|
|
|
|
|
|
|
b.notifyBlockEpochs(block.Height, block.Hash)
|
2018-10-12 03:30:40 +03:00
|
|
|
return b.txNotifier.NotifyHeight(uint32(block.Height))
|
2018-08-09 10:05:29 +03:00
|
|
|
}
|
|
|
|
|
2017-11-10 03:30:20 +03:00
|
|
|
// notifyBlockEpochs notifies all registered block epoch clients of the newly
|
|
|
|
// connected block to the main chain.
|
|
|
|
func (b *BitcoindNotifier) notifyBlockEpochs(newHeight int32, newSha *chainhash.Hash) {
|
2018-08-09 10:05:29 +03:00
|
|
|
for _, client := range b.blockEpochClients {
|
|
|
|
b.notifyBlockEpochClient(client, newHeight, newSha)
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
2018-08-09 10:05:29 +03:00
|
|
|
}
|
2017-11-10 03:30:20 +03:00
|
|
|
|
2018-08-09 10:05:29 +03:00
|
|
|
// notifyBlockEpochClient sends a registered block epoch client a notification
|
|
|
|
// about a specific block.
|
|
|
|
func (b *BitcoindNotifier) notifyBlockEpochClient(epochClient *blockEpochRegistration,
|
|
|
|
height int32, sha *chainhash.Hash) {
|
2017-11-10 03:30:20 +03:00
|
|
|
|
2018-08-09 10:05:29 +03:00
|
|
|
epoch := &chainntnfs.BlockEpoch{
|
|
|
|
Height: height,
|
|
|
|
Hash: sha,
|
|
|
|
}
|
2017-11-10 03:30:20 +03:00
|
|
|
|
2018-08-09 10:05:29 +03:00
|
|
|
select {
|
|
|
|
case epochClient.epochQueue.ChanIn() <- epoch:
|
|
|
|
case <-epochClient.cancelChan:
|
|
|
|
case <-b.quit:
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterSpendNtfn registers an intent to be notified once the target
|
2018-12-07 08:14:28 +03:00
|
|
|
// outpoint/output script has been spent by a transaction on-chain. When
|
|
|
|
// intending to be notified of the spend of an output script, a nil outpoint
|
|
|
|
// must be used. The heightHint should represent the earliest height in the
|
|
|
|
// chain of the transaction that spent the outpoint/output script.
|
|
|
|
//
|
|
|
|
// Once a spend of has been detected, the details of the spending event will be
|
|
|
|
// sent across the 'Spend' channel.
|
2017-11-10 03:30:20 +03:00
|
|
|
func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
2018-07-18 05:02:25 +03:00
|
|
|
pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) {
|
2017-11-10 03:30:20 +03:00
|
|
|
|
2018-10-05 12:07:55 +03:00
|
|
|
// First, we'll construct a spend notification request and hand it off
|
|
|
|
// to the txNotifier.
|
|
|
|
spendID := atomic.AddUint64(&b.spendClientCounter, 1)
|
2018-12-07 08:14:28 +03:00
|
|
|
spendRequest, err := chainntnfs.NewSpendRequest(outpoint, pkScript)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2018-08-15 03:55:29 +03:00
|
|
|
}
|
2018-10-05 12:07:55 +03:00
|
|
|
ntfn := &chainntnfs.SpendNtfn{
|
2018-12-07 08:14:28 +03:00
|
|
|
SpendID: spendID,
|
|
|
|
SpendRequest: spendRequest,
|
|
|
|
Event: chainntnfs.NewSpendEvent(func() {
|
|
|
|
b.txNotifier.CancelSpend(spendRequest, spendID)
|
|
|
|
}),
|
2018-10-05 12:07:55 +03:00
|
|
|
HeightHint: heightHint,
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
|
2018-12-07 08:14:28 +03:00
|
|
|
historicalDispatch, _, err := b.txNotifier.RegisterSpend(ntfn)
|
2018-10-05 12:07:55 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-12-07 08:14:28 +03:00
|
|
|
// We'll then request the backend to notify us when it has detected the
|
|
|
|
// outpoint/output script as spent.
|
|
|
|
//
|
|
|
|
// TODO(wilmer): use LoadFilter API instead.
|
|
|
|
if spendRequest.OutPoint == chainntnfs.ZeroOutPoint {
|
|
|
|
addr, err := spendRequest.PkScript.Address(b.chainParams)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
addrs := []btcutil.Address{addr}
|
|
|
|
if err := b.chainConn.NotifyReceived(addrs); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ops := []*wire.OutPoint{&spendRequest.OutPoint}
|
|
|
|
if err := b.chainConn.NotifySpent(ops); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-05 12:07:55 +03:00
|
|
|
// If the txNotifier didn't return any details to perform a historical
|
|
|
|
// scan of the chain, then we can return early as there's nothing left
|
|
|
|
// for us to do.
|
|
|
|
if historicalDispatch == nil {
|
|
|
|
return ntfn.Event, nil
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
|
2018-12-07 08:14:28 +03:00
|
|
|
// Otherwise, we'll need to dispatch a historical rescan to determine if
|
|
|
|
// the outpoint was already spent at a previous height.
|
|
|
|
//
|
|
|
|
// We'll short-circuit the path when dispatching the spend of a script,
|
|
|
|
// rather than an outpoint, as there aren't any additional checks we can
|
|
|
|
// make for scripts.
|
|
|
|
if spendRequest.OutPoint == chainntnfs.ZeroOutPoint {
|
|
|
|
select {
|
|
|
|
case b.notificationRegistry <- historicalDispatch:
|
|
|
|
case <-b.quit:
|
2018-12-11 05:25:41 +03:00
|
|
|
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
2018-12-07 08:14:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return ntfn.Event, nil
|
2018-03-28 11:22:41 +03:00
|
|
|
}
|
|
|
|
|
2018-12-07 08:14:28 +03:00
|
|
|
// When dispatching spends of outpoints, there are a number of checks we
|
|
|
|
// can make to start our rescan from a better height or completely avoid
|
|
|
|
// it.
|
|
|
|
//
|
|
|
|
// We'll start by checking the backend's UTXO set to determine whether
|
|
|
|
// the outpoint has been spent. If it hasn't, we can return to the
|
|
|
|
// caller as well.
|
|
|
|
txOut, err := b.chainConn.GetTxOut(
|
|
|
|
&spendRequest.OutPoint.Hash, spendRequest.OutPoint.Index, true,
|
|
|
|
)
|
2017-11-10 03:30:20 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-08-15 03:55:29 +03:00
|
|
|
if txOut != nil {
|
2018-10-05 12:07:55 +03:00
|
|
|
// We'll let the txNotifier know the outpoint is still unspent
|
|
|
|
// in order to begin updating its spend hint.
|
2018-12-07 08:14:28 +03:00
|
|
|
err := b.txNotifier.UpdateSpendDetails(spendRequest, nil)
|
2018-08-15 03:55:29 +03:00
|
|
|
if err != nil {
|
2018-10-05 12:07:55 +03:00
|
|
|
return nil, err
|
2018-02-26 21:37:58 +03:00
|
|
|
}
|
|
|
|
|
2018-10-05 12:07:55 +03:00
|
|
|
return ntfn.Event, nil
|
|
|
|
}
|
2017-11-10 03:30:20 +03:00
|
|
|
|
2018-12-07 08:14:28 +03:00
|
|
|
// Since the outpoint was spent, as it no longer exists within the UTXO
|
|
|
|
// set, we'll determine when it happened by scanning the chain.
|
2018-10-05 12:07:55 +03:00
|
|
|
//
|
|
|
|
// As a minimal optimization, we'll query the backend's transaction
|
|
|
|
// index (if enabled) to determine if we have a better rescan starting
|
|
|
|
// height. We can do this as the GetRawTransaction call will return the
|
|
|
|
// hash of the block it was included in within the chain.
|
2018-12-07 08:14:28 +03:00
|
|
|
tx, err := b.chainConn.GetRawTransactionVerbose(&spendRequest.OutPoint.Hash)
|
2018-10-05 12:07:55 +03:00
|
|
|
if err != nil {
|
|
|
|
// Avoid returning an error if the transaction was not found to
|
|
|
|
// proceed with fallback methods.
|
|
|
|
jsonErr, ok := err.(*btcjson.RPCError)
|
|
|
|
if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo {
|
2018-12-07 08:14:28 +03:00
|
|
|
return nil, fmt.Errorf("unable to query for txid %v: %v",
|
|
|
|
spendRequest.OutPoint.Hash, err)
|
2018-10-05 12:07:55 +03:00
|
|
|
}
|
|
|
|
}
|
2018-04-07 04:25:41 +03:00
|
|
|
|
2018-10-05 12:07:55 +03:00
|
|
|
// If the transaction index was enabled, we'll use the block's hash to
|
|
|
|
// retrieve its height and check whether it provides a better starting
|
|
|
|
// point for our rescan.
|
|
|
|
if tx != nil {
|
|
|
|
// If the transaction containing the outpoint hasn't confirmed
|
|
|
|
// on-chain, then there's no need to perform a rescan.
|
|
|
|
if tx.BlockHash == "" {
|
|
|
|
return ntfn.Event, nil
|
|
|
|
}
|
2018-04-07 04:25:41 +03:00
|
|
|
|
2018-10-05 12:07:55 +03:00
|
|
|
blockHash, err := chainhash.NewHashFromStr(tx.BlockHash)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
blockHeight, err := b.chainConn.GetBlockHeight(blockHash)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-07-27 07:33:32 +03:00
|
|
|
|
2018-10-05 12:07:55 +03:00
|
|
|
if uint32(blockHeight) > historicalDispatch.StartHeight {
|
|
|
|
historicalDispatch.StartHeight = uint32(blockHeight)
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-05 12:07:55 +03:00
|
|
|
// Now that we've determined the starting point of our rescan, we can
|
2018-12-07 08:14:28 +03:00
|
|
|
// dispatch it and return.
|
2018-10-05 12:07:55 +03:00
|
|
|
select {
|
|
|
|
case b.notificationRegistry <- historicalDispatch:
|
|
|
|
case <-b.quit:
|
2018-12-11 05:25:41 +03:00
|
|
|
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
2018-10-05 12:07:55 +03:00
|
|
|
}
|
2018-12-07 08:14:28 +03:00
|
|
|
|
|
|
|
return ntfn.Event, nil
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
|
2019-04-02 04:20:52 +03:00
|
|
|
// historicalSpendDetails attempts to manually scan the chain within the given
|
|
|
|
// height range for a transaction that spends the given outpoint/output script.
|
|
|
|
// If one is found, the spend details are assembled and returned to the caller.
|
|
|
|
// If the spend is not found, a nil spend detail will be returned.
|
|
|
|
func (b *BitcoindNotifier) historicalSpendDetails(
|
|
|
|
spendRequest chainntnfs.SpendRequest, startHeight, endHeight uint32) (
|
|
|
|
*chainntnfs.SpendDetail, error) {
|
2018-07-27 07:33:32 +03:00
|
|
|
|
|
|
|
// Begin scanning blocks at every height to determine if the outpoint
|
|
|
|
// was spent.
|
2018-11-23 00:58:58 +03:00
|
|
|
for height := endHeight; height >= startHeight && height > 0; height-- {
|
2018-07-27 07:33:32 +03:00
|
|
|
// Ensure we haven't been requested to shut down before
|
|
|
|
// processing the next height.
|
|
|
|
select {
|
|
|
|
case <-b.quit:
|
2019-04-02 04:20:52 +03:00
|
|
|
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
2018-07-27 07:33:32 +03:00
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
2018-10-05 12:07:55 +03:00
|
|
|
// First, we'll fetch the block for the current height.
|
2018-07-27 07:33:32 +03:00
|
|
|
blockHash, err := b.chainConn.GetBlockHash(int64(height))
|
|
|
|
if err != nil {
|
2019-04-02 04:20:52 +03:00
|
|
|
return nil, fmt.Errorf("unable to retrieve hash for "+
|
|
|
|
"block with height %d: %v", height, err)
|
2018-07-27 07:33:32 +03:00
|
|
|
}
|
|
|
|
block, err := b.chainConn.GetBlock(blockHash)
|
|
|
|
if err != nil {
|
2019-04-02 04:20:52 +03:00
|
|
|
return nil, fmt.Errorf("unable to retrieve block "+
|
|
|
|
"with hash %v: %v", blockHash, err)
|
2018-07-27 07:33:32 +03:00
|
|
|
}
|
|
|
|
|
2018-12-07 08:14:28 +03:00
|
|
|
// Then, we'll manually go over every input in every transaction
|
|
|
|
// in it and determine whether it spends the request in
|
|
|
|
// question. If we find one, we'll dispatch the spend details.
|
2018-07-27 07:33:32 +03:00
|
|
|
for _, tx := range block.Transactions {
|
2018-12-07 08:14:28 +03:00
|
|
|
matches, inputIdx, err := spendRequest.MatchesTx(tx)
|
|
|
|
if err != nil {
|
2019-04-02 04:20:52 +03:00
|
|
|
return nil, err
|
2018-12-07 08:14:28 +03:00
|
|
|
}
|
|
|
|
if !matches {
|
|
|
|
continue
|
|
|
|
}
|
2018-07-27 07:33:32 +03:00
|
|
|
|
2018-12-07 08:14:28 +03:00
|
|
|
txHash := tx.TxHash()
|
2019-04-02 04:20:52 +03:00
|
|
|
return &chainntnfs.SpendDetail{
|
2018-12-07 08:14:28 +03:00
|
|
|
SpentOutPoint: &tx.TxIn[inputIdx].PreviousOutPoint,
|
|
|
|
SpenderTxHash: &txHash,
|
|
|
|
SpendingTx: tx,
|
|
|
|
SpenderInputIndex: inputIdx,
|
|
|
|
SpendingHeight: int32(height),
|
2019-04-02 04:20:52 +03:00
|
|
|
}, nil
|
2018-07-27 07:33:32 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-02 04:20:52 +03:00
|
|
|
return nil, nil
|
2018-07-27 07:33:32 +03:00
|
|
|
}
|
|
|
|
|
2018-12-07 08:14:19 +03:00
|
|
|
// RegisterConfirmationsNtfn registers an intent to be notified once the target
|
|
|
|
// txid/output script has reached numConfs confirmations on-chain. When
|
|
|
|
// intending to be notified of the confirmation of an output script, a nil txid
|
|
|
|
// must be used. The heightHint should represent the earliest height at which
|
|
|
|
// the txid/output script could have been included in the chain.
|
|
|
|
//
|
|
|
|
// Progress on the number of confirmations left can be read from the 'Updates'
|
|
|
|
// channel. Once it has reached all of its confirmations, a notification will be
|
|
|
|
// sent across the 'Confirmed' channel.
|
2017-11-10 03:30:20 +03:00
|
|
|
func (b *BitcoindNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
2018-12-07 08:14:19 +03:00
|
|
|
pkScript []byte,
|
|
|
|
numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
|
2017-11-10 03:30:20 +03:00
|
|
|
|
2018-08-15 03:54:21 +03:00
|
|
|
// Construct a notification request for the transaction and send it to
|
|
|
|
// the main event loop.
|
2018-12-11 05:27:25 +03:00
|
|
|
confID := atomic.AddUint64(&b.confClientCounter, 1)
|
2018-12-07 08:14:19 +03:00
|
|
|
confRequest, err := chainntnfs.NewConfRequest(txid, pkScript)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-08-25 06:09:53 +03:00
|
|
|
ntfn := &chainntnfs.ConfNtfn{
|
2018-12-11 05:27:25 +03:00
|
|
|
ConfID: confID,
|
2018-12-07 08:14:19 +03:00
|
|
|
ConfRequest: confRequest,
|
2018-08-25 06:09:53 +03:00
|
|
|
NumConfirmations: numConfs,
|
2018-12-11 05:27:25 +03:00
|
|
|
Event: chainntnfs.NewConfirmationEvent(numConfs, func() {
|
|
|
|
b.txNotifier.CancelConf(confRequest, confID)
|
|
|
|
}),
|
|
|
|
HeightHint: heightHint,
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
|
2018-12-07 08:14:19 +03:00
|
|
|
chainntnfs.Log.Infof("New confirmation subscription: %v, num_confs=%v",
|
|
|
|
confRequest, numConfs)
|
2018-08-27 07:36:17 +03:00
|
|
|
|
2018-10-05 12:07:55 +03:00
|
|
|
// Register the conf notification with the TxNotifier. A non-nil value
|
2018-08-27 07:36:17 +03:00
|
|
|
// for `dispatch` will be returned if we are required to perform a
|
|
|
|
// manual scan for the confirmation. Otherwise the notifier will begin
|
|
|
|
// watching at tip for the transaction to confirm.
|
2018-12-07 08:14:19 +03:00
|
|
|
dispatch, _, err := b.txNotifier.RegisterConf(ntfn)
|
2018-08-27 07:36:17 +03:00
|
|
|
if err != nil {
|
2018-07-27 07:32:55 +03:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-08-27 07:36:17 +03:00
|
|
|
if dispatch == nil {
|
|
|
|
return ntfn.Event, nil
|
|
|
|
}
|
|
|
|
|
2017-11-10 03:30:20 +03:00
|
|
|
select {
|
2018-08-27 07:36:17 +03:00
|
|
|
case b.notificationRegistry <- dispatch:
|
2017-11-10 03:30:20 +03:00
|
|
|
return ntfn.Event, nil
|
2018-07-27 07:32:55 +03:00
|
|
|
case <-b.quit:
|
2018-12-11 05:25:41 +03:00
|
|
|
return nil, chainntnfs.ErrChainNotifierShuttingDown
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// blockEpochRegistration represents a client's intent to receive a
|
|
|
|
// notification with each newly connected block.
|
|
|
|
type blockEpochRegistration struct {
|
|
|
|
epochID uint64
|
|
|
|
|
|
|
|
epochChan chan *chainntnfs.BlockEpoch
|
|
|
|
|
2018-10-12 18:08:14 +03:00
|
|
|
epochQueue *queue.ConcurrentQueue
|
chainntnfs: ensure all block epoch notifications are sent *in order*
In this commit, we fix a lingering bug related to the way that we
deliver block epoch notifications to end users. Before this commit, we
would launch a new goroutine for *each block*. This was done in order
to ensure that the notification dispatch wouldn’t block the main
goroutine that was dispatching the notifications. This method archived
the goal, but had a nasty side effect that the goroutines could be
re-ordered during scheduling, meaning that in the case of fast
successive blocks, then notifications would be delivered out of order.
Receiving out of order notifications is either disallowed, or can cause
sub-systems that rely on these notifications to get into weird states.
In order to fix this issue, we’ll no longer launch a new goroutine to
deliver each notification to an awaiting client. Instead, each client
will now gain a concurrent in-order queue for notification delivery.
Due to the internal design of chainntnfs.ConcurrentQueue, the caller
should never block, yet the receivers will receive notifications in
order. This change solves the re-ordering issue and also minimizes the
number of goroutines that we’ll create in order to deliver block epoch
notifications.
2018-02-10 03:13:21 +03:00
|
|
|
|
2018-08-09 10:05:27 +03:00
|
|
|
bestBlock *chainntnfs.BlockEpoch
|
|
|
|
|
|
|
|
errorChan chan error
|
|
|
|
|
2017-11-10 03:30:20 +03:00
|
|
|
cancelChan chan struct{}
|
|
|
|
|
|
|
|
wg sync.WaitGroup
|
|
|
|
}
|
|
|
|
|
|
|
|
// epochCancel is a message sent to the BitcoindNotifier when a client wishes
|
|
|
|
// to cancel an outstanding epoch notification that has yet to be dispatched.
|
|
|
|
type epochCancel struct {
|
|
|
|
epochID uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterBlockEpochNtfn returns a BlockEpochEvent which subscribes the
|
|
|
|
// caller to receive notifications, of each new block connected to the main
|
2018-08-09 10:05:27 +03:00
|
|
|
// chain. Clients have the option of passing in their best known block, which
|
2018-12-11 05:29:28 +03:00
|
|
|
// the notifier uses to check if they are behind on blocks and catch them up. If
|
|
|
|
// they do not provide one, then a notification will be dispatched immediately
|
|
|
|
// for the current tip of the chain upon a successful registration.
|
2018-08-09 10:05:27 +03:00
|
|
|
func (b *BitcoindNotifier) RegisterBlockEpochNtfn(
|
|
|
|
bestBlock *chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent, error) {
|
|
|
|
|
chainntnfs: ensure all block epoch notifications are sent *in order*
In this commit, we fix a lingering bug related to the way that we
deliver block epoch notifications to end users. Before this commit, we
would launch a new goroutine for *each block*. This was done in order
to ensure that the notification dispatch wouldn’t block the main
goroutine that was dispatching the notifications. This method archived
the goal, but had a nasty side effect that the goroutines could be
re-ordered during scheduling, meaning that in the case of fast
successive blocks, then notifications would be delivered out of order.
Receiving out of order notifications is either disallowed, or can cause
sub-systems that rely on these notifications to get into weird states.
In order to fix this issue, we’ll no longer launch a new goroutine to
deliver each notification to an awaiting client. Instead, each client
will now gain a concurrent in-order queue for notification delivery.
Due to the internal design of chainntnfs.ConcurrentQueue, the caller
should never block, yet the receivers will receive notifications in
order. This change solves the re-ordering issue and also minimizes the
number of goroutines that we’ll create in order to deliver block epoch
notifications.
2018-02-10 03:13:21 +03:00
|
|
|
reg := &blockEpochRegistration{
|
2018-10-12 18:08:14 +03:00
|
|
|
epochQueue: queue.NewConcurrentQueue(20),
|
2017-11-10 03:30:20 +03:00
|
|
|
epochChan: make(chan *chainntnfs.BlockEpoch, 20),
|
|
|
|
cancelChan: make(chan struct{}),
|
|
|
|
epochID: atomic.AddUint64(&b.epochClientCounter, 1),
|
2018-08-09 10:05:27 +03:00
|
|
|
bestBlock: bestBlock,
|
|
|
|
errorChan: make(chan error, 1),
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
chainntnfs: ensure all block epoch notifications are sent *in order*
In this commit, we fix a lingering bug related to the way that we
deliver block epoch notifications to end users. Before this commit, we
would launch a new goroutine for *each block*. This was done in order
to ensure that the notification dispatch wouldn’t block the main
goroutine that was dispatching the notifications. This method archived
the goal, but had a nasty side effect that the goroutines could be
re-ordered during scheduling, meaning that in the case of fast
successive blocks, then notifications would be delivered out of order.
Receiving out of order notifications is either disallowed, or can cause
sub-systems that rely on these notifications to get into weird states.
In order to fix this issue, we’ll no longer launch a new goroutine to
deliver each notification to an awaiting client. Instead, each client
will now gain a concurrent in-order queue for notification delivery.
Due to the internal design of chainntnfs.ConcurrentQueue, the caller
should never block, yet the receivers will receive notifications in
order. This change solves the re-ordering issue and also minimizes the
number of goroutines that we’ll create in order to deliver block epoch
notifications.
2018-02-10 03:13:21 +03:00
|
|
|
reg.epochQueue.Start()
|
|
|
|
|
|
|
|
// Before we send the request to the main goroutine, we'll launch a new
|
|
|
|
// goroutine to proxy items added to our queue to the client itself.
|
|
|
|
// This ensures that all notifications are received *in order*.
|
|
|
|
reg.wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer reg.wg.Done()
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case ntfn := <-reg.epochQueue.ChanOut():
|
|
|
|
blockNtfn := ntfn.(*chainntnfs.BlockEpoch)
|
|
|
|
select {
|
|
|
|
case reg.epochChan <- blockNtfn:
|
|
|
|
|
|
|
|
case <-reg.cancelChan:
|
|
|
|
return
|
|
|
|
|
|
|
|
case <-b.quit:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
case <-reg.cancelChan:
|
|
|
|
return
|
|
|
|
|
|
|
|
case <-b.quit:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2017-11-10 03:30:20 +03:00
|
|
|
|
|
|
|
select {
|
|
|
|
case <-b.quit:
|
chainntnfs: ensure all block epoch notifications are sent *in order*
In this commit, we fix a lingering bug related to the way that we
deliver block epoch notifications to end users. Before this commit, we
would launch a new goroutine for *each block*. This was done in order
to ensure that the notification dispatch wouldn’t block the main
goroutine that was dispatching the notifications. This method archived
the goal, but had a nasty side effect that the goroutines could be
re-ordered during scheduling, meaning that in the case of fast
successive blocks, then notifications would be delivered out of order.
Receiving out of order notifications is either disallowed, or can cause
sub-systems that rely on these notifications to get into weird states.
In order to fix this issue, we’ll no longer launch a new goroutine to
deliver each notification to an awaiting client. Instead, each client
will now gain a concurrent in-order queue for notification delivery.
Due to the internal design of chainntnfs.ConcurrentQueue, the caller
should never block, yet the receivers will receive notifications in
order. This change solves the re-ordering issue and also minimizes the
number of goroutines that we’ll create in order to deliver block epoch
notifications.
2018-02-10 03:13:21 +03:00
|
|
|
// As we're exiting before the registration could be sent,
|
|
|
|
// we'll stop the queue now ourselves.
|
|
|
|
reg.epochQueue.Stop()
|
|
|
|
|
2017-11-10 03:30:20 +03:00
|
|
|
return nil, errors.New("chainntnfs: system interrupt while " +
|
|
|
|
"attempting to register for block epoch notification.")
|
chainntnfs: ensure all block epoch notifications are sent *in order*
In this commit, we fix a lingering bug related to the way that we
deliver block epoch notifications to end users. Before this commit, we
would launch a new goroutine for *each block*. This was done in order
to ensure that the notification dispatch wouldn’t block the main
goroutine that was dispatching the notifications. This method archived
the goal, but had a nasty side effect that the goroutines could be
re-ordered during scheduling, meaning that in the case of fast
successive blocks, then notifications would be delivered out of order.
Receiving out of order notifications is either disallowed, or can cause
sub-systems that rely on these notifications to get into weird states.
In order to fix this issue, we’ll no longer launch a new goroutine to
deliver each notification to an awaiting client. Instead, each client
will now gain a concurrent in-order queue for notification delivery.
Due to the internal design of chainntnfs.ConcurrentQueue, the caller
should never block, yet the receivers will receive notifications in
order. This change solves the re-ordering issue and also minimizes the
number of goroutines that we’ll create in order to deliver block epoch
notifications.
2018-02-10 03:13:21 +03:00
|
|
|
case b.notificationRegistry <- reg:
|
2017-11-10 03:30:20 +03:00
|
|
|
return &chainntnfs.BlockEpochEvent{
|
chainntnfs: ensure all block epoch notifications are sent *in order*
In this commit, we fix a lingering bug related to the way that we
deliver block epoch notifications to end users. Before this commit, we
would launch a new goroutine for *each block*. This was done in order
to ensure that the notification dispatch wouldn’t block the main
goroutine that was dispatching the notifications. This method archived
the goal, but had a nasty side effect that the goroutines could be
re-ordered during scheduling, meaning that in the case of fast
successive blocks, then notifications would be delivered out of order.
Receiving out of order notifications is either disallowed, or can cause
sub-systems that rely on these notifications to get into weird states.
In order to fix this issue, we’ll no longer launch a new goroutine to
deliver each notification to an awaiting client. Instead, each client
will now gain a concurrent in-order queue for notification delivery.
Due to the internal design of chainntnfs.ConcurrentQueue, the caller
should never block, yet the receivers will receive notifications in
order. This change solves the re-ordering issue and also minimizes the
number of goroutines that we’ll create in order to deliver block epoch
notifications.
2018-02-10 03:13:21 +03:00
|
|
|
Epochs: reg.epochChan,
|
2017-11-10 03:30:20 +03:00
|
|
|
Cancel: func() {
|
|
|
|
cancel := &epochCancel{
|
chainntnfs: ensure all block epoch notifications are sent *in order*
In this commit, we fix a lingering bug related to the way that we
deliver block epoch notifications to end users. Before this commit, we
would launch a new goroutine for *each block*. This was done in order
to ensure that the notification dispatch wouldn’t block the main
goroutine that was dispatching the notifications. This method archived
the goal, but had a nasty side effect that the goroutines could be
re-ordered during scheduling, meaning that in the case of fast
successive blocks, then notifications would be delivered out of order.
Receiving out of order notifications is either disallowed, or can cause
sub-systems that rely on these notifications to get into weird states.
In order to fix this issue, we’ll no longer launch a new goroutine to
deliver each notification to an awaiting client. Instead, each client
will now gain a concurrent in-order queue for notification delivery.
Due to the internal design of chainntnfs.ConcurrentQueue, the caller
should never block, yet the receivers will receive notifications in
order. This change solves the re-ordering issue and also minimizes the
number of goroutines that we’ll create in order to deliver block epoch
notifications.
2018-02-10 03:13:21 +03:00
|
|
|
epochID: reg.epochID,
|
2017-11-10 03:30:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Submit epoch cancellation to notification dispatcher.
|
|
|
|
select {
|
|
|
|
case b.notificationCancels <- cancel:
|
|
|
|
// Cancellation is being handled, drain the epoch channel until it is
|
|
|
|
// closed before yielding to caller.
|
|
|
|
for {
|
|
|
|
select {
|
chainntnfs: ensure all block epoch notifications are sent *in order*
In this commit, we fix a lingering bug related to the way that we
deliver block epoch notifications to end users. Before this commit, we
would launch a new goroutine for *each block*. This was done in order
to ensure that the notification dispatch wouldn’t block the main
goroutine that was dispatching the notifications. This method archived
the goal, but had a nasty side effect that the goroutines could be
re-ordered during scheduling, meaning that in the case of fast
successive blocks, then notifications would be delivered out of order.
Receiving out of order notifications is either disallowed, or can cause
sub-systems that rely on these notifications to get into weird states.
In order to fix this issue, we’ll no longer launch a new goroutine to
deliver each notification to an awaiting client. Instead, each client
will now gain a concurrent in-order queue for notification delivery.
Due to the internal design of chainntnfs.ConcurrentQueue, the caller
should never block, yet the receivers will receive notifications in
order. This change solves the re-ordering issue and also minimizes the
number of goroutines that we’ll create in order to deliver block epoch
notifications.
2018-02-10 03:13:21 +03:00
|
|
|
case _, ok := <-reg.epochChan:
|
2017-11-10 03:30:20 +03:00
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case <-b.quit:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case <-b.quit:
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
}
|