mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
136 lines
3.5 KiB
Go
136 lines
3.5 KiB
Go
|
package chainntnfs
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"sync"
|
||
|
"sync/atomic"
|
||
|
|
||
|
"github.com/btcsuite/btcd/wire"
|
||
|
)
|
||
|
|
||
|
// BestBlockView is an interface that allows the querying of the most
|
||
|
// up-to-date blockchain state with low overhead. Valid implementations of this
|
||
|
// interface must track the latest chain state.
|
||
|
type BestBlockView interface {
|
||
|
// BestHeight gets the most recent block height known to the view.
|
||
|
BestHeight() (uint32, error)
|
||
|
|
||
|
// BestBlockHeader gets the most recent block header known to the view.
|
||
|
BestBlockHeader() (*wire.BlockHeader, error)
|
||
|
}
|
||
|
|
||
|
// BestBlockTracker is a tiny subsystem that tracks the blockchain tip
|
||
|
// and saves the most recent tip information in memory for querying. It is a
|
||
|
// valid implementation of BestBlockView and additionally includes
|
||
|
// methods for starting and stopping the system.
|
||
|
type BestBlockTracker struct {
|
||
|
notifier ChainNotifier
|
||
|
blockNtfnStream *BlockEpochEvent
|
||
|
current atomic.Pointer[BlockEpoch]
|
||
|
mu sync.Mutex
|
||
|
quit chan struct{}
|
||
|
wg sync.WaitGroup
|
||
|
}
|
||
|
|
||
|
// This is a compile time check to ensure that BestBlockTracker implements
|
||
|
// BestBlockView.
|
||
|
var _ BestBlockView = (*BestBlockTracker)(nil)
|
||
|
|
||
|
// NewBestBlockTracker creates a new BestBlockTracker that isn't running yet.
|
||
|
// It will not provide up to date information unless it has been started. The
|
||
|
// ChainNotifier parameter must also be started prior to starting the
|
||
|
// BestBlockTracker.
|
||
|
func NewBestBlockTracker(chainNotifier ChainNotifier) *BestBlockTracker {
|
||
|
return &BestBlockTracker{
|
||
|
notifier: chainNotifier,
|
||
|
blockNtfnStream: nil,
|
||
|
quit: make(chan struct{}),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// BestHeight gets the most recent block height known to the
|
||
|
// BestBlockTracker.
|
||
|
func (t *BestBlockTracker) BestHeight() (uint32, error) {
|
||
|
epoch := t.current.Load()
|
||
|
if epoch == nil {
|
||
|
return 0, errors.New("best block height not yet known")
|
||
|
}
|
||
|
|
||
|
return uint32(epoch.Height), nil
|
||
|
}
|
||
|
|
||
|
// BestBlockHeader gets the most recent block header known to the
|
||
|
// BestBlockTracker.
|
||
|
func (t *BestBlockTracker) BestBlockHeader() (*wire.BlockHeader, error) {
|
||
|
epoch := t.current.Load()
|
||
|
if epoch == nil {
|
||
|
return nil, errors.New("best block header not yet known")
|
||
|
}
|
||
|
|
||
|
return epoch.BlockHeader, nil
|
||
|
}
|
||
|
|
||
|
// updateLoop is a helper that subscribes to the underlying BlockEpochEvent
|
||
|
// stream and updates the internal values to match the new BlockEpochs that
|
||
|
// are discovered.
|
||
|
//
|
||
|
// MUST be run as a goroutine.
|
||
|
func (t *BestBlockTracker) updateLoop() {
|
||
|
defer t.wg.Done()
|
||
|
for {
|
||
|
select {
|
||
|
case epoch, ok := <-t.blockNtfnStream.Epochs:
|
||
|
if !ok {
|
||
|
Log.Error("dead epoch stream in " +
|
||
|
"BestBlockTracker")
|
||
|
|
||
|
return
|
||
|
}
|
||
|
t.current.Store(epoch)
|
||
|
case <-t.quit:
|
||
|
t.current.Store(nil)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Start starts the BestBlockTracker. It is an error to start it if it
|
||
|
// is already started.
|
||
|
func (t *BestBlockTracker) Start() error {
|
||
|
t.mu.Lock()
|
||
|
defer t.mu.Unlock()
|
||
|
|
||
|
if t.blockNtfnStream != nil {
|
||
|
return fmt.Errorf("BestBlockTracker is already started")
|
||
|
}
|
||
|
|
||
|
var err error
|
||
|
t.blockNtfnStream, err = t.notifier.RegisterBlockEpochNtfn(nil)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
t.wg.Add(1)
|
||
|
go t.updateLoop()
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Stop stops the BestBlockTracker. It is an error to stop it if it has
|
||
|
// not been started or if it has already been stopped.
|
||
|
func (t *BestBlockTracker) Stop() error {
|
||
|
t.mu.Lock()
|
||
|
defer t.mu.Unlock()
|
||
|
|
||
|
if t.blockNtfnStream == nil {
|
||
|
return fmt.Errorf("BestBlockTracker is not running")
|
||
|
}
|
||
|
close(t.quit)
|
||
|
t.wg.Wait()
|
||
|
t.blockNtfnStream.Cancel()
|
||
|
t.blockNtfnStream = nil
|
||
|
|
||
|
return nil
|
||
|
}
|