From 89325a13b7a9d9615c86f3cb26aae33b41f86fb1 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Mon, 6 Aug 2018 14:02:31 -0700 Subject: [PATCH] autopilot+config: support opening private channels with autopilot agent --- autopilot/agent_test.go | 144 ++++++++++++++++++++++++++++++++++++++-- config.go | 1 + pilot.go | 14 ++-- 3 files changed, 148 insertions(+), 11 deletions(-) diff --git a/autopilot/agent_test.go b/autopilot/agent_test.go index e71432e45..73c94bd88 100644 --- a/autopilot/agent_test.go +++ b/autopilot/agent_test.go @@ -75,23 +75,27 @@ func (m *mockHeuristic) Select(self *btcec.PublicKey, graph ChannelGraph, var _ AttachmentHeuristic = (*mockHeuristic)(nil) type openChanIntent struct { - target *btcec.PublicKey - amt btcutil.Amount - addrs []net.Addr + target *btcec.PublicKey + amt btcutil.Amount + addrs []net.Addr + private bool } type mockChanController struct { openChanSignals chan openChanIntent + private bool } func (m *mockChanController) OpenChannel(target *btcec.PublicKey, amt btcutil.Amount, addrs []net.Addr) error { m.openChanSignals <- openChanIntent{ - target: target, - amt: amt, - addrs: addrs, + target: target, + amt: amt, + addrs: addrs, + private: m.private, } + return nil } @@ -704,6 +708,134 @@ func TestAgentImmediateAttach(t *testing.T) { } } +// TestAgentPrivateChannels ensure that only requests for private channels are +// sent if set. +func TestAgentPrivateChannels(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), + } + // The chanController should be initialized such that all of its open + // channel requests are for private channels. + chanController := &mockChanController{ + openChanSignals: make(chan openChanIntent), + private: true, + } + memGraph, _, _ := newMemChanGraph() + + // The wallet will start with 10 BTC available. + const walletBalance = btcutil.SatoshiPerBitcoin * 10 + + // With the dependencies we created, we can now create the initial + // agent itself. + cfg := Config{ + Self: self, + Heuristic: heuristic, + ChanController: chanController, + WalletBalance: func() (btcutil.Amount, error) { + return walletBalance, nil + }, + Graph: memGraph, + MaxPendingOpens: 10, + } + agent, err := New(cfg, nil) + if err != nil { + t.Fatalf("unable to create agent: %v", err) + } + + // With the autopilot agent and all its dependencies we'll star the + // primary controller goroutine. + if err := agent.Start(); err != nil { + t.Fatalf("unable to start agent: %v", err) + } + defer agent.Stop() + + const numChans = 5 + var wg sync.WaitGroup + + // The very first thing the agent should do is query the NeedMoreChans + // method on the passed heuristic. So we'll provide it with a response + // that will kick off the main loop. + wg.Add(1) + go func() { + defer wg.Done() + + // We'll send over a response indicating that it should + // establish more channels, and give it a budget of 5 BTC to do + // so. + resp := moreChansResp{ + needMore: true, + numMore: numChans, + amt: 5 * btcutil.SatoshiPerBitcoin, + } + select { + case heuristic.moreChansResps <- resp: + return + case <-time.After(time.Second * 10): + t.Fatalf("heuristic wasn't queried in time") + } + }() + + // We'll wait here for the agent to query the heuristic. If it doesn't + // do so within 10 seconds, then the test will fail out. + wg.Wait() + + // At this point, the agent should now be querying the heuristic to + // requests attachment directives. We'll generate 5 mock directives so + // it can progress within its loop. + directives := make([]AttachmentDirective, numChans) + for i := 0; i < numChans; i++ { + directives[i] = AttachmentDirective{ + PeerKey: self, + ChanAmt: btcutil.SatoshiPerBitcoin, + Addrs: []net.Addr{ + &net.TCPAddr{ + IP: bytes.Repeat([]byte("a"), 16), + }, + }, + } + } + + // With our fake directives created, we'll now send then to the agent + // as a return value for the Select function. + wg.Add(1) + go func() { + defer wg.Done() + + select { + case heuristic.directiveResps <- directives: + return + case <-time.After(time.Second * 10): + t.Fatalf("heuristic wasn't queried in time") + } + }() + + // We'll wait here for either the agent to query the heuristic to be + // queried, or for the timeout above to tick. + wg.Wait() + + // Finally, we should receive 5 calls to the OpenChannel method, each + // specifying that it's for a private channel. + for i := 0; i < numChans; i++ { + select { + case openChan := <-chanController.openChanSignals: + if !openChan.private { + t.Fatal("expected open channel request to be private") + } + case <-time.After(10 * time.Second): + t.Fatal("channel not opened in time") + } + } +} + // TestAgentPendingChannelState ensures that the agent properly factors in its // pending channel state when making decisions w.r.t if it needs more channels // or not, and if so, who is eligible to open new channels to. diff --git a/config.go b/config.go index 976f659fd..6e65afb7b 100644 --- a/config.go +++ b/config.go @@ -145,6 +145,7 @@ type autoPilotConfig struct { Allocation float64 `long:"allocation" description:"The percentage of total funds that should be committed to automatic channel establishment"` MinChannelSize int64 `long:"minchansize" description:"The smallest channel that the autopilot agent should create"` MaxChannelSize int64 `long:"maxchansize" description:"The largest channel that the autopilot agent should create"` + Private bool `long:"private" description:"Whether the channels created by the autopilot agent should be private or not. Private channels won't be announced to the network."` } type torConfig struct { diff --git a/pilot.go b/pilot.go index b1d8f0a90..64b029da0 100644 --- a/pilot.go +++ b/pilot.go @@ -16,7 +16,8 @@ import ( // chanController is an implementation of the autopilot.ChannelController // interface that's backed by a running lnd instance. type chanController struct { - server *server + server *server + private bool } // OpenChannel opens a channel to a target peer, with a capacity of the @@ -89,7 +90,7 @@ func (c *chanController) OpenChannel(target *btcec.PublicKey, minHtlc := lnwire.NewMSatFromSatoshis(1) updateStream, errChan := c.server.OpenChannel( - target, amt, 0, minHtlc, feePerKw, false, 0, + target, amt, 0, minHtlc, feePerKw, c.private, 0, ) select { @@ -148,9 +149,12 @@ func initAutoPilot(svr *server, cfg *autoPilotConfig) (*autopilot.Agent, error) // of the items that the autopilot agent needs to perform its duties. self := svr.identityPriv.PubKey() pilotCfg := autopilot.Config{ - Self: self, - Heuristic: prefAttachment, - ChanController: &chanController{svr}, + Self: self, + Heuristic: prefAttachment, + ChanController: &chanController{ + server: svr, + private: cfg.Private, + }, WalletBalance: func() (btcutil.Amount, error) { return svr.cc.wallet.ConfirmedBalance(1) },