diff --git a/templates/golang/bitcoin.go b/templates/golang/bitcoin.go new file mode 100644 index 0000000..21717b1 --- /dev/null +++ b/templates/golang/bitcoin.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "os/exec" + "strconv" +) + +// Mine n blocks using bitcoin cli. +func Mine(n int) error { + // Santiy check for fat fingering to stop us setting our laptops on + // fire, can be removed. + if n > 1000{ + return fmt.Errorf("trying to mine: %v blocks failed "+ + "santiy check", n) + } + + cmd := exec.Command("bitcoin-cli", "-generate", strconv.Itoa(n)) + + _, err := cmd.Output() + return err +} diff --git a/templates/golang/graph.go b/templates/golang/graph.go new file mode 100644 index 0000000..d62327d --- /dev/null +++ b/templates/golang/graph.go @@ -0,0 +1,160 @@ +package main + +import ( + "context" + "fmt" + "time" + + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/lndclient" + "github.com/lightningnetwork/lnd/routing/route" +) + +// GraphHarness is responsible for all graph related operations (P2P, channels, +// gossip etc). +type GraphHarness struct { + LndNodes Nodes +} + +type OpenChannelReq struct { + Source int + Dest route.Vertex + // Host is an optional host address for the node, we'll lookup in our + // graph if this value is empty. + Host string + CapacitySat btcutil.Amount + PushAmt btcutil.Amount + Private bool +} + +// OpenChannel is a blocking call that opens a channel from the source node +// provided to the target: +// - looks up a node in the graph +// - connects to it if we are not currently connected +// - opens a channel with the parameters provided +// - opens a channel and mines block to confirm it +// - waits for the channel to be active +func (c *GraphHarness) OpenChannel(ctx context.Context, + req OpenChannelReq) (*wire.OutPoint, error) { + + connected, err := c.PeerConnected(ctx, req.Source, req.Dest) + if err != nil { + return nil, err + } + + if !connected { + host := req.Host + if host == "" { + node, err := c.LookupNode( + ctx, req.Source, req.Dest, false, + ) + if err != nil { + return nil, err + } + + if len(node.Addresses) == 0 { + return nil, fmt.Errorf("no public address "+ + "for: %v", req.Dest) + } + + host = node.Addresses[0] + } + + err = c.ConnectPeer(ctx, req.Source, req.Dest, host) + if err != nil { + return nil, err + } + } + + sourceNode := c.LndNodes.GetNode(req.Source) + streamChan, errChan, err := sourceNode.Client.OpenChannelStream( + ctx, req.Dest, req.CapacitySat, req.PushAmt, req.Private, + ) + if err != nil { + return nil, err + } + for { + select { + case update := <-streamChan: + // Wait for the channel to be pending before we mine. + if update.ChanPending != nil { + if err := Mine(6); err != nil { + return nil, fmt.Errorf("could not "+ + "mine: %v", err) + } + } + + if update.ChanOpen != nil { + return outpointFromRPC( + update.ChanOpen.ChannelPoint, + ), nil + } + + case e := <-errChan: + return nil, e + + case <-ctx.Done(): + return nil, ctx.Err() + } + + } +} + +func (c *GraphHarness) LookupNode(ctx context.Context, source int, + dest route.Vertex, includeChannels bool) (*lndclient.NodeInfo, error) { + + sourceNode := c.LndNodes.GetNode(source) + destNode, err := sourceNode.Client.GetNodeInfo( + ctx, dest, includeChannels, + ) + if err != nil { + return nil, err + } + + return destNode, nil +} + +func (c *GraphHarness) ConnectPeer(ctx context.Context, source int, + dest route.Vertex, addr string) error { + + sourceNode := c.LndNodes.GetNode(source) + if err := sourceNode.Client.Connect(ctx, dest, addr, true); err != nil { + return err + } + + for i := 0; i < 5; i++ { + connected, err := c.PeerConnected(ctx, source, dest) + if err != nil || connected { + return err + } + + select { + case <-time.After(time.Second): + + case <-ctx.Done(): + return ctx.Err() + } + } + + return fmt.Errorf("timeout waiting for peer: %v to connect", dest) +} + +func (c *GraphHarness) PeerConnected(ctx context.Context, source int, + target route.Vertex) (bool, error) { + + sourceNode := c.LndNodes.GetNode(source) + + peers, err := sourceNode.Client.ListPeers(ctx) + if err != nil { + return false, nil + } + + for _, peer := range peers { + if peer.Pubkey == target { + return true, nil + } + } + + return false, nil +} diff --git a/templates/golang/utils.go b/templates/golang/utils.go index b471a7f..b400fe5 100644 --- a/templates/golang/utils.go +++ b/templates/golang/utils.go @@ -3,6 +3,9 @@ package main import ( "crypto/rand" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntypes" ) @@ -16,10 +19,34 @@ func genPreimage() lntypes.Preimage { panic(err) } - preimage, err:= lntypes.MakePreimage(randomBytes) - if err!=nil{ - panic(err) - } + preimage, err := lntypes.MakePreimage(randomBytes) + if err != nil { + panic(err) + } - return preimage + return preimage +} + +func outpointFromRPC(rpc *lnrpc.ChannelPoint) *wire.OutPoint { + var ( + hash *chainhash.Hash + err error + ) + + switch h := rpc.FundingTxid.(type) { + case *lnrpc.ChannelPoint_FundingTxidBytes: + hash, err = chainhash.NewHash(h.FundingTxidBytes) + + case *lnrpc.ChannelPoint_FundingTxidStr: + hash, err = chainhash.NewHashFromStr(h.FundingTxidStr) + + default: + panic("Unknown channel point type") + } + + if err != nil { + panic(err) + } + + return wire.NewOutPoint(hash, rpc.OutputIndex) }