52db5ed682
In this commit, we extend the NeutrinoNotifier to support registering scripts for spends notifications. Once the script has been detected as spent within the chain, a spend notification will be dispatched through the Spend channel of the SpendEvent returned upon registration. For scripts that have been spent in the past, the rescan logic has been modified to match on the script rather than the outpoint. A concurrent queue for relevant transactions has been added to proxy notifications from the underlying rescan to the txNotifier. This is needed for scripts, as we cannot perform a historical rescan for scripts through `GetUtxo`, like we do with outpoints. For scripts that are unspent, a filter update is sent to the underlying rescan to ensure that we match and dispatch on the script when processing new blocks. Along the way, we also address an issue where we'd miss detecting that an outpoint/script has been spent in the future due to not receiving a historical dispatch request from the underlying txNotifier. To fix this, we ensure that we always request the backend to notify us of the spend once it detects it at tip, regardless of whether a historical rescan was detected or not.
99 lines
2.9 KiB
Go
99 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,
|
|
)
|
|
|
|
// 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()
|
|
n.txUpdates.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
|
|
}
|