autopilot/prefattach: use HeuristicConstraints

This commit makes the pref attach heuristic and the agent use the
HeuristicConstraints internally.
This commit is contained in:
Johan T. Halseth 2018-11-22 23:18:08 +01:00
parent e98d859882
commit 35f4ec84d1
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26
5 changed files with 120 additions and 105 deletions

@ -51,11 +51,9 @@ type Config struct {
// within the graph. // within the graph.
Graph ChannelGraph Graph ChannelGraph
// MaxPendingOpens is the maximum number of pending channel // Constraints is the set of constraints the autopilot must adhere to
// establishment goroutines that can be lingering. We cap this value in // when opening channels.
// order to control the level of parallelism caused by the autopilot Constraints *HeuristicConstraints
// agent.
MaxPendingOpens uint16
// TODO(roasbeef): add additional signals from fee rates and revenue of // TODO(roasbeef): add additional signals from fee rates and revenue of
// currently opened channels // currently opened channels
@ -525,12 +523,12 @@ func (a *Agent) controller() {
// connections succeed, we will they will be ignored and made // connections succeed, we will they will be ignored and made
// available to future heuristic selections. // available to future heuristic selections.
pendingMtx.Lock() pendingMtx.Lock()
if uint16(len(pendingOpens)) >= a.cfg.MaxPendingOpens { if uint16(len(pendingOpens)) >= a.cfg.Constraints.MaxPendingOpens {
pendingMtx.Unlock() pendingMtx.Unlock()
log.Debugf("Reached cap of %v pending "+ log.Debugf("Reached cap of %v pending "+
"channel opens, will retry "+ "channel opens, will retry "+
"after success/failure", "after success/failure",
a.cfg.MaxPendingOpens) a.cfg.Constraints.MaxPendingOpens)
continue continue
} }
@ -587,7 +585,7 @@ func (a *Agent) controller() {
// finished first. // finished first.
pendingMtx.Lock() pendingMtx.Lock()
if uint16(len(pendingOpens)) >= if uint16(len(pendingOpens)) >=
a.cfg.MaxPendingOpens { a.cfg.Constraints.MaxPendingOpens {
// Since we've reached our max number of // Since we've reached our max number of
// pending opens, we'll disconnect this // pending opens, we'll disconnect this
// peer and exit. However, if we were // peer and exit. However, if we were

@ -167,8 +167,10 @@ func TestAgentChannelOpenSignal(t *testing.T) {
DisconnectPeer: func(*btcec.PublicKey) error { DisconnectPeer: func(*btcec.PublicKey) error {
return nil return nil
}, },
Graph: memGraph, Graph: memGraph,
MaxPendingOpens: 10, Constraints: &HeuristicConstraints{
MaxPendingOpens: 10,
},
} }
initialChans := []Channel{} initialChans := []Channel{}
agent, err := New(testCfg, initialChans) agent, err := New(testCfg, initialChans)
@ -288,8 +290,10 @@ func TestAgentChannelFailureSignal(t *testing.T) {
DisconnectPeer: func(*btcec.PublicKey) error { DisconnectPeer: func(*btcec.PublicKey) error {
return nil return nil
}, },
Graph: memGraph, Graph: memGraph,
MaxPendingOpens: 10, Constraints: &HeuristicConstraints{
MaxPendingOpens: 10,
},
} }
initialChans := []Channel{} initialChans := []Channel{}
@ -389,8 +393,10 @@ func TestAgentChannelCloseSignal(t *testing.T) {
DisconnectPeer: func(*btcec.PublicKey) error { DisconnectPeer: func(*btcec.PublicKey) error {
return nil return nil
}, },
Graph: memGraph, Graph: memGraph,
MaxPendingOpens: 10, Constraints: &HeuristicConstraints{
MaxPendingOpens: 10,
},
} }
// We'll start the agent with two channels already being active. // We'll start the agent with two channels already being active.
@ -503,8 +509,10 @@ func TestAgentBalanceUpdate(t *testing.T) {
DisconnectPeer: func(*btcec.PublicKey) error { DisconnectPeer: func(*btcec.PublicKey) error {
return nil return nil
}, },
Graph: memGraph, Graph: memGraph,
MaxPendingOpens: 10, Constraints: &HeuristicConstraints{
MaxPendingOpens: 10,
},
} }
initialChans := []Channel{} initialChans := []Channel{}
agent, err := New(testCfg, initialChans) agent, err := New(testCfg, initialChans)
@ -608,8 +616,10 @@ func TestAgentImmediateAttach(t *testing.T) {
DisconnectPeer: func(*btcec.PublicKey) error { DisconnectPeer: func(*btcec.PublicKey) error {
return nil return nil
}, },
Graph: memGraph, Graph: memGraph,
MaxPendingOpens: 10, Constraints: &HeuristicConstraints{
MaxPendingOpens: 10,
},
} }
initialChans := []Channel{} initialChans := []Channel{}
agent, err := New(testCfg, initialChans) agent, err := New(testCfg, initialChans)
@ -743,8 +753,10 @@ func TestAgentPrivateChannels(t *testing.T) {
DisconnectPeer: func(*btcec.PublicKey) error { DisconnectPeer: func(*btcec.PublicKey) error {
return nil return nil
}, },
Graph: memGraph, Graph: memGraph,
MaxPendingOpens: 10, Constraints: &HeuristicConstraints{
MaxPendingOpens: 10,
},
} }
agent, err := New(cfg, nil) agent, err := New(cfg, nil)
if err != nil { if err != nil {
@ -866,8 +878,10 @@ func TestAgentPendingChannelState(t *testing.T) {
DisconnectPeer: func(*btcec.PublicKey) error { DisconnectPeer: func(*btcec.PublicKey) error {
return nil return nil
}, },
Graph: memGraph, Graph: memGraph,
MaxPendingOpens: 10, Constraints: &HeuristicConstraints{
MaxPendingOpens: 10,
},
} }
initialChans := []Channel{} initialChans := []Channel{}
agent, err := New(testCfg, initialChans) agent, err := New(testCfg, initialChans)
@ -1035,8 +1049,10 @@ func TestAgentPendingOpenChannel(t *testing.T) {
WalletBalance: func() (btcutil.Amount, error) { WalletBalance: func() (btcutil.Amount, error) {
return walletBalance, nil return walletBalance, nil
}, },
Graph: memGraph, Graph: memGraph,
MaxPendingOpens: 10, Constraints: &HeuristicConstraints{
MaxPendingOpens: 10,
},
} }
agent, err := New(cfg, nil) agent, err := New(cfg, nil)
if err != nil { if err != nil {
@ -1118,8 +1134,10 @@ func TestAgentOnNodeUpdates(t *testing.T) {
WalletBalance: func() (btcutil.Amount, error) { WalletBalance: func() (btcutil.Amount, error) {
return walletBalance, nil return walletBalance, nil
}, },
Graph: memGraph, Graph: memGraph,
MaxPendingOpens: 10, Constraints: &HeuristicConstraints{
MaxPendingOpens: 10,
},
} }
agent, err := New(cfg, nil) agent, err := New(cfg, nil)
if err != nil { if err != nil {
@ -1232,8 +1250,10 @@ func TestAgentSkipPendingConns(t *testing.T) {
DisconnectPeer: func(*btcec.PublicKey) error { DisconnectPeer: func(*btcec.PublicKey) error {
return nil return nil
}, },
Graph: memGraph, Graph: memGraph,
MaxPendingOpens: 10, Constraints: &HeuristicConstraints{
MaxPendingOpens: 10,
},
} }
initialChans := []Channel{} initialChans := []Channel{}
agent, err := New(testCfg, initialChans) agent, err := New(testCfg, initialChans)

@ -22,28 +22,20 @@ import (
// //
// TODO(roasbeef): BA, with k=-3 // TODO(roasbeef): BA, with k=-3
type ConstrainedPrefAttachment struct { type ConstrainedPrefAttachment struct {
minChanSize btcutil.Amount constraints *HeuristicConstraints
maxChanSize btcutil.Amount
chanLimit uint16
threshold float64
} }
// NewConstrainedPrefAttachment creates a new instance of a // NewConstrainedPrefAttachment creates a new instance of a
// ConstrainedPrefAttachment heuristics given bounds on allowed channel sizes, // ConstrainedPrefAttachment heuristics given bounds on allowed channel sizes,
// and an allocation amount which is interpreted as a percentage of funds that // and an allocation amount which is interpreted as a percentage of funds that
// is to be committed to channels at all times. // is to be committed to channels at all times.
func NewConstrainedPrefAttachment(minChanSize, maxChanSize btcutil.Amount, func NewConstrainedPrefAttachment(
chanLimit uint16, allocation float64) *ConstrainedPrefAttachment { cfg *HeuristicConstraints) *ConstrainedPrefAttachment {
prand.Seed(time.Now().Unix()) prand.Seed(time.Now().Unix())
return &ConstrainedPrefAttachment{ return &ConstrainedPrefAttachment{
minChanSize: minChanSize, constraints: cfg,
chanLimit: chanLimit,
maxChanSize: maxChanSize,
threshold: allocation,
} }
} }
@ -61,45 +53,11 @@ var _ AttachmentHeuristic = (*ConstrainedPrefAttachment)(nil)
func (p *ConstrainedPrefAttachment) NeedMoreChans(channels []Channel, func (p *ConstrainedPrefAttachment) NeedMoreChans(channels []Channel,
funds btcutil.Amount) (btcutil.Amount, uint32, bool) { funds btcutil.Amount) (btcutil.Amount, uint32, bool) {
// If we're already over our maximum allowed number of channels, then // We'll try to open more channels as long as the constraints allow it.
// we'll instruct the controller not to create any more channels. availableFunds, availableChans := p.constraints.availableChans(
if len(channels) >= int(p.chanLimit) { channels, funds,
return 0, 0, false )
} return availableFunds, availableChans, availableChans > 0
// The number of additional channels that should be opened is the
// difference between the channel limit, and the number of channels we
// already have open.
numAdditionalChans := uint32(p.chanLimit) - uint32(len(channels))
// First, we'll tally up the total amount of funds that are currently
// present within the set of active channels.
var totalChanAllocation btcutil.Amount
for _, channel := range channels {
totalChanAllocation += channel.Capacity
}
// With this value known, we'll now compute the total amount of fund
// allocated across regular utxo's and channel utxo's.
totalFunds := funds + totalChanAllocation
// Once the total amount has been computed, we then calculate the
// fraction of funds currently allocated to channels.
fundsFraction := float64(totalChanAllocation) / float64(totalFunds)
// If this fraction is below our threshold, then we'll return true, to
// indicate the controller should call Select to obtain a candidate set
// of channels to attempt to open.
needMore := fundsFraction < p.threshold
if !needMore {
return 0, 0, false
}
// Now that we know we need more funds, we'll compute the amount of
// additional funds we should allocate towards channels.
targetAllocation := btcutil.Amount(float64(totalFunds) * p.threshold)
fundsAvailable := targetAllocation - totalChanAllocation
return fundsAvailable, numAdditionalChans, true
} }
// NodeID is a simple type that holds an EC public key serialized in compressed // NodeID is a simple type that holds an EC public key serialized in compressed
@ -150,7 +108,7 @@ func (p *ConstrainedPrefAttachment) Select(self *btcec.PublicKey, g ChannelGraph
var directives []AttachmentDirective var directives []AttachmentDirective
if fundsAvailable < p.minChanSize { if fundsAvailable < p.constraints.MinChanSize {
return directives, nil return directives, nil
} }
@ -263,9 +221,9 @@ func (p *ConstrainedPrefAttachment) Select(self *btcec.PublicKey, g ChannelGraph
// If we have enough available funds to distribute the maximum channel // If we have enough available funds to distribute the maximum channel
// size for each of the selected peers to attach to, then we'll // size for each of the selected peers to attach to, then we'll
// allocate the maximum amount to each peer. // allocate the maximum amount to each peer.
case int64(fundsAvailable) >= numSelectedNodes*int64(p.maxChanSize): case int64(fundsAvailable) >= numSelectedNodes*int64(p.constraints.MaxChanSize):
for i := 0; i < int(numSelectedNodes); i++ { for i := 0; i < int(numSelectedNodes); i++ {
directives[i].ChanAmt = p.maxChanSize directives[i].ChanAmt = p.constraints.MaxChanSize
} }
return directives, nil return directives, nil
@ -273,14 +231,14 @@ func (p *ConstrainedPrefAttachment) Select(self *btcec.PublicKey, g ChannelGraph
// Otherwise, we'll greedily allocate our funds to the channels // Otherwise, we'll greedily allocate our funds to the channels
// successively until we run out of available funds, or can't create a // successively until we run out of available funds, or can't create a
// channel above the min channel size. // channel above the min channel size.
case int64(fundsAvailable) < numSelectedNodes*int64(p.maxChanSize): case int64(fundsAvailable) < numSelectedNodes*int64(p.constraints.MaxChanSize):
i := 0 i := 0
for fundsAvailable > p.minChanSize { for fundsAvailable > p.constraints.MinChanSize {
// We'll attempt to allocate the max channel size // We'll attempt to allocate the max channel size
// initially. If we don't have enough funds to do this, // initially. If we don't have enough funds to do this,
// then we'll allocate the remainder of the funds // then we'll allocate the remainder of the funds
// available to the channel. // available to the channel.
delta := p.maxChanSize delta := p.constraints.MaxChanSize
if fundsAvailable-delta < 0 { if fundsAvailable-delta < 0 {
delta = fundsAvailable delta = fundsAvailable
} }

@ -29,6 +29,13 @@ func TestConstrainedPrefAttachmentNeedMoreChan(t *testing.T) {
threshold = 0.5 threshold = 0.5
) )
constraints := &HeuristicConstraints{
MinChanSize: minChanSize,
MaxChanSize: maxChanSize,
ChanLimit: chanLimit,
Allocation: threshold,
}
randChanID := func() lnwire.ShortChannelID { randChanID := func() lnwire.ShortChannelID {
return lnwire.NewShortChanIDFromInt(uint64(prand.Int63())) return lnwire.NewShortChanIDFromInt(uint64(prand.Int63()))
} }
@ -146,8 +153,7 @@ func TestConstrainedPrefAttachmentNeedMoreChan(t *testing.T) {
}, },
} }
prefAttach := NewConstrainedPrefAttachment(minChanSize, maxChanSize, prefAttach := NewConstrainedPrefAttachment(constraints)
chanLimit, threshold)
for i, testCase := range testCases { for i, testCase := range testCases {
amtToAllocate, numMore, needMore := prefAttach.NeedMoreChans( amtToAllocate, numMore, needMore := prefAttach.NeedMoreChans(
@ -236,14 +242,20 @@ func TestConstrainedPrefAttachmentSelectEmptyGraph(t *testing.T) {
threshold = 0.5 threshold = 0.5
) )
constraints := &HeuristicConstraints{
MinChanSize: minChanSize,
MaxChanSize: maxChanSize,
ChanLimit: chanLimit,
Allocation: threshold,
}
// First, we'll generate a random key that represents "us", and create // First, we'll generate a random key that represents "us", and create
// a new instance of the heuristic with our set parameters. // a new instance of the heuristic with our set parameters.
self, err := randKey() self, err := randKey()
if err != nil { if err != nil {
t.Fatalf("unable to generate self key: %v", err) t.Fatalf("unable to generate self key: %v", err)
} }
prefAttach := NewConstrainedPrefAttachment(minChanSize, maxChanSize, prefAttach := NewConstrainedPrefAttachment(constraints)
chanLimit, threshold)
skipNodes := make(map[NodeID]struct{}) skipNodes := make(map[NodeID]struct{})
for _, graph := range chanGraphs { for _, graph := range chanGraphs {
@ -296,6 +308,12 @@ func TestConstrainedPrefAttachmentSelectTwoVertexes(t *testing.T) {
threshold = 0.5 threshold = 0.5
) )
constraints := &HeuristicConstraints{
MinChanSize: minChanSize,
MaxChanSize: maxChanSize,
ChanLimit: chanLimit,
Allocation: threshold,
}
skipNodes := make(map[NodeID]struct{}) skipNodes := make(map[NodeID]struct{})
for _, graph := range chanGraphs { for _, graph := range chanGraphs {
success := t.Run(graph.name, func(t1 *testing.T) { success := t.Run(graph.name, func(t1 *testing.T) {
@ -314,8 +332,7 @@ func TestConstrainedPrefAttachmentSelectTwoVertexes(t *testing.T) {
if err != nil { if err != nil {
t1.Fatalf("unable to generate self key: %v", err) t1.Fatalf("unable to generate self key: %v", err)
} }
prefAttach := NewConstrainedPrefAttachment(minChanSize, maxChanSize, prefAttach := NewConstrainedPrefAttachment(constraints)
chanLimit, threshold)
// For this set, we'll load the memory graph with two // For this set, we'll load the memory graph with two
// nodes, and a random channel connecting them. // nodes, and a random channel connecting them.
@ -386,6 +403,13 @@ func TestConstrainedPrefAttachmentSelectInsufficientFunds(t *testing.T) {
threshold = 0.5 threshold = 0.5
) )
constraints := &HeuristicConstraints{
MinChanSize: minChanSize,
MaxChanSize: maxChanSize,
ChanLimit: chanLimit,
Allocation: threshold,
}
skipNodes := make(map[NodeID]struct{}) skipNodes := make(map[NodeID]struct{})
for _, graph := range chanGraphs { for _, graph := range chanGraphs {
success := t.Run(graph.name, func(t1 *testing.T) { success := t.Run(graph.name, func(t1 *testing.T) {
@ -404,9 +428,7 @@ func TestConstrainedPrefAttachmentSelectInsufficientFunds(t *testing.T) {
if err != nil { if err != nil {
t1.Fatalf("unable to generate self key: %v", err) t1.Fatalf("unable to generate self key: %v", err)
} }
prefAttach := NewConstrainedPrefAttachment( prefAttach := NewConstrainedPrefAttachment(constraints)
minChanSize, maxChanSize, chanLimit, threshold,
)
// Next, we'll attempt to select a set of candidates, // Next, we'll attempt to select a set of candidates,
// passing zero for the amount of wallet funds. This // passing zero for the amount of wallet funds. This
@ -445,6 +467,13 @@ func TestConstrainedPrefAttachmentSelectGreedyAllocation(t *testing.T) {
threshold = 0.5 threshold = 0.5
) )
constraints := &HeuristicConstraints{
MinChanSize: minChanSize,
MaxChanSize: maxChanSize,
ChanLimit: chanLimit,
Allocation: threshold,
}
skipNodes := make(map[NodeID]struct{}) skipNodes := make(map[NodeID]struct{})
for _, graph := range chanGraphs { for _, graph := range chanGraphs {
success := t.Run(graph.name, func(t1 *testing.T) { success := t.Run(graph.name, func(t1 *testing.T) {
@ -463,9 +492,7 @@ func TestConstrainedPrefAttachmentSelectGreedyAllocation(t *testing.T) {
if err != nil { if err != nil {
t1.Fatalf("unable to generate self key: %v", err) t1.Fatalf("unable to generate self key: %v", err)
} }
prefAttach := NewConstrainedPrefAttachment( prefAttach := NewConstrainedPrefAttachment(constraints)
minChanSize, maxChanSize, chanLimit, threshold,
)
const chanCapacity = btcutil.SatoshiPerBitcoin const chanCapacity = btcutil.SatoshiPerBitcoin
@ -581,6 +608,13 @@ func TestConstrainedPrefAttachmentSelectSkipNodes(t *testing.T) {
threshold = 0.5 threshold = 0.5
) )
constraints := &HeuristicConstraints{
MinChanSize: minChanSize,
MaxChanSize: maxChanSize,
ChanLimit: chanLimit,
Allocation: threshold,
}
for _, graph := range chanGraphs { for _, graph := range chanGraphs {
success := t.Run(graph.name, func(t1 *testing.T) { success := t.Run(graph.name, func(t1 *testing.T) {
skipNodes := make(map[NodeID]struct{}) skipNodes := make(map[NodeID]struct{})
@ -600,9 +634,7 @@ func TestConstrainedPrefAttachmentSelectSkipNodes(t *testing.T) {
if err != nil { if err != nil {
t1.Fatalf("unable to generate self key: %v", err) t1.Fatalf("unable to generate self key: %v", err)
} }
prefAttach := NewConstrainedPrefAttachment( prefAttach := NewConstrainedPrefAttachment(constraints)
minChanSize, maxChanSize, chanLimit, threshold,
)
// Next, we'll create a simple topology of two nodes, // Next, we'll create a simple topology of two nodes,
// with a single channel connecting them. // with a single channel connecting them.

@ -85,12 +85,19 @@ var _ autopilot.ChannelController = (*chanController)(nil)
func initAutoPilot(svr *server, cfg *autoPilotConfig) (*autopilot.Agent, error) { func initAutoPilot(svr *server, cfg *autoPilotConfig) (*autopilot.Agent, error) {
atplLog.Infof("Instantiating autopilot with cfg: %v", spew.Sdump(cfg)) atplLog.Infof("Instantiating autopilot with cfg: %v", spew.Sdump(cfg))
// Set up the constraints the autopilot heuristics must adhere to.
atplConstraints := &autopilot.HeuristicConstraints{
MinChanSize: btcutil.Amount(cfg.MinChannelSize),
MaxChanSize: btcutil.Amount(cfg.MaxChannelSize),
ChanLimit: uint16(cfg.MaxChannels),
Allocation: cfg.Allocation,
MaxPendingOpens: 10,
}
// First, we'll create the preferential attachment heuristic, // First, we'll create the preferential attachment heuristic,
// initialized with the passed auto pilot configuration parameters. // initialized with the passed auto pilot configuration parameters.
prefAttachment := autopilot.NewConstrainedPrefAttachment( prefAttachment := autopilot.NewConstrainedPrefAttachment(
btcutil.Amount(cfg.MinChannelSize), atplConstraints,
btcutil.Amount(cfg.MaxChannelSize),
uint16(cfg.MaxChannels), cfg.Allocation,
) )
// With the heuristic itself created, we can now populate the remainder // With the heuristic itself created, we can now populate the remainder
@ -107,8 +114,8 @@ func initAutoPilot(svr *server, cfg *autoPilotConfig) (*autopilot.Agent, error)
WalletBalance: func() (btcutil.Amount, error) { WalletBalance: func() (btcutil.Amount, error) {
return svr.cc.wallet.ConfirmedBalance(cfg.MinConfs) return svr.cc.wallet.ConfirmedBalance(cfg.MinConfs)
}, },
Graph: autopilot.ChannelGraphFromDatabase(svr.chanDB.ChannelGraph()), Graph: autopilot.ChannelGraphFromDatabase(svr.chanDB.ChannelGraph()),
MaxPendingOpens: 10, Constraints: atplConstraints,
ConnectToPeer: func(target *btcec.PublicKey, addrs []net.Addr) (bool, error) { ConnectToPeer: func(target *btcec.PublicKey, addrs []net.Addr) (bool, error) {
// First, we'll check if we're already connected to the // First, we'll check if we're already connected to the
// target peer. If we are, we can exit early. Otherwise, // target peer. If we are, we can exit early. Otherwise,