Skip to content

Instantly share code, notes, and snippets.

@champo
Last active February 21, 2020 19:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save champo/8e90ce1ef38f2dd546814ff7898988c3 to your computer and use it in GitHub Desktop.
Save champo/8e90ce1ef38f2dd546814ff7898988c3 to your computer and use it in GitHub Desktop.
Virtual link consumer example
package main
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"log"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
v "github.com/lightningnetwork/lnd/lnrpc/virtualchannelsrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/zpay32"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"gopkg.in/macaroon.v2"
)
func main() {
cc, err := connection()
if err != nil {
panic(err)
}
c := v.NewVirtualChannelsClient(cc)
_ = c
// newPriv()
priv, pub := keys()
_ = pub
_ = priv
fundingId, _ := hex.DecodeString("f5e1d1f4b72ee88f3443e2f56983c393287760b618375faa1bbd0de64dd3ba96")
_ = fundingId
registerChannel(c, pub.SerializeCompressed(), fundingId)
preimage := invoice(priv)
stream, err := c.SubscribeVirtualForward(context.Background(), &v.SubscribeVirtualForwardRequest{})
if err != nil {
panic(err)
}
router := sphinx.NewRouter(priv, &chaincfg.RegressionNetParams, sphinx.NewMemoryReplayLog())
if err := router.Start(); err != nil {
panic(err)
}
onionProcessor := hop.NewOnionProcessor(router)
onionProcessor.Start()
for {
forward, err := stream.Recv()
if err == io.EOF {
fmt.Println("Stream ended")
return
}
fmt.Println("Received fwd for %v", hex.EncodeToString(forward.ChannelId))
if !bytes.Equal(forward.ChannelId, fundingId) {
continue
}
addHTLC := lnwire.NewUpdateAddHTLC()
err = addHTLC.Decode(bytes.NewBuffer(forward.Htlc), 0)
if err != nil {
fmt.Println("Failed to decoded incoming packet: %v", err)
continue
}
fmt.Println("Amount", addHTLC.Amount)
if !preimage.Matches(addHTLC.PaymentHash) {
fmt.Println("Got unknown payment hash %v", hex.EncodeToString(addHTLC.PaymentHash[:]))
continue
}
iterator, code := onionProcessor.DecodeHopIterator(
bytes.NewReader(addHTLC.OnionBlob[:]),
addHTLC.PaymentHash[:],
addHTLC.Expiry)
if code != lnwire.CodeNone {
fmt.Println("Failed decode sphinx due to %v", code.String())
continue
}
payload, err := iterator.HopPayload()
if err != nil {
fmt.Println("Failed sth", err)
}
fwdInfo := payload.FwdInfo
fmt.Printf("Onion info:\nHop %v\nAmt %v\nCLTV %v\n",
fwdInfo.NextHop,
fwdInfo.AmountToForward,
fwdInfo.OutgoingCTLV)
if fwdInfo.AmountToForward != addHTLC.Amount {
fmt.Printf("Last hop tried to steal from us: %v vs %v\n", fwdInfo.AmountToForward, addHTLC.Amount)
continue
}
// TODO: Signal we use payment_secret and extract it
settle(c, fundingId, preimage)
}
onionProcessor.Stop()
router.Stop()
}
func settle(c v.VirtualChannelsClient, chanId []byte, preimage lntypes.Preimage) {
c.SettleVirtualForward(context.Background(), &v.SettleVirtualForwardRequest{
ChannelId: chanId,
Preimage: preimage[:],
})
}
func invoice(priv *btcec.PrivateKey) lntypes.Preimage {
target := pubFromHex("020d105ad99776e163baf7854b25e142d1415e32761f5458642409d4a5f3b0f1c9")
var preimage lntypes.Preimage
rand.Read(preimage[:])
fmt.Printf("Preimage = %v\n", preimage.String())
fmt.Printf("Payment hash = %v\n", preimage.Hash().String())
hint := zpay32.RouteHint([]zpay32.HopHint{
zpay32.HopHint{
NodeID: target,
ChannelID: 1099511693312,
FeeBaseMSat: 1,
FeeProportionalMillionths: 1,
CLTVExpiryDelta: 144,
},
})
invoice, err := zpay32.NewInvoice(
&chaincfg.RegressionNetParams,
preimage.Hash(),
time.Now(),
zpay32.Description("Test it so hard"),
hint)
if err != nil {
panic(err)
}
signer := netann.NewNodeSigner(priv)
encoded, err := invoice.Encode(zpay32.MessageSigner{SignCompact: signer.SignDigestCompact})
if err != nil {
panic(err)
}
fmt.Println(encoded)
return preimage
}
func registerChannel(c v.VirtualChannelsClient, pubKey []byte, fundingId []byte) {
_, err := c.CreateVirtualChannel(context.Background(), &v.CreateVirtualChannelRequest{
NodePubKey: pubKey,
ChannelId: fundingId,
})
if err != nil {
panic(err)
}
}
func keys() (*btcec.PrivateKey, *btcec.PublicKey) {
privHex := "e5186c406375b7b9779aa3361c7751ba5e0abcd53f9302cf17a25e25e71a0bb3"
privBytes, _ := hex.DecodeString(privHex)
return btcec.PrivKeyFromBytes(btcec.S256(), privBytes)
}
func pubFromHex(str string) *btcec.PublicKey {
bytes, _ := hex.DecodeString(str)
key, _ := btcec.ParsePubKey(bytes, btcec.S256())
return key
}
func newPriv() {
priv, _ := btcec.NewPrivateKey(btcec.S256())
fmt.Printf(hex.EncodeToString(priv.Serialize()))
panic("")
}
func connection() (*grpc.ClientConn, error) {
creds, err := credentials.NewClientTLSFromFile("~/Library/Application Support/Lnd/tls.cert", "")
if err != nil {
return nil, err
}
macaroonBytes, err := ioutil.ReadFile("~/Library/Application Support/Lnd/data/chain/bitcoin/regtest/virtualchannels.macaroon")
if err != nil {
return nil, err
}
mac := &macaroon.Macaroon{}
if err = mac.UnmarshalBinary(macaroonBytes); err != nil {
return nil, err
}
log.Printf("attempting connection to node")
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
opts := []grpc.DialOption{
grpc.WithBlock(),
grpc.WithTransportCredentials(creds),
grpc.WithPerRPCCredentials(macaroons.NewMacaroonCredential(mac)),
}
return grpc.DialContext(ctx, "localhost:10000", opts...)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment