lnd/htlcswitch/sequencer.go

129 lines
3.6 KiB
Go

package htlcswitch
import (
"sync"
"github.com/coreos/bbolt"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/channeldb"
)
// defaultSequenceBatchSize specifies the window of sequence numbers that are
// allocated for each write to disk made by the sequencer.
const defaultSequenceBatchSize = 1000
// Sequencer emits sequence numbers for locally initiated HTLCs. These are
// only used internally for tracking pending payments, however they must be
// unique in order to avoid circuit key collision in the circuit map.
type Sequencer interface {
// NextID returns a unique sequence number for each invocation.
NextID() (uint64, error)
}
var (
// nextPaymentIDKey identifies the bucket that will keep track of the
// persistent sequence numbers for payments.
nextPaymentIDKey = []byte("next-payment-id-key")
// ErrSequencerCorrupted signals that the persistence engine was not
// initialized, or has been corrupted since startup.
ErrSequencerCorrupted = errors.New(
"sequencer database has been corrupted")
)
// persistentSequencer is a concrete implementation of IDGenerator, that uses
// channeldb to allocate sequence numbers.
type persistentSequencer struct {
db *channeldb.DB
mu sync.Mutex
nextID uint64
horizonID uint64
}
// NewPersistentSequencer initializes a new sequencer using a channeldb backend.
func NewPersistentSequencer(db *channeldb.DB) (Sequencer, error) {
g := &persistentSequencer{
db: db,
}
// Ensure the database bucket is created before any updates are
// performed.
if err := g.initDB(); err != nil {
return nil, err
}
return g, nil
}
// NextID returns a unique sequence number for every invocation, persisting the
// assignment to avoid reuse.
func (s *persistentSequencer) NextID() (uint64, error) {
// nextID will be the unique sequence number returned if no errors are
// encountered.
var nextID uint64
// If our sequence batch has not been exhausted, we can allocate the
// next identifier in the range.
s.mu.Lock()
defer s.mu.Unlock()
if s.nextID < s.horizonID {
nextID = s.nextID
s.nextID++
return nextID, nil
}
// Otherwise, our sequence batch has been exhausted. We use the last
// known sequence number on disk to mark the beginning of the next
// sequence batch, and allocate defaultSequenceBatchSize (1000) at a
// time.
//
// NOTE: This also will happen on the first invocation after startup,
// i.e. when nextID and horizonID are both 0. The next sequence batch to be
// allocated will start from the last known tip on disk, which is fine
// as we only require uniqueness of the allocated numbers.
var nextHorizonID uint64
if err := s.db.Update(func(tx *bbolt.Tx) error {
nextIDBkt := tx.Bucket(nextPaymentIDKey)
if nextIDBkt == nil {
return ErrSequencerCorrupted
}
nextID = nextIDBkt.Sequence()
nextHorizonID = nextID + defaultSequenceBatchSize
// Cannot fail when used in Update.
nextIDBkt.SetSequence(nextHorizonID)
return nil
}); err != nil {
return 0, err
}
// Never assign index zero, to avoid collisions with the EmptyKeystone.
if nextID == 0 {
nextID++
}
// If our batch sequence allocation succeed, update our in-memory values
// so we can continue to allocate sequence numbers without hitting disk.
// The nextID is incremented by one in memory so the in can be used
// issued directly on the next invocation.
s.nextID = nextID + 1
s.horizonID = nextHorizonID
return nextID, nil
}
// initDB populates the bucket used to generate payment sequence numbers.
func (s *persistentSequencer) initDB() error {
return s.db.Update(func(tx *bbolt.Tx) error {
_, err := tx.CreateBucketIfNotExists(nextPaymentIDKey)
return err
})
}