mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-13 12:32:48 +02:00
Replace the (startTime, endTime time.Time) parameters on NodeUpdatesInHorizon and ChanUpdatesInHorizon with (v GossipVersion, r NodeUpdateRange/ChanUpdateRange). The range types enforce version-correct bounds at the type level: v1 uses unix timestamps, v2 will use block heights. The KV store rejects non-v1 versions since it only stores v1 data. The SQL store dispatches to version-specific helpers (nodeUpdatesInHorizonV1, chanUpdatesInHorizonV1); the v2 block-height paths return an error for now and will be wired up in follow-up commits. VersionedGraph wrappers supply the version from the embedded field, so callers only pass the range.
379 lines
11 KiB
Go
379 lines
11 KiB
Go
package graphdb
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/lightningnetwork/lnd/fn/v2"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
)
|
|
|
|
const (
|
|
// DefaultRejectCacheSize is the default number of rejectCacheEntries to
|
|
// cache for use in the rejection cache of incoming gossip traffic. This
|
|
// produces a cache size of around 1MB.
|
|
DefaultRejectCacheSize = 50000
|
|
|
|
// DefaultChannelCacheSize is the default number of ChannelEdges cached
|
|
// in order to reply to gossip queries. This produces a cache size of
|
|
// around 40MB.
|
|
DefaultChannelCacheSize = 20000
|
|
|
|
// DefaultPreAllocCacheNumNodes is the default number of channels we
|
|
// assume for mainnet for pre-allocating the graph cache. As of
|
|
// September 2021, there currently are 14k nodes in a strictly pruned
|
|
// graph, so we choose a number that is slightly higher.
|
|
DefaultPreAllocCacheNumNodes = 15000
|
|
)
|
|
|
|
// IteratorOption is a functional option used to change the per-call
|
|
// configuration for iterators.
|
|
type IteratorOption func(*iterConfig)
|
|
|
|
// iterConfig holds the configuration for graph operations.
|
|
type iterConfig struct {
|
|
// chanUpdateIterBatchSize is the batch size to use when reading out
|
|
// channel updates to send a peer a backlog.
|
|
chanUpdateIterBatchSize int
|
|
|
|
// nodeUpdateIterBatchSize is the batch size to use when reading out
|
|
// node updates to send to a peer backlog.
|
|
nodeUpdateIterBatchSize int
|
|
|
|
// iterPublicNodes is used to make an iterator that only iterates over
|
|
// public nodes.
|
|
iterPublicNodes bool
|
|
}
|
|
|
|
// ChanUpdateRange describes a range for channel updates. Only one of the time
|
|
// or height ranges should be set depending on the gossip version.
|
|
type ChanUpdateRange struct {
|
|
// StartTime is the inclusive lower time bound (v1 gossip only).
|
|
StartTime fn.Option[time.Time]
|
|
|
|
// EndTime is the exclusive upper time bound (v1 gossip only).
|
|
EndTime fn.Option[time.Time]
|
|
|
|
// StartHeight is the inclusive lower block-height bound (v2 gossip
|
|
// only).
|
|
StartHeight fn.Option[uint32]
|
|
|
|
// EndHeight is the exclusive upper block-height bound (v2 gossip
|
|
// only).
|
|
EndHeight fn.Option[uint32]
|
|
}
|
|
|
|
// validateForVersion checks that the range fields are consistent with the
|
|
// given gossip version: v1 requires time bounds, v2 requires block-height
|
|
// bounds, and mixing the two is rejected.
|
|
func (r ChanUpdateRange) validateForVersion(v lnwire.GossipVersion) error {
|
|
var (
|
|
hasStartTime = r.StartTime.IsSome()
|
|
hasEndTime = r.EndTime.IsSome()
|
|
hasTimeRange = hasStartTime || hasEndTime
|
|
|
|
hasStartHeight = r.StartHeight.IsSome()
|
|
hasEndHeight = r.EndHeight.IsSome()
|
|
hasBlockRange = hasStartHeight || hasEndHeight
|
|
)
|
|
|
|
if hasTimeRange && hasBlockRange {
|
|
return fmt.Errorf("chan update range has both time and block " +
|
|
"ranges")
|
|
}
|
|
|
|
switch v {
|
|
case gossipV1:
|
|
if hasBlockRange {
|
|
return fmt.Errorf("v1 chan update range must use time")
|
|
}
|
|
|
|
if !hasTimeRange {
|
|
return fmt.Errorf("v1 chan update range missing time")
|
|
}
|
|
|
|
if !hasStartTime || !hasEndTime {
|
|
return fmt.Errorf("v1 chan update range " +
|
|
"missing time bounds")
|
|
}
|
|
|
|
start := r.StartTime.UnwrapOr(time.Time{})
|
|
end := r.EndTime.UnwrapOr(time.Time{})
|
|
|
|
if start.After(end) {
|
|
return fmt.Errorf("v1 chan update range: " +
|
|
"start time after end time")
|
|
}
|
|
|
|
case gossipV2:
|
|
if hasTimeRange {
|
|
return fmt.Errorf("v2 chan update range must use " +
|
|
"blocks")
|
|
}
|
|
|
|
if !hasBlockRange {
|
|
return fmt.Errorf("v2 chan update range missing " +
|
|
"block range")
|
|
}
|
|
|
|
if !hasStartHeight || !hasEndHeight {
|
|
return fmt.Errorf("v2 chan update range " +
|
|
"missing block bounds")
|
|
}
|
|
|
|
start := r.StartHeight.UnwrapOr(0)
|
|
end := r.EndHeight.UnwrapOr(0)
|
|
if start > end {
|
|
return fmt.Errorf("v2 chan update range: " +
|
|
"start height after end height")
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("unknown gossip version: %v", v)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NodeUpdateRange describes a range for node updates. Only one of the time or
|
|
// height ranges should be set depending on the gossip version.
|
|
type NodeUpdateRange struct {
|
|
// StartTime is the inclusive lower time bound (v1 gossip only).
|
|
StartTime fn.Option[time.Time]
|
|
|
|
// EndTime is the exclusive upper time bound (v1 gossip only).
|
|
EndTime fn.Option[time.Time]
|
|
|
|
// StartHeight is the inclusive lower block-height bound (v2 gossip
|
|
// only).
|
|
StartHeight fn.Option[uint32]
|
|
|
|
// EndHeight is the exclusive upper block-height bound (v2 gossip
|
|
// only).
|
|
EndHeight fn.Option[uint32]
|
|
}
|
|
|
|
// validateForVersion checks that the range fields are consistent with the
|
|
// given gossip version: v1 requires time bounds, v2 requires block-height
|
|
// bounds, and mixing the two is rejected.
|
|
func (r NodeUpdateRange) validateForVersion(v lnwire.GossipVersion) error {
|
|
var (
|
|
hasStartTime = r.StartTime.IsSome()
|
|
hasEndTime = r.EndTime.IsSome()
|
|
|
|
hasStartHeight = r.StartHeight.IsSome()
|
|
hasEndHeight = r.EndHeight.IsSome()
|
|
|
|
hasTimeRange = hasStartTime || hasEndTime
|
|
hasBlockRange = hasStartHeight || hasEndHeight
|
|
)
|
|
|
|
if hasTimeRange && hasBlockRange {
|
|
return fmt.Errorf("node update range has both " +
|
|
"time and block ranges")
|
|
}
|
|
|
|
switch v {
|
|
case gossipV1:
|
|
if hasBlockRange {
|
|
return fmt.Errorf("v1 node update range must use time")
|
|
}
|
|
|
|
if !hasTimeRange {
|
|
return fmt.Errorf("v1 node update range missing time")
|
|
}
|
|
if !hasStartTime || !hasEndTime {
|
|
return fmt.Errorf("v1 node update range missing " +
|
|
"time bounds")
|
|
}
|
|
|
|
start := r.StartTime.UnwrapOr(time.Time{})
|
|
end := r.EndTime.UnwrapOr(time.Time{})
|
|
if start.After(end) {
|
|
return fmt.Errorf("v1 node update range: start time " +
|
|
"after end time")
|
|
}
|
|
|
|
case gossipV2:
|
|
if hasTimeRange {
|
|
return fmt.Errorf("v2 node update range must use " +
|
|
"height")
|
|
}
|
|
if !hasBlockRange {
|
|
return fmt.Errorf("v2 node update range missing height")
|
|
}
|
|
if !hasStartHeight || !hasEndHeight {
|
|
return fmt.Errorf("v2 node update range missing " +
|
|
"height bounds")
|
|
}
|
|
|
|
start := r.StartHeight.UnwrapOr(0)
|
|
end := r.EndHeight.UnwrapOr(0)
|
|
if start > end {
|
|
return fmt.Errorf("v2 node update range: start " +
|
|
"height after end height")
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("unknown gossip version: %d", v)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// defaultIteratorConfig returns the default configuration.
|
|
func defaultIteratorConfig() *iterConfig {
|
|
return &iterConfig{
|
|
chanUpdateIterBatchSize: 1_000,
|
|
nodeUpdateIterBatchSize: 1_000,
|
|
}
|
|
}
|
|
|
|
// WithChanUpdateIterBatchSize sets the batch size for channel update
|
|
// iterators.
|
|
func WithChanUpdateIterBatchSize(size int) IteratorOption {
|
|
return func(cfg *iterConfig) {
|
|
if size > 0 {
|
|
cfg.chanUpdateIterBatchSize = size
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithNodeUpdateIterBatchSize set the batch size for node ann iterators.
|
|
func WithNodeUpdateIterBatchSize(size int) IteratorOption {
|
|
return func(cfg *iterConfig) {
|
|
if size > 0 {
|
|
cfg.nodeUpdateIterBatchSize = size
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithIterPublicNodesOnly is used to create an iterator that only iterates over
|
|
// public nodes.
|
|
func WithIterPublicNodesOnly() IteratorOption {
|
|
return func(cfg *iterConfig) {
|
|
cfg.iterPublicNodes = true
|
|
}
|
|
}
|
|
|
|
// chanGraphOptions holds parameters for tuning and customizing the
|
|
// ChannelGraph.
|
|
type chanGraphOptions struct {
|
|
// useGraphCache denotes whether the in-memory graph cache should be
|
|
// used or a fallback version that uses the underlying database for
|
|
// path finding.
|
|
useGraphCache bool
|
|
|
|
// preAllocCacheNumNodes is the number of nodes we expect to be in the
|
|
// graph cache, so we can pre-allocate the map accordingly.
|
|
preAllocCacheNumNodes int
|
|
|
|
// asyncGraphCachePopulation indicates whether the graph cache
|
|
// should be populated asynchronously or if the Start method should
|
|
// block until the cache is fully populated. This is true by
|
|
// default.
|
|
asyncGraphCachePopulation bool
|
|
}
|
|
|
|
// defaultChanGraphOptions returns a new chanGraphOptions instance populated
|
|
// with default values.
|
|
func defaultChanGraphOptions() *chanGraphOptions {
|
|
return &chanGraphOptions{
|
|
useGraphCache: true,
|
|
asyncGraphCachePopulation: true,
|
|
preAllocCacheNumNodes: DefaultPreAllocCacheNumNodes,
|
|
}
|
|
}
|
|
|
|
// ChanGraphOption describes the signature of a functional option that can be
|
|
// used to customize a ChannelGraph instance.
|
|
type ChanGraphOption func(*chanGraphOptions)
|
|
|
|
// WithUseGraphCache sets whether the in-memory graph cache should be used.
|
|
func WithUseGraphCache(use bool) ChanGraphOption {
|
|
return func(o *chanGraphOptions) {
|
|
o.useGraphCache = use
|
|
}
|
|
}
|
|
|
|
// WithPreAllocCacheNumNodes sets the number of nodes we expect to be in the
|
|
// graph cache, so we can pre-allocate the map accordingly.
|
|
func WithPreAllocCacheNumNodes(n int) ChanGraphOption {
|
|
return func(o *chanGraphOptions) {
|
|
o.preAllocCacheNumNodes = n
|
|
}
|
|
}
|
|
|
|
// WithAsyncGraphCachePopulation sets whether the graph cache should be
|
|
// populated asynchronously or if the Start method should block until the
|
|
// cache is fully populated.
|
|
func WithAsyncGraphCachePopulation(async bool) ChanGraphOption {
|
|
return func(o *chanGraphOptions) {
|
|
o.asyncGraphCachePopulation = async
|
|
}
|
|
}
|
|
|
|
// WithSyncGraphCachePopulation will cause the ChannelGraph to block
|
|
// until the graph cache is fully populated before returning from the Start
|
|
// method. This is useful for tests that need to ensure the graph cache is
|
|
// fully populated before proceeding with further operations.
|
|
func WithSyncGraphCachePopulation() ChanGraphOption {
|
|
return func(o *chanGraphOptions) {
|
|
o.asyncGraphCachePopulation = false
|
|
}
|
|
}
|
|
|
|
// StoreOptions holds parameters for tuning and customizing a graph DB.
|
|
type StoreOptions struct {
|
|
// RejectCacheSize is the maximum number of rejectCacheEntries to hold
|
|
// in the rejection cache.
|
|
RejectCacheSize int
|
|
|
|
// ChannelCacheSize is the maximum number of ChannelEdges to hold in the
|
|
// channel cache.
|
|
ChannelCacheSize int
|
|
|
|
// BatchCommitInterval is the maximum duration the batch schedulers will
|
|
// wait before attempting to commit a pending set of updates.
|
|
BatchCommitInterval time.Duration
|
|
|
|
// NoMigration specifies that underlying backend was opened in read-only
|
|
// mode and migrations shouldn't be performed. This can be useful for
|
|
// applications that use the channeldb package as a library.
|
|
NoMigration bool
|
|
}
|
|
|
|
// DefaultOptions returns a StoreOptions populated with default values.
|
|
func DefaultOptions() *StoreOptions {
|
|
return &StoreOptions{
|
|
RejectCacheSize: DefaultRejectCacheSize,
|
|
ChannelCacheSize: DefaultChannelCacheSize,
|
|
NoMigration: false,
|
|
}
|
|
}
|
|
|
|
// StoreOptionModifier is a function signature for modifying the default
|
|
// StoreOptions.
|
|
type StoreOptionModifier func(*StoreOptions)
|
|
|
|
// WithRejectCacheSize sets the RejectCacheSize to n.
|
|
func WithRejectCacheSize(n int) StoreOptionModifier {
|
|
return func(o *StoreOptions) {
|
|
o.RejectCacheSize = n
|
|
}
|
|
}
|
|
|
|
// WithChannelCacheSize sets the ChannelCacheSize to n.
|
|
func WithChannelCacheSize(n int) StoreOptionModifier {
|
|
return func(o *StoreOptions) {
|
|
o.ChannelCacheSize = n
|
|
}
|
|
}
|
|
|
|
// WithBatchCommitInterval sets the batch commit interval for the interval batch
|
|
// schedulers.
|
|
func WithBatchCommitInterval(interval time.Duration) StoreOptionModifier {
|
|
return func(o *StoreOptions) {
|
|
o.BatchCommitInterval = interval
|
|
}
|
|
}
|