lnd.xprv/chainntnfs/neutrinonotify/neutrino_dev.go

100 lines
2.9 KiB
Go

// +build dev
package neutrinonotify
import (
"fmt"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/lightninglabs/neutrino"
"github.com/lightningnetwork/lnd/chainntnfs"
)
// UnsafeStart starts the notifier with a specified best height and optional
// best hash. Its bestHeight, txNotifier and neutrino node are initialized with
// bestHeight. The parameter generateBlocks is necessary for the bitcoind
// notifier to ensure we drain all notifications up to syncHeight, since if they
// are generated ahead of UnsafeStart the chainConn may start up with an
// outdated best block and miss sending ntfns. Used for testing.
func (n *NeutrinoNotifier) UnsafeStart(bestHeight int32,
bestHash *chainhash.Hash, syncHeight int32,
generateBlocks func() error) error {
// We'll obtain the latest block height of the p2p node. We'll
// start the auto-rescan from this point. Once a caller actually wishes
// to register a chain view, the rescan state will be rewound
// accordingly.
startingPoint, err := n.p2pNode.BestBlock()
if err != nil {
return err
}
// Next, we'll create our set of rescan options. Currently it's
// required that a user MUST set an addr/outpoint/txid when creating a
// rescan. To get around this, we'll add a "zero" outpoint, that won't
// actually be matched.
var zeroInput neutrino.InputWithScript
rescanOptions := []neutrino.RescanOption{
neutrino.StartBlock(startingPoint),
neutrino.QuitChan(n.quit),
neutrino.NotificationHandlers(
rpcclient.NotificationHandlers{
OnFilteredBlockConnected: n.onFilteredBlockConnected,
OnFilteredBlockDisconnected: n.onFilteredBlockDisconnected,
},
),
neutrino.WatchInputs(zeroInput),
}
n.txNotifier = chainntnfs.NewTxNotifier(
uint32(bestHeight), chainntnfs.ReorgSafetyLimit,
n.confirmHintCache, n.spendHintCache,
)
n.chainConn = &NeutrinoChainConn{n.p2pNode}
// Finally, we'll create our rescan struct, start it, and launch all
// the goroutines we need to operate this ChainNotifier instance.
n.chainView = n.p2pNode.NewRescan(rescanOptions...)
n.rescanErr = n.chainView.Start()
n.chainUpdates.Start()
if generateBlocks != nil {
// Ensure no block notifications are pending when we start the
// notification dispatcher goroutine.
// First generate the blocks, then drain the notifications
// for the generated blocks.
if err := generateBlocks(); err != nil {
return err
}
timeout := time.After(60 * time.Second)
loop:
for {
select {
case ntfn := <-n.chainUpdates.ChanOut():
lastReceivedNtfn := ntfn.(*filteredBlock)
if lastReceivedNtfn.height >= uint32(syncHeight) {
break loop
}
case <-timeout:
return fmt.Errorf("unable to catch up to height %d",
syncHeight)
}
}
}
// Run notificationDispatcher after setting the notifier's best height
// to avoid a race condition.
n.bestHeight = uint32(bestHeight)
n.wg.Add(1)
go n.notificationDispatcher()
return nil
}