autopilot: Make autopilot aware of channel open failures
This commit is contained in:
parent
42014f5b7b
commit
a43d7d3532
@ -177,13 +177,17 @@ type chanOpenUpdate struct {
|
|||||||
newChan Channel
|
newChan Channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// chanOpenFailureUpdate is a type of external state update that indicates
|
||||||
|
// a previous channel open failed, and that it might be possible to try again.
|
||||||
|
type chanOpenFailureUpdate struct{}
|
||||||
|
|
||||||
// chanCloseUpdate is a type of external state update that indicates that the
|
// chanCloseUpdate is a type of external state update that indicates that the
|
||||||
// backing Lightning Node has closed a previously open channel.
|
// backing Lightning Node has closed a previously open channel.
|
||||||
type chanCloseUpdate struct {
|
type chanCloseUpdate struct {
|
||||||
closedChans []lnwire.ShortChannelID
|
closedChans []lnwire.ShortChannelID
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnBalanceChange is a callback that should be executed each the balance of
|
// OnBalanceChange is a callback that should be executed each time the balance of
|
||||||
// the backing wallet changes.
|
// the backing wallet changes.
|
||||||
func (a *Agent) OnBalanceChange(delta btcutil.Amount) {
|
func (a *Agent) OnBalanceChange(delta btcutil.Amount) {
|
||||||
go func() {
|
go func() {
|
||||||
@ -203,6 +207,15 @@ func (a *Agent) OnChannelOpen(c Channel) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnChannelOpenFailure is a callback that should be executed when the
|
||||||
|
// autopilot has attempted to open a channel, but failed. In this case we can
|
||||||
|
// retry channel creation with a different node.
|
||||||
|
func (a *Agent) OnChannelOpenFailure() {
|
||||||
|
go func() {
|
||||||
|
a.stateUpdates <- &chanOpenFailureUpdate{}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
// OnChannelClose is a callback that should be executed each time a prior
|
// OnChannelClose is a callback that should be executed each time a prior
|
||||||
// channel has been closed for any reason. This includes regular
|
// channel has been closed for any reason. This includes regular
|
||||||
// closes, force closes, and channel breaches.
|
// closes, force closes, and channel breaches.
|
||||||
@ -294,6 +307,11 @@ func (a *Agent) controller(startingBalance btcutil.Amount) {
|
|||||||
|
|
||||||
a.totalBalance += update.balanceDelta
|
a.totalBalance += update.balanceDelta
|
||||||
|
|
||||||
|
// The channel we tried to open previously failed for
|
||||||
|
// whatever reason.
|
||||||
|
case *chanOpenFailureUpdate:
|
||||||
|
log.Debug("Retrying after previous channel open failure.")
|
||||||
|
|
||||||
// A new channel has been opened successfully. This was
|
// A new channel has been opened successfully. This was
|
||||||
// either opened by the Agent, or an external system
|
// either opened by the Agent, or an external system
|
||||||
// that is able to drive the Lightning Node.
|
// that is able to drive the Lightning Node.
|
||||||
@ -412,6 +430,10 @@ func (a *Agent) controller(startingBalance btcutil.Amount) {
|
|||||||
delete(pendingOpens, nID)
|
delete(pendingOpens, nID)
|
||||||
pendingMtx.Unlock()
|
pendingMtx.Unlock()
|
||||||
|
|
||||||
|
// Trigger the autopilot controller to
|
||||||
|
// re-evaluate everything and possibly
|
||||||
|
// retry with a different node.
|
||||||
|
a.OnChannelOpenFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
}(chanCandidate)
|
}(chanCandidate)
|
||||||
|
@ -7,6 +7,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/roasbeef/btcd/btcec"
|
"github.com/roasbeef/btcd/btcec"
|
||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
"github.com/roasbeef/btcutil"
|
"github.com/roasbeef/btcutil"
|
||||||
@ -218,6 +220,119 @@ func TestAgentChannelOpenSignal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A mockFailingChanController always fails to open a channel.
|
||||||
|
type mockFailingChanController struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockFailingChanController) OpenChannel(target *btcec.PublicKey, amt btcutil.Amount,
|
||||||
|
addrs []net.Addr) error {
|
||||||
|
return errors.New("failure")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockFailingChanController) CloseChannel(chanPoint *wire.OutPoint) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (m *mockFailingChanController) SpliceIn(chanPoint *wire.OutPoint,
|
||||||
|
amt btcutil.Amount) (*Channel, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (m *mockFailingChanController) SpliceOut(chanPoint *wire.OutPoint,
|
||||||
|
amt btcutil.Amount) (*Channel, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ChannelController = (*mockFailingChanController)(nil)
|
||||||
|
|
||||||
|
// TestAgentChannelFailureSignal tests that if an autopilot channel fails to
|
||||||
|
// open, the agent is signalled to make a new decision.
|
||||||
|
func TestAgentChannelFailureSignal(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// First, we'll create all the dependencies that we'll need in order to
|
||||||
|
// create the autopilot agent.
|
||||||
|
self, err := randKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to generate key: %v", err)
|
||||||
|
}
|
||||||
|
heuristic := &mockHeuristic{
|
||||||
|
moreChansResps: make(chan moreChansResp),
|
||||||
|
directiveResps: make(chan []AttachmentDirective),
|
||||||
|
}
|
||||||
|
chanController := &mockFailingChanController{}
|
||||||
|
memGraph, _, _ := newMemChanGraph()
|
||||||
|
|
||||||
|
// With the dependencies we created, we can now create the initial
|
||||||
|
// agent itself.
|
||||||
|
testCfg := Config{
|
||||||
|
Self: self,
|
||||||
|
Heuristic: heuristic,
|
||||||
|
ChanController: chanController,
|
||||||
|
WalletBalance: func() (btcutil.Amount, error) {
|
||||||
|
return 0, nil
|
||||||
|
},
|
||||||
|
Graph: memGraph,
|
||||||
|
}
|
||||||
|
|
||||||
|
initialChans := []Channel{}
|
||||||
|
agent, err := New(testCfg, initialChans)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create agent: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the autopilot agent and all its dependencies we'll start the
|
||||||
|
// primary controller goroutine.
|
||||||
|
if err := agent.Start(); err != nil {
|
||||||
|
t.Fatalf("unable to start agent: %v", err)
|
||||||
|
}
|
||||||
|
defer agent.Stop()
|
||||||
|
|
||||||
|
// First ensure the agent will attempt to open a new channel. Return
|
||||||
|
// that we need more channels, and have 5BTC to use.
|
||||||
|
select {
|
||||||
|
case heuristic.moreChansResps <- moreChansResp{true, 5 * btcutil.SatoshiPerBitcoin}:
|
||||||
|
fmt.Println("Returning 5BTC from heuristic")
|
||||||
|
case <-time.After(time.Second * 10):
|
||||||
|
t.Fatal("heuristic wasn't queried in time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, the agent should now be querying the heuristic to
|
||||||
|
// request attachment directives, return a fake so the agent will attempt
|
||||||
|
// to open a channel.
|
||||||
|
var fakeDirective = AttachmentDirective{
|
||||||
|
PeerKey: self,
|
||||||
|
ChanAmt: btcutil.SatoshiPerBitcoin,
|
||||||
|
Addrs: []net.Addr{
|
||||||
|
&net.TCPAddr{
|
||||||
|
IP: bytes.Repeat([]byte("a"), 16),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case heuristic.directiveResps <- []AttachmentDirective{fakeDirective}:
|
||||||
|
fmt.Println("Returning a node to connect to from heuristic")
|
||||||
|
case <-time.After(time.Second * 10):
|
||||||
|
t.Fatal("heuristic wasn't queried in time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point the agent will attempt to create a channel and fail.
|
||||||
|
|
||||||
|
// Now ensure that the controller loop is re-executed.
|
||||||
|
select {
|
||||||
|
case heuristic.moreChansResps <- moreChansResp{true, 5 * btcutil.SatoshiPerBitcoin}:
|
||||||
|
fmt.Println("Returning need more channels from heuristic")
|
||||||
|
case <-time.After(time.Second * 10):
|
||||||
|
t.Fatal("heuristic wasn't queried in time")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case heuristic.directiveResps <- []AttachmentDirective{}:
|
||||||
|
fmt.Println("Returning an empty directives list")
|
||||||
|
case <-time.After(time.Second * 10):
|
||||||
|
t.Fatal("heuristic wasn't queried in time")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestAgentChannelCloseSignal ensures that once the agent receives an outside
|
// TestAgentChannelCloseSignal ensures that once the agent receives an outside
|
||||||
// signal of a channel belonging to the backing LN node being closed, then it
|
// signal of a channel belonging to the backing LN node being closed, then it
|
||||||
// will query the heuristic to make its next decision.
|
// will query the heuristic to make its next decision.
|
||||||
|
Loading…
Reference in New Issue
Block a user