use internal cln id as correlation id

This commit is contained in:
Jesse de Wit
2022-12-29 23:54:16 +01:00
parent 3c558ab5ad
commit 2bea61d8e7
12 changed files with 1746 additions and 948 deletions

View File

@@ -3,6 +3,7 @@ package main
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"io"
"log"
@@ -10,7 +11,7 @@ import (
"sync"
"time"
"github.com/breez/lspd/cln_plugin"
"github.com/breez/lspd/cln_plugin/proto"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
@@ -24,7 +25,7 @@ import (
type ClnHtlcInterceptor struct {
pluginAddress string
client *ClnClient
pluginClient cln_plugin.ClnPluginClient
pluginClient proto.ClnPluginClient
initWg sync.WaitGroup
doneWg sync.WaitGroup
ctx context.Context
@@ -51,7 +52,7 @@ func (i *ClnHtlcInterceptor) Start() error {
return err
}
i.pluginClient = cln_plugin.NewClnPluginClient(conn)
i.pluginClient = proto.NewClnPluginClient(conn)
i.ctx = ctx
i.cancel = cancel
return i.intercept()
@@ -111,7 +112,7 @@ func (i *ClnHtlcInterceptor) intercept() error {
request.Htlc,
request.Onion.ShortChannelId,
request.Htlc.AmountMsat, //with fees
request.Onion.ForwardAmountMsat,
request.Onion.ForwardMsat,
request.Htlc.CltvExpiryRelative,
request.Htlc.CltvExpiry,
request.Htlc.PaymentHash,
@@ -120,28 +121,25 @@ func (i *ClnHtlcInterceptor) intercept() error {
i.doneWg.Add(1)
go func() {
interceptResult := intercept(request.Htlc.PaymentHash, request.Onion.ForwardAmountMsat, request.Htlc.CltvExpiry)
paymentHash, err := hex.DecodeString(request.Htlc.PaymentHash)
if err != nil {
interceptorClient.Send(i.defaultResolution(request))
i.doneWg.Done()
}
interceptResult := intercept(paymentHash, request.Onion.ForwardMsat, request.Htlc.CltvExpiry)
switch interceptResult.action {
case INTERCEPT_RESUME_WITH_ONION:
interceptorClient.Send(i.resumeWithOnion(request, interceptResult))
case INTERCEPT_FAIL_HTLC_WITH_CODE:
interceptorClient.Send(&cln_plugin.HtlcResolution{
Correlationid: request.Correlationid,
Outcome: &cln_plugin.HtlcResolution_Fail{
Fail: &cln_plugin.HtlcFail{
FailureMessage: i.mapFailureCode(interceptResult.failureCode),
},
},
})
interceptorClient.Send(
i.failWithCode(request, interceptResult.failureCode),
)
case INTERCEPT_RESUME:
fallthrough
default:
interceptorClient.Send(&cln_plugin.HtlcResolution{
Correlationid: request.Correlationid,
Outcome: &cln_plugin.HtlcResolution_Continue{
Continue: &cln_plugin.HtlcContinue{},
},
})
interceptorClient.Send(
i.defaultResolution(request),
)
}
i.doneWg.Done()
@@ -163,29 +161,51 @@ func (i *ClnHtlcInterceptor) WaitStarted() LightningClient {
return i.client
}
func (i *ClnHtlcInterceptor) resumeWithOnion(request *cln_plugin.HtlcAccepted, interceptResult interceptResult) *cln_plugin.HtlcResolution {
func (i *ClnHtlcInterceptor) resumeWithOnion(request *proto.HtlcAccepted, interceptResult interceptResult) *proto.HtlcResolution {
//decoding and encoding onion with alias in type 6 record.
newPayload, err := encodePayloadWithNextHop(request.Onion.Payload, interceptResult.channelId)
payload, err := hex.DecodeString(request.Onion.Payload)
if err != nil {
log.Printf("resumeWithOnion: hex.DecodeString(%v) error: %v", request.Onion.Payload, err)
return i.failWithCode(request, FAILURE_TEMPORARY_CHANNEL_FAILURE)
}
newPayload, err := encodePayloadWithNextHop(payload, interceptResult.channelId)
if err != nil {
log.Printf("encodePayloadWithNextHop error: %v", err)
return &cln_plugin.HtlcResolution{
return i.failWithCode(request, FAILURE_TEMPORARY_CHANNEL_FAILURE)
}
newPayloadStr := hex.EncodeToString(newPayload)
chanId := lnwire.NewChanIDFromOutPoint(interceptResult.channelPoint).String()
log.Printf("forwarding htlc to the destination node and a new private channel was opened")
return &proto.HtlcResolution{
Correlationid: request.Correlationid,
Outcome: &cln_plugin.HtlcResolution_Fail{
Fail: &cln_plugin.HtlcFail{
FailureMessage: i.mapFailureCode(FAILURE_TEMPORARY_CHANNEL_FAILURE),
Outcome: &proto.HtlcResolution_Continue{
Continue: &proto.HtlcContinue{
ForwardTo: &chanId,
Payload: &newPayloadStr,
},
},
}
}
chanId := lnwire.NewChanIDFromOutPoint(interceptResult.channelPoint)
log.Printf("forwarding htlc to the destination node and a new private channel was opened")
return &cln_plugin.HtlcResolution{
func (i *ClnHtlcInterceptor) defaultResolution(request *proto.HtlcAccepted) *proto.HtlcResolution {
return &proto.HtlcResolution{
Correlationid: request.Correlationid,
Outcome: &cln_plugin.HtlcResolution_ContinueWith{
ContinueWith: &cln_plugin.HtlcContinueWith{
ChannelId: chanId[:],
Payload: newPayload,
Outcome: &proto.HtlcResolution_Continue{
Continue: &proto.HtlcContinue{},
},
}
}
func (i *ClnHtlcInterceptor) failWithCode(request *proto.HtlcAccepted, code interceptFailureCode) *proto.HtlcResolution {
return &proto.HtlcResolution{
Correlationid: request.Correlationid,
Outcome: &proto.HtlcResolution_Fail{
Fail: &proto.HtlcFail{
Failure: &proto.HtlcFail_FailureMessage{
FailureMessage: i.mapFailureCode(code),
},
},
},
}
@@ -237,16 +257,16 @@ func encodePayloadWithNextHop(payload []byte, channelId uint64) ([]byte, error)
return newPayloadBuf.Bytes(), nil
}
func (i *ClnHtlcInterceptor) mapFailureCode(original interceptFailureCode) []byte {
func (i *ClnHtlcInterceptor) mapFailureCode(original interceptFailureCode) string {
switch original {
case FAILURE_TEMPORARY_CHANNEL_FAILURE:
return []byte{0x10, 0x07}
return "1007"
case FAILURE_TEMPORARY_NODE_FAILURE:
return []byte{0x20, 0x02}
return "2002"
case FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS:
return []byte{0x40, 0x0F}
return "400F"
default:
log.Printf("Unknown failure code %v, default to temporary channel failure.", original)
return []byte{0x10, 0x07} // temporary channel failure
return "1007" // temporary channel failure
}
}

117
cln_plugin/cln_messages.go Normal file
View File

@@ -0,0 +1,117 @@
package cln_plugin
import (
"encoding/json"
)
type Request struct {
Id json.RawMessage `json:"id,omitempty"`
Method string `json:"method"`
JsonRpc string `json:"jsonrpc"`
Params json.RawMessage `json:"params,omitempty"`
}
type Response struct {
Id json.RawMessage `json:"id"`
JsonRpc string `json:"jsonrpc"`
Result Result `json:"result,omitempty"`
Error *RpcError `json:"error,omitempty"`
}
type Result interface{}
type RpcError struct {
Code int `json:"code"`
Message string `json:"message"`
Data json.RawMessage `json:"data,omitempty"`
}
type Manifest struct {
Options []Option `json:"options"`
RpcMethods []*RpcMethod `json:"rpcmethods"`
Dynamic bool `json:"dynamic"`
Subscriptions []string `json:"subscriptions,omitempty"`
Hooks []Hook `json:"hooks,omitempty"`
FeatureBits *FeatureBits `json:"featurebits,omitempty"`
NonNumericIds bool `json:"nonnumericids"`
}
type Option struct {
Name string `json:"name"`
Type string `json:"type"`
Description string `json:"description"`
Default *string `json:"default,omitempty"`
Multi *bool `json:"multi,omitempty"`
Deprecated *bool `json:"deprecated,omitempty"`
}
type RpcMethod struct {
Name string `json:"name"`
Usage string `json:"usage"`
Description string `json:"description"`
LongDescription *string `json:"long_description,omitempty"`
Deprecated *bool `json:"deprecated,omitempty"`
}
type Hook struct {
Name string `json:"name"`
Before []string `json:"before,omitempty"`
}
type FeatureBits struct {
Node *string `json:"node,omitempty"`
Init *string `json:"init,omitempty"`
Channel *string `json:"channel,omitempty"`
Invoice *string `json:"invoice,omitempty"`
}
type InitMessage struct {
Options map[string]interface{} `json:"options,omitempty"`
Configuration *InitConfiguration `json:"configuration,omitempty"`
}
type InitConfiguration struct {
LightningDir string `json:"lightning-dir"`
RpcFile string `json:"rpc-file"`
Startup bool `json:"startup"`
Network string `json:"network"`
FeatureSet *FeatureBits `json:"feature_set"`
Proxy *Proxy `json:"proxy"`
TorV3Enabled bool `json:"torv3-enabled"`
AlwaysUseProxy bool `json:"always_use_proxy"`
}
type Proxy struct {
Type string `json:"type"`
Address string `json:"address"`
Port int `json:"port"`
}
type HtlcAccepted struct {
Onion *Onion `json:"onion"`
Htlc *Htlc `json:"htlc"`
ForwardTo string `json:"forward_to"`
}
type Onion struct {
Payload string `json:"payload"`
ShortChannelId string `json:"short_channel_id"`
ForwardMsat uint64 `json:"forward_msat"`
OutgoingCltvValue uint32 `json:"outgoing_cltv_value"`
SharedSecret string `json:"shared_secret"`
NextOnion string `json:"next_onion"`
}
type Htlc struct {
ShortChannelId string `json:"short_channel_id"`
Id uint64 `json:"id"`
AmountMsat uint64 `json:"amount_msat"`
CltvExpiry uint32 `json:"cltv_expiry"`
CltvExpiryRelative uint32 `json:"cltv_expiry_relative"`
PaymentHash string `json:"payment_hash"`
}
type LogNotification struct {
Level string `json:"level"`
Message string `json:"message"`
}

View File

@@ -1,110 +1,488 @@
package cln_plugin
import (
"encoding/hex"
"bufio"
"encoding/json"
"fmt"
"io"
"log"
"os"
"strings"
"sync"
"time"
)
"github.com/breez/lspd/basetypes"
"github.com/niftynei/glightning/glightning"
const (
SubscriberTimeoutOption = "lsp.subscribertimeout"
ListenAddressOption = "lsp.listen"
)
var (
DefaultSubscriberTimeout = "1m"
)
const (
MaxIntakeBuffer = 500 * 1024 * 1023
)
const (
SpecVersion = "2.0"
ParseError = -32700
InvalidRequest = -32600
MethodNotFound = -32601
InvalidParams = -32603
InternalErr = -32603
)
var (
TwoNewLines = []byte("\n\n")
)
type ClnPlugin struct {
done chan struct{}
server *server
plugin *glightning.Plugin
in *os.File
out *bufio.Writer
writeMtx sync.Mutex
}
func NewClnPlugin(server *server) *ClnPlugin {
func NewClnPlugin(in, out *os.File) *ClnPlugin {
c := &ClnPlugin{
server: server,
done: make(chan struct{}),
in: in,
out: bufio.NewWriter(out),
}
c.plugin = glightning.NewPlugin(c.onInit)
c.plugin.RegisterHooks(&glightning.Hooks{
HtlcAccepted: c.onHtlcAccepted,
})
return c
}
func (c *ClnPlugin) Start() error {
err := c.plugin.Start(os.Stdin, os.Stdout)
if err != nil {
log.Printf("Plugin error: %v", err)
// Starts the cln plugin.
// NOTE: The grpc server is started in the handleInit function.
func (c *ClnPlugin) Start() {
c.setupLogging()
go c.listen()
<-c.done
}
func (c *ClnPlugin) setupLogging() {
in, out := io.Pipe()
go func(in io.Reader) {
// everytime we get a new message, log it thru c-lightning
scanner := bufio.NewScanner(in)
for {
select {
case <-c.done:
return
default:
if !scanner.Scan() {
if err := scanner.Err(); err != nil {
log.Fatalf(
"can't print out to std err, killing: %v",
err,
)
}
}
for _, line := range strings.Split(scanner.Text(), "\n") {
c.log("info", line)
}
}
}
}(in)
log.SetFlags(log.Ltime | log.Lshortfile)
log.SetOutput(out)
}
// Stops the cln plugin. Drops any remaining work immediately.
// Pending htlcs will be replayed when cln starts again.
func (c *ClnPlugin) Stop() {
close(c.done)
s := c.server
if s != nil {
s.Stop()
}
}
// listens stdout for requests from cln and sends the requests to the
// appropriate handler in fifo order.
func (c *ClnPlugin) listen() error {
scanner := bufio.NewScanner(c.in)
buf := make([]byte, 1024)
scanner.Buffer(buf, MaxIntakeBuffer)
// cln messages are split by a double newline.
scanner.Split(scanDoubleNewline)
for {
select {
case <-c.done:
return nil
default:
if !scanner.Scan() {
if err := scanner.Err(); err != nil {
log.Fatal(err)
return err
}
return nil
}
func (c *ClnPlugin) Stop() {
c.plugin.Stop()
c.server.Stop()
msg := scanner.Bytes()
// TODO: Pipe logs to the proper place.
log.Println(string(msg))
// pass down a copy so things stay sane
msg_buf := make([]byte, len(msg))
copy(msg_buf, msg)
// NOTE: processMsg is synchronous, so it should only do quick work.
c.processMsg(msg_buf)
}
}
}
func (c *ClnPlugin) onInit(plugin *glightning.Plugin, options map[string]glightning.Option, config *glightning.Config) {
log.Printf("successfully init'd! %v\n", config.RpcFile)
log.Printf("Starting htlc grpc server.")
go func() {
err := c.server.Start()
if err == nil {
log.Printf("WARNING server stopped.")
} else {
log.Printf("ERROR Server stopped with error: %v", err)
}
}()
// processes a single message from cln. Sends the message to the appropriate
// handler.
func (c *ClnPlugin) processMsg(msg []byte) {
if len(msg) == 0 {
c.sendError(nil, InvalidRequest, "Invalid Request")
return
}
func (c *ClnPlugin) onHtlcAccepted(event *glightning.HtlcAcceptedEvent) (*glightning.HtlcAcceptedResponse, error) {
payload, err := hex.DecodeString(event.Onion.Payload)
// Right now we don't handle arrays of requests...
if msg[0] == '[' {
var requests []*Request
err := json.Unmarshal(msg, &requests)
if err != nil {
log.Printf("ERROR failed to decode payload %s: %v", event.Onion.Payload, err)
return nil, err
}
scid, err := basetypes.NewShortChannelIDFromString(event.Onion.ShortChannelId)
if err != nil {
log.Printf("ERROR failed to decode short channel id %s: %v", event.Onion.ShortChannelId, err)
return nil, err
}
ph, err := hex.DecodeString(event.Htlc.PaymentHash)
if err != nil {
log.Printf("ERROR failed to decode payment hash %s: %v", event.Onion.ShortChannelId, err)
return nil, err
c.sendError(
nil,
ParseError,
fmt.Sprintf("Parse error:%s [%s]", err.Error(), msg),
)
return
}
resp := c.server.Send(&HtlcAccepted{
Onion: &Onion{
Payload: payload,
ShortChannelId: uint64(*scid),
ForwardAmountMsat: event.Onion.ForwardAmount,
for _, request := range requests {
c.processRequest(request)
}
return
}
// Parse the received buffer into a request object.
var request Request
err := json.Unmarshal(msg, &request)
if err != nil {
log.Printf("failed to unmarshal request: %v", err)
c.sendError(
nil,
ParseError,
fmt.Sprintf("Parse error:%s [%s]", err.Error(), msg),
)
return
}
c.processRequest(&request)
}
func (c *ClnPlugin) processRequest(request *Request) {
// Make sure the spec version is expected.
if request.JsonRpc != SpecVersion {
c.sendError(
request.Id,
InvalidRequest,
fmt.Sprintf(
`Invalid jsonrpc, expected '%s' got '%s'`,
SpecVersion,
request.JsonRpc,
),
)
return
}
// Send the message to the appropriate handler.
switch request.Method {
case "getmanifest":
c.handleGetManifest(request)
case "init":
c.handleInit(request)
case "shutdown":
c.handleShutdown(request)
case "htlc_accepted":
c.handleHtlcAccepted(request)
default:
c.sendError(
request.Id,
MethodNotFound,
fmt.Sprintf("Method '%s' not found", request.Method),
)
}
}
// Returns this plugin's manifest to cln.
func (c *ClnPlugin) handleGetManifest(request *Request) {
c.sendToCln(&Response{
Id: request.Id,
JsonRpc: SpecVersion,
Result: &Manifest{
Options: []Option{
{
Name: ListenAddressOption,
Type: "string",
Description: "listen address for the htlc_accepted lsp " +
"grpc server",
},
{
Name: SubscriberTimeoutOption,
Type: "string",
Description: "htlc timeout duration when there is no " +
"subscriber to the grpc server. golang duration " +
"string.",
Default: &DefaultSubscriberTimeout,
},
},
RpcMethods: []*RpcMethod{},
Dynamic: true,
Hooks: []Hook{
{
Name: "htlc_accepted",
},
},
NonNumericIds: true,
Subscriptions: []string{
"shutdown",
},
Htlc: &HtlcOffer{
AmountMsat: event.Htlc.AmountMilliSatoshi,
CltvExpiryRelative: uint32(event.Htlc.CltvExpiryRelative),
CltvExpiry: uint32(event.Htlc.CltvExpiry),
PaymentHash: ph,
},
})
_, ok := resp.Outcome.(*HtlcResolution_Continue)
if ok {
return event.Continue(), nil
}
cont, ok := resp.Outcome.(*HtlcResolution_ContinueWith)
if ok {
chanId := hex.EncodeToString(cont.ContinueWith.ChannelId)
pl := hex.EncodeToString(cont.ContinueWith.Payload)
return event.ContinueWith(chanId, pl), nil
// Handles plugin initialization. Parses startup options and starts the grpc
// server.
func (c *ClnPlugin) handleInit(request *Request) {
// Deserialize the init message.
var initMsg InitMessage
err := json.Unmarshal(request.Params, &initMsg)
if err != nil {
c.sendError(
request.Id,
ParseError,
fmt.Sprintf(
"Error parsing init params:%s [%s]",
err.Error(),
request.Params,
),
)
return
}
fail, ok := resp.Outcome.(*HtlcResolution_Fail)
if ok {
fm := hex.EncodeToString(fail.Fail.FailureMessage)
return event.Fail(fm), err
// Get the listen address option.
l, ok := initMsg.Options[ListenAddressOption]
if !ok {
c.sendError(
request.Id,
InvalidParams,
fmt.Sprintf("Missing option '%s'", ListenAddressOption),
)
return
}
log.Printf("Unexpected htlc resolution type %T: %+v", resp.Outcome, resp.Outcome)
return event.Fail("1007"), nil // temporary channel failure
addr, ok := l.(string)
if !ok || addr == "" {
c.sendError(
request.Id,
InvalidParams,
fmt.Sprintf(
"Invalid value '%v' for option '%s'",
l,
ListenAddressOption,
),
)
return
}
// Get the subscriber timeout option.
t, ok := initMsg.Options[SubscriberTimeoutOption]
if !ok {
c.sendError(
request.Id,
InvalidParams,
fmt.Sprintf("Missing option '%s'", SubscriberTimeoutOption),
)
return
}
s, ok := t.(string)
if !ok || s == "" {
c.sendError(
request.Id,
InvalidParams,
fmt.Sprintf(
"Invalid value '%v' for option '%s'",
t,
SubscriberTimeoutOption,
),
)
return
}
subscriberTimeout, err := time.ParseDuration(s)
if err != nil {
c.sendError(
request.Id,
InvalidParams,
fmt.Sprintf(
"Invalid value '%v' for option '%s'",
s,
SubscriberTimeoutOption,
),
)
return
}
// Start the grpc server.
c.server = NewServer(addr, subscriberTimeout)
go c.server.Start()
err = c.server.WaitStarted()
if err != nil {
c.sendError(
request.Id,
InternalErr,
fmt.Sprintf("Failed to start server: %s", err.Error()),
)
return
}
// Listen for responses from the grpc server.
go c.listenServer()
// Let cln know the plugin is initialized.
c.sendToCln(&Response{
Id: request.Id,
JsonRpc: SpecVersion,
})
}
// Listens to responses to htlc_accepted requests from the grpc server.
func (c *ClnPlugin) listenServer() {
for {
select {
case <-c.done:
return
default:
id, result := c.server.Receive()
// The server may return nil if it is stopped.
if result == nil {
continue
}
serid, _ := json.Marshal(&id)
c.sendToCln(&Response{
Id: serid,
JsonRpc: SpecVersion,
Result: result,
})
}
}
}
// Handles the shutdown message. Stops any work immediately.
func (c *ClnPlugin) handleShutdown(request *Request) {
c.Stop()
}
// Sends a htlc_accepted message to the grpc server.
func (c *ClnPlugin) handleHtlcAccepted(request *Request) {
var htlc HtlcAccepted
err := json.Unmarshal(request.Params, &htlc)
if err != nil {
c.sendError(
request.Id,
ParseError,
fmt.Sprintf(
"Error parsing htlc_accepted params:%s [%s]",
err.Error(),
request.Params,
),
)
return
}
c.server.Send(c.idToString(request.Id), &htlc)
}
// Sends an error to cln.
func (c *ClnPlugin) sendError(id json.RawMessage, code int, message string) {
resp := &Response{
JsonRpc: SpecVersion,
Error: &RpcError{
Code: code,
Message: message,
},
}
if len(id) > 0 {
resp.Id = id
}
c.sendToCln(resp)
}
// converts a raw cln id to string. The CLN id can either be an integer or a
// string. if it's a string, the quotes are removed.
func (c *ClnPlugin) idToString(id json.RawMessage) string {
if len(id) == 0 {
return ""
}
str := string(id)
str = strings.TrimSpace(str)
str = strings.Trim(str, "\"")
str = strings.Trim(str, "'")
return str
}
// Sends a message to cln.
func (c *ClnPlugin) sendToCln(msg interface{}) {
c.writeMtx.Lock()
defer c.writeMtx.Unlock()
// TODO: log
data, err := json.Marshal(msg)
if err != nil {
log.Println(err.Error())
return
}
data = append(data, TwoNewLines...)
c.out.Write(data)
c.out.Flush()
}
func (c *ClnPlugin) log(level string, message string) {
params, _ := json.Marshal(&LogNotification{
Level: level,
Message: message,
})
c.sendToCln(&Request{
Method: "log",
JsonRpc: SpecVersion,
Params: params,
})
}
// Helper method for the bufio scanner to split messages on double newlines.
func scanDoubleNewline(
data []byte,
atEOF bool,
) (advance int, token []byte, err error) {
for i := 0; i < len(data); i++ {
if data[i] == '\n' && (i+1) < len(data) && data[i+1] == '\n' {
return i + 2, data[:i], nil
}
}
// this trashes anything left over in
// the buffer if we're at EOF, with no /n/n present
return 0, nil, nil
}

View File

@@ -1,674 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.8
// source: cln_plugin.proto
package cln_plugin
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type HtlcAccepted struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Correlationid uint64 `protobuf:"varint,1,opt,name=correlationid,proto3" json:"correlationid,omitempty"`
Onion *Onion `protobuf:"bytes,2,opt,name=onion,proto3" json:"onion,omitempty"`
Htlc *HtlcOffer `protobuf:"bytes,3,opt,name=htlc,proto3" json:"htlc,omitempty"`
}
func (x *HtlcAccepted) Reset() {
*x = HtlcAccepted{}
if protoimpl.UnsafeEnabled {
mi := &file_cln_plugin_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcAccepted) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcAccepted) ProtoMessage() {}
func (x *HtlcAccepted) ProtoReflect() protoreflect.Message {
mi := &file_cln_plugin_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcAccepted.ProtoReflect.Descriptor instead.
func (*HtlcAccepted) Descriptor() ([]byte, []int) {
return file_cln_plugin_proto_rawDescGZIP(), []int{0}
}
func (x *HtlcAccepted) GetCorrelationid() uint64 {
if x != nil {
return x.Correlationid
}
return 0
}
func (x *HtlcAccepted) GetOnion() *Onion {
if x != nil {
return x.Onion
}
return nil
}
func (x *HtlcAccepted) GetHtlc() *HtlcOffer {
if x != nil {
return x.Htlc
}
return nil
}
type Onion struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
ShortChannelId uint64 `protobuf:"varint,2,opt,name=short_channel_id,json=shortChannelId,proto3" json:"short_channel_id,omitempty"`
ForwardAmountMsat uint64 `protobuf:"varint,3,opt,name=forward_amount_msat,json=forwardAmountMsat,proto3" json:"forward_amount_msat,omitempty"`
}
func (x *Onion) Reset() {
*x = Onion{}
if protoimpl.UnsafeEnabled {
mi := &file_cln_plugin_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Onion) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Onion) ProtoMessage() {}
func (x *Onion) ProtoReflect() protoreflect.Message {
mi := &file_cln_plugin_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Onion.ProtoReflect.Descriptor instead.
func (*Onion) Descriptor() ([]byte, []int) {
return file_cln_plugin_proto_rawDescGZIP(), []int{1}
}
func (x *Onion) GetPayload() []byte {
if x != nil {
return x.Payload
}
return nil
}
func (x *Onion) GetShortChannelId() uint64 {
if x != nil {
return x.ShortChannelId
}
return 0
}
func (x *Onion) GetForwardAmountMsat() uint64 {
if x != nil {
return x.ForwardAmountMsat
}
return 0
}
type HtlcOffer struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AmountMsat uint64 `protobuf:"varint,2,opt,name=amount_msat,json=amountMsat,proto3" json:"amount_msat,omitempty"`
CltvExpiryRelative uint32 `protobuf:"varint,3,opt,name=cltv_expiry_relative,json=cltvExpiryRelative,proto3" json:"cltv_expiry_relative,omitempty"`
CltvExpiry uint32 `protobuf:"varint,4,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"`
PaymentHash []byte `protobuf:"bytes,5,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
}
func (x *HtlcOffer) Reset() {
*x = HtlcOffer{}
if protoimpl.UnsafeEnabled {
mi := &file_cln_plugin_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcOffer) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcOffer) ProtoMessage() {}
func (x *HtlcOffer) ProtoReflect() protoreflect.Message {
mi := &file_cln_plugin_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcOffer.ProtoReflect.Descriptor instead.
func (*HtlcOffer) Descriptor() ([]byte, []int) {
return file_cln_plugin_proto_rawDescGZIP(), []int{2}
}
func (x *HtlcOffer) GetAmountMsat() uint64 {
if x != nil {
return x.AmountMsat
}
return 0
}
func (x *HtlcOffer) GetCltvExpiryRelative() uint32 {
if x != nil {
return x.CltvExpiryRelative
}
return 0
}
func (x *HtlcOffer) GetCltvExpiry() uint32 {
if x != nil {
return x.CltvExpiry
}
return 0
}
func (x *HtlcOffer) GetPaymentHash() []byte {
if x != nil {
return x.PaymentHash
}
return nil
}
type HtlcResolution struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Correlationid uint64 `protobuf:"varint,1,opt,name=correlationid,proto3" json:"correlationid,omitempty"`
// Types that are assignable to Outcome:
// *HtlcResolution_Fail
// *HtlcResolution_Continue
// *HtlcResolution_ContinueWith
Outcome isHtlcResolution_Outcome `protobuf_oneof:"outcome"`
}
func (x *HtlcResolution) Reset() {
*x = HtlcResolution{}
if protoimpl.UnsafeEnabled {
mi := &file_cln_plugin_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcResolution) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcResolution) ProtoMessage() {}
func (x *HtlcResolution) ProtoReflect() protoreflect.Message {
mi := &file_cln_plugin_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcResolution.ProtoReflect.Descriptor instead.
func (*HtlcResolution) Descriptor() ([]byte, []int) {
return file_cln_plugin_proto_rawDescGZIP(), []int{3}
}
func (x *HtlcResolution) GetCorrelationid() uint64 {
if x != nil {
return x.Correlationid
}
return 0
}
func (m *HtlcResolution) GetOutcome() isHtlcResolution_Outcome {
if m != nil {
return m.Outcome
}
return nil
}
func (x *HtlcResolution) GetFail() *HtlcFail {
if x, ok := x.GetOutcome().(*HtlcResolution_Fail); ok {
return x.Fail
}
return nil
}
func (x *HtlcResolution) GetContinue() *HtlcContinue {
if x, ok := x.GetOutcome().(*HtlcResolution_Continue); ok {
return x.Continue
}
return nil
}
func (x *HtlcResolution) GetContinueWith() *HtlcContinueWith {
if x, ok := x.GetOutcome().(*HtlcResolution_ContinueWith); ok {
return x.ContinueWith
}
return nil
}
type isHtlcResolution_Outcome interface {
isHtlcResolution_Outcome()
}
type HtlcResolution_Fail struct {
Fail *HtlcFail `protobuf:"bytes,2,opt,name=fail,proto3,oneof"`
}
type HtlcResolution_Continue struct {
Continue *HtlcContinue `protobuf:"bytes,3,opt,name=continue,proto3,oneof"`
}
type HtlcResolution_ContinueWith struct {
ContinueWith *HtlcContinueWith `protobuf:"bytes,4,opt,name=continue_with,json=continueWith,proto3,oneof"`
}
func (*HtlcResolution_Fail) isHtlcResolution_Outcome() {}
func (*HtlcResolution_Continue) isHtlcResolution_Outcome() {}
func (*HtlcResolution_ContinueWith) isHtlcResolution_Outcome() {}
type HtlcFail struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
FailureMessage []byte `protobuf:"bytes,1,opt,name=failure_message,json=failureMessage,proto3" json:"failure_message,omitempty"`
}
func (x *HtlcFail) Reset() {
*x = HtlcFail{}
if protoimpl.UnsafeEnabled {
mi := &file_cln_plugin_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcFail) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcFail) ProtoMessage() {}
func (x *HtlcFail) ProtoReflect() protoreflect.Message {
mi := &file_cln_plugin_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcFail.ProtoReflect.Descriptor instead.
func (*HtlcFail) Descriptor() ([]byte, []int) {
return file_cln_plugin_proto_rawDescGZIP(), []int{4}
}
func (x *HtlcFail) GetFailureMessage() []byte {
if x != nil {
return x.FailureMessage
}
return nil
}
type HtlcContinue struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *HtlcContinue) Reset() {
*x = HtlcContinue{}
if protoimpl.UnsafeEnabled {
mi := &file_cln_plugin_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcContinue) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcContinue) ProtoMessage() {}
func (x *HtlcContinue) ProtoReflect() protoreflect.Message {
mi := &file_cln_plugin_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcContinue.ProtoReflect.Descriptor instead.
func (*HtlcContinue) Descriptor() ([]byte, []int) {
return file_cln_plugin_proto_rawDescGZIP(), []int{5}
}
type HtlcContinueWith struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ChannelId []byte `protobuf:"bytes,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"`
Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"`
}
func (x *HtlcContinueWith) Reset() {
*x = HtlcContinueWith{}
if protoimpl.UnsafeEnabled {
mi := &file_cln_plugin_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcContinueWith) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcContinueWith) ProtoMessage() {}
func (x *HtlcContinueWith) ProtoReflect() protoreflect.Message {
mi := &file_cln_plugin_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcContinueWith.ProtoReflect.Descriptor instead.
func (*HtlcContinueWith) Descriptor() ([]byte, []int) {
return file_cln_plugin_proto_rawDescGZIP(), []int{6}
}
func (x *HtlcContinueWith) GetChannelId() []byte {
if x != nil {
return x.ChannelId
}
return nil
}
func (x *HtlcContinueWith) GetPayload() []byte {
if x != nil {
return x.Payload
}
return nil
}
var File_cln_plugin_proto protoreflect.FileDescriptor
var file_cln_plugin_proto_rawDesc = []byte{
0x0a, 0x10, 0x63, 0x6c, 0x6e, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x22, 0x72, 0x0a, 0x0c, 0x48, 0x74, 0x6c, 0x63, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74,
0x65, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65,
0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x05, 0x6f, 0x6e, 0x69, 0x6f,
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x4f, 0x6e, 0x69, 0x6f, 0x6e, 0x52,
0x05, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x04, 0x68, 0x74, 0x6c, 0x63, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x4f, 0x66, 0x66, 0x65, 0x72,
0x52, 0x04, 0x68, 0x74, 0x6c, 0x63, 0x22, 0x7b, 0x0a, 0x05, 0x4f, 0x6e, 0x69, 0x6f, 0x6e, 0x12,
0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x68, 0x6f,
0x72, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
0x6c, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x61,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04,
0x52, 0x11, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d,
0x73, 0x61, 0x74, 0x22, 0xa2, 0x01, 0x0a, 0x09, 0x48, 0x74, 0x6c, 0x63, 0x4f, 0x66, 0x66, 0x65,
0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73,
0x61, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72,
0x79, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d,
0x52, 0x12, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x52, 0x65, 0x6c, 0x61,
0x74, 0x69, 0x76, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70,
0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x45,
0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74,
0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79,
0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x22, 0xc9, 0x01, 0x0a, 0x0e, 0x48, 0x74, 0x6c,
0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x63,
0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x69,
0x64, 0x12, 0x1f, 0x0a, 0x04, 0x66, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x09, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x46, 0x61, 0x69, 0x6c, 0x48, 0x00, 0x52, 0x04, 0x66, 0x61,
0x69, 0x6c, 0x12, 0x2b, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x69,
0x6e, 0x75, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x12,
0x38, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x5f, 0x77, 0x69, 0x74, 0x68,
0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x43, 0x6f, 0x6e,
0x74, 0x69, 0x6e, 0x75, 0x65, 0x57, 0x69, 0x74, 0x68, 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6f, 0x6e,
0x74, 0x69, 0x6e, 0x75, 0x65, 0x57, 0x69, 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x6f, 0x75, 0x74,
0x63, 0x6f, 0x6d, 0x65, 0x22, 0x33, 0x0a, 0x08, 0x48, 0x74, 0x6c, 0x63, 0x46, 0x61, 0x69, 0x6c,
0x12, 0x27, 0x0a, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75,
0x72, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x0e, 0x0a, 0x0c, 0x48, 0x74, 0x6c,
0x63, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x22, 0x4b, 0x0a, 0x10, 0x48, 0x74, 0x6c,
0x63, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x57, 0x69, 0x74, 0x68, 0x12, 0x1d, 0x0a,
0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07,
0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70,
0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x32, 0x3d, 0x0a, 0x09, 0x43, 0x6c, 0x6e, 0x50, 0x6c, 0x75,
0x67, 0x69, 0x6e, 0x12, 0x30, 0x0a, 0x0a, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61,
0x6d, 0x12, 0x0f, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69,
0x6f, 0x6e, 0x1a, 0x0d, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65,
0x64, 0x28, 0x01, 0x30, 0x01, 0x42, 0x22, 0x5a, 0x20, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x72, 0x65, 0x65, 0x7a, 0x2f, 0x6c, 0x73, 0x70, 0x64, 0x2f, 0x63,
0x6c, 0x6e, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
file_cln_plugin_proto_rawDescOnce sync.Once
file_cln_plugin_proto_rawDescData = file_cln_plugin_proto_rawDesc
)
func file_cln_plugin_proto_rawDescGZIP() []byte {
file_cln_plugin_proto_rawDescOnce.Do(func() {
file_cln_plugin_proto_rawDescData = protoimpl.X.CompressGZIP(file_cln_plugin_proto_rawDescData)
})
return file_cln_plugin_proto_rawDescData
}
var file_cln_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_cln_plugin_proto_goTypes = []interface{}{
(*HtlcAccepted)(nil), // 0: HtlcAccepted
(*Onion)(nil), // 1: Onion
(*HtlcOffer)(nil), // 2: HtlcOffer
(*HtlcResolution)(nil), // 3: HtlcResolution
(*HtlcFail)(nil), // 4: HtlcFail
(*HtlcContinue)(nil), // 5: HtlcContinue
(*HtlcContinueWith)(nil), // 6: HtlcContinueWith
}
var file_cln_plugin_proto_depIdxs = []int32{
1, // 0: HtlcAccepted.onion:type_name -> Onion
2, // 1: HtlcAccepted.htlc:type_name -> HtlcOffer
4, // 2: HtlcResolution.fail:type_name -> HtlcFail
5, // 3: HtlcResolution.continue:type_name -> HtlcContinue
6, // 4: HtlcResolution.continue_with:type_name -> HtlcContinueWith
3, // 5: ClnPlugin.HtlcStream:input_type -> HtlcResolution
0, // 6: ClnPlugin.HtlcStream:output_type -> HtlcAccepted
6, // [6:7] is the sub-list for method output_type
5, // [5:6] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
}
func init() { file_cln_plugin_proto_init() }
func file_cln_plugin_proto_init() {
if File_cln_plugin_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_cln_plugin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcAccepted); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_cln_plugin_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Onion); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_cln_plugin_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcOffer); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_cln_plugin_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcResolution); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_cln_plugin_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcFail); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_cln_plugin_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcContinue); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_cln_plugin_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcContinueWith); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_cln_plugin_proto_msgTypes[3].OneofWrappers = []interface{}{
(*HtlcResolution_Fail)(nil),
(*HtlcResolution_Continue)(nil),
(*HtlcResolution_ContinueWith)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_cln_plugin_proto_rawDesc,
NumEnums: 0,
NumMessages: 7,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_cln_plugin_proto_goTypes,
DependencyIndexes: file_cln_plugin_proto_depIdxs,
MessageInfos: file_cln_plugin_proto_msgTypes,
}.Build()
File_cln_plugin_proto = out.File
file_cln_plugin_proto_rawDesc = nil
file_cln_plugin_proto_goTypes = nil
file_cln_plugin_proto_depIdxs = nil
}

View File

@@ -1,31 +1,12 @@
package main
import (
"log"
"os"
"os/signal"
"syscall"
"github.com/breez/lspd/cln_plugin"
)
func main() {
listen := os.Getenv("LISTEN_ADDRESS")
server := cln_plugin.NewServer(listen)
plugin := cln_plugin.NewClnPlugin(server)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGINT)
go func() {
sig := <-c
log.Printf("Received stop signal %v. Stopping.", sig)
plugin.Stop()
}()
err := plugin.Start()
if err == nil {
log.Printf("cln plugin stopped.")
} else {
log.Printf("cln plugin stopped with error: %v", err)
}
plugin := cln_plugin.NewClnPlugin(os.Stdin, os.Stdout)
plugin.Start()
}

View File

@@ -2,4 +2,4 @@
SCRIPTDIR=$(dirname $0)
PROTO_ROOT=$SCRIPTDIR/proto
protoc --go_out=$SCRIPTDIR --go_opt=paths=source_relative --go-grpc_out=$SCRIPTDIR --go-grpc_opt=paths=source_relative -I=$PROTO_ROOT $PROTO_ROOT/*
protoc --go_out=$PROTO_ROOT --go_opt=paths=source_relative --go-grpc_out=$PROTO_ROOT --go-grpc_opt=paths=source_relative -I=$PROTO_ROOT $PROTO_ROOT/*.proto

View File

@@ -0,0 +1,787 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.12
// source: cln_plugin.proto
package proto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type HtlcAccepted struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Correlationid string `protobuf:"bytes,1,opt,name=correlationid,proto3" json:"correlationid,omitempty"`
Onion *Onion `protobuf:"bytes,2,opt,name=onion,proto3" json:"onion,omitempty"`
Htlc *Htlc `protobuf:"bytes,3,opt,name=htlc,proto3" json:"htlc,omitempty"`
ForwardTo string `protobuf:"bytes,4,opt,name=forward_to,json=forwardTo,proto3" json:"forward_to,omitempty"`
}
func (x *HtlcAccepted) Reset() {
*x = HtlcAccepted{}
if protoimpl.UnsafeEnabled {
mi := &file_cln_plugin_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcAccepted) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcAccepted) ProtoMessage() {}
func (x *HtlcAccepted) ProtoReflect() protoreflect.Message {
mi := &file_cln_plugin_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcAccepted.ProtoReflect.Descriptor instead.
func (*HtlcAccepted) Descriptor() ([]byte, []int) {
return file_cln_plugin_proto_rawDescGZIP(), []int{0}
}
func (x *HtlcAccepted) GetCorrelationid() string {
if x != nil {
return x.Correlationid
}
return ""
}
func (x *HtlcAccepted) GetOnion() *Onion {
if x != nil {
return x.Onion
}
return nil
}
func (x *HtlcAccepted) GetHtlc() *Htlc {
if x != nil {
return x.Htlc
}
return nil
}
func (x *HtlcAccepted) GetForwardTo() string {
if x != nil {
return x.ForwardTo
}
return ""
}
type Onion struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Payload string `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
ShortChannelId string `protobuf:"bytes,2,opt,name=short_channel_id,json=shortChannelId,proto3" json:"short_channel_id,omitempty"`
ForwardMsat uint64 `protobuf:"varint,3,opt,name=forward_msat,json=forwardMsat,proto3" json:"forward_msat,omitempty"`
OutgoingCltvValue uint32 `protobuf:"varint,4,opt,name=outgoing_cltv_value,json=outgoingCltvValue,proto3" json:"outgoing_cltv_value,omitempty"`
SharedSecret string `protobuf:"bytes,5,opt,name=shared_secret,json=sharedSecret,proto3" json:"shared_secret,omitempty"`
NextOnion string `protobuf:"bytes,6,opt,name=next_onion,json=nextOnion,proto3" json:"next_onion,omitempty"`
}
func (x *Onion) Reset() {
*x = Onion{}
if protoimpl.UnsafeEnabled {
mi := &file_cln_plugin_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Onion) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Onion) ProtoMessage() {}
func (x *Onion) ProtoReflect() protoreflect.Message {
mi := &file_cln_plugin_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Onion.ProtoReflect.Descriptor instead.
func (*Onion) Descriptor() ([]byte, []int) {
return file_cln_plugin_proto_rawDescGZIP(), []int{1}
}
func (x *Onion) GetPayload() string {
if x != nil {
return x.Payload
}
return ""
}
func (x *Onion) GetShortChannelId() string {
if x != nil {
return x.ShortChannelId
}
return ""
}
func (x *Onion) GetForwardMsat() uint64 {
if x != nil {
return x.ForwardMsat
}
return 0
}
func (x *Onion) GetOutgoingCltvValue() uint32 {
if x != nil {
return x.OutgoingCltvValue
}
return 0
}
func (x *Onion) GetSharedSecret() string {
if x != nil {
return x.SharedSecret
}
return ""
}
func (x *Onion) GetNextOnion() string {
if x != nil {
return x.NextOnion
}
return ""
}
type Htlc struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ShortChannelId string `protobuf:"bytes,1,opt,name=short_channel_id,json=shortChannelId,proto3" json:"short_channel_id,omitempty"`
Id uint64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"`
AmountMsat uint64 `protobuf:"varint,3,opt,name=amount_msat,json=amountMsat,proto3" json:"amount_msat,omitempty"`
CltvExpiry uint32 `protobuf:"varint,4,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"`
CltvExpiryRelative uint32 `protobuf:"varint,5,opt,name=cltv_expiry_relative,json=cltvExpiryRelative,proto3" json:"cltv_expiry_relative,omitempty"`
PaymentHash string `protobuf:"bytes,6,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
}
func (x *Htlc) Reset() {
*x = Htlc{}
if protoimpl.UnsafeEnabled {
mi := &file_cln_plugin_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Htlc) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Htlc) ProtoMessage() {}
func (x *Htlc) ProtoReflect() protoreflect.Message {
mi := &file_cln_plugin_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Htlc.ProtoReflect.Descriptor instead.
func (*Htlc) Descriptor() ([]byte, []int) {
return file_cln_plugin_proto_rawDescGZIP(), []int{2}
}
func (x *Htlc) GetShortChannelId() string {
if x != nil {
return x.ShortChannelId
}
return ""
}
func (x *Htlc) GetId() uint64 {
if x != nil {
return x.Id
}
return 0
}
func (x *Htlc) GetAmountMsat() uint64 {
if x != nil {
return x.AmountMsat
}
return 0
}
func (x *Htlc) GetCltvExpiry() uint32 {
if x != nil {
return x.CltvExpiry
}
return 0
}
func (x *Htlc) GetCltvExpiryRelative() uint32 {
if x != nil {
return x.CltvExpiryRelative
}
return 0
}
func (x *Htlc) GetPaymentHash() string {
if x != nil {
return x.PaymentHash
}
return ""
}
type HtlcResolution struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Correlationid string `protobuf:"bytes,1,opt,name=correlationid,proto3" json:"correlationid,omitempty"`
// Types that are assignable to Outcome:
// *HtlcResolution_Fail
// *HtlcResolution_Continue
// *HtlcResolution_Resolve
Outcome isHtlcResolution_Outcome `protobuf_oneof:"outcome"`
}
func (x *HtlcResolution) Reset() {
*x = HtlcResolution{}
if protoimpl.UnsafeEnabled {
mi := &file_cln_plugin_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcResolution) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcResolution) ProtoMessage() {}
func (x *HtlcResolution) ProtoReflect() protoreflect.Message {
mi := &file_cln_plugin_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcResolution.ProtoReflect.Descriptor instead.
func (*HtlcResolution) Descriptor() ([]byte, []int) {
return file_cln_plugin_proto_rawDescGZIP(), []int{3}
}
func (x *HtlcResolution) GetCorrelationid() string {
if x != nil {
return x.Correlationid
}
return ""
}
func (m *HtlcResolution) GetOutcome() isHtlcResolution_Outcome {
if m != nil {
return m.Outcome
}
return nil
}
func (x *HtlcResolution) GetFail() *HtlcFail {
if x, ok := x.GetOutcome().(*HtlcResolution_Fail); ok {
return x.Fail
}
return nil
}
func (x *HtlcResolution) GetContinue() *HtlcContinue {
if x, ok := x.GetOutcome().(*HtlcResolution_Continue); ok {
return x.Continue
}
return nil
}
func (x *HtlcResolution) GetResolve() *HtlcResolve {
if x, ok := x.GetOutcome().(*HtlcResolution_Resolve); ok {
return x.Resolve
}
return nil
}
type isHtlcResolution_Outcome interface {
isHtlcResolution_Outcome()
}
type HtlcResolution_Fail struct {
Fail *HtlcFail `protobuf:"bytes,2,opt,name=fail,proto3,oneof"`
}
type HtlcResolution_Continue struct {
Continue *HtlcContinue `protobuf:"bytes,3,opt,name=continue,proto3,oneof"`
}
type HtlcResolution_Resolve struct {
Resolve *HtlcResolve `protobuf:"bytes,4,opt,name=resolve,proto3,oneof"`
}
func (*HtlcResolution_Fail) isHtlcResolution_Outcome() {}
func (*HtlcResolution_Continue) isHtlcResolution_Outcome() {}
func (*HtlcResolution_Resolve) isHtlcResolution_Outcome() {}
type HtlcContinue struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Payload *string `protobuf:"bytes,1,opt,name=payload,proto3,oneof" json:"payload,omitempty"`
ForwardTo *string `protobuf:"bytes,2,opt,name=forward_to,json=forwardTo,proto3,oneof" json:"forward_to,omitempty"`
}
func (x *HtlcContinue) Reset() {
*x = HtlcContinue{}
if protoimpl.UnsafeEnabled {
mi := &file_cln_plugin_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcContinue) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcContinue) ProtoMessage() {}
func (x *HtlcContinue) ProtoReflect() protoreflect.Message {
mi := &file_cln_plugin_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcContinue.ProtoReflect.Descriptor instead.
func (*HtlcContinue) Descriptor() ([]byte, []int) {
return file_cln_plugin_proto_rawDescGZIP(), []int{4}
}
func (x *HtlcContinue) GetPayload() string {
if x != nil && x.Payload != nil {
return *x.Payload
}
return ""
}
func (x *HtlcContinue) GetForwardTo() string {
if x != nil && x.ForwardTo != nil {
return *x.ForwardTo
}
return ""
}
type HtlcFail struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Failure:
// *HtlcFail_FailureMessage
// *HtlcFail_FailureOnion
Failure isHtlcFail_Failure `protobuf_oneof:"failure"`
}
func (x *HtlcFail) Reset() {
*x = HtlcFail{}
if protoimpl.UnsafeEnabled {
mi := &file_cln_plugin_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcFail) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcFail) ProtoMessage() {}
func (x *HtlcFail) ProtoReflect() protoreflect.Message {
mi := &file_cln_plugin_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcFail.ProtoReflect.Descriptor instead.
func (*HtlcFail) Descriptor() ([]byte, []int) {
return file_cln_plugin_proto_rawDescGZIP(), []int{5}
}
func (m *HtlcFail) GetFailure() isHtlcFail_Failure {
if m != nil {
return m.Failure
}
return nil
}
func (x *HtlcFail) GetFailureMessage() string {
if x, ok := x.GetFailure().(*HtlcFail_FailureMessage); ok {
return x.FailureMessage
}
return ""
}
func (x *HtlcFail) GetFailureOnion() string {
if x, ok := x.GetFailure().(*HtlcFail_FailureOnion); ok {
return x.FailureOnion
}
return ""
}
type isHtlcFail_Failure interface {
isHtlcFail_Failure()
}
type HtlcFail_FailureMessage struct {
FailureMessage string `protobuf:"bytes,1,opt,name=failure_message,json=failureMessage,proto3,oneof"`
}
type HtlcFail_FailureOnion struct {
FailureOnion string `protobuf:"bytes,2,opt,name=failure_onion,json=failureOnion,proto3,oneof"`
}
func (*HtlcFail_FailureMessage) isHtlcFail_Failure() {}
func (*HtlcFail_FailureOnion) isHtlcFail_Failure() {}
type HtlcResolve struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PaymentKey string `protobuf:"bytes,1,opt,name=payment_key,json=paymentKey,proto3" json:"payment_key,omitempty"`
}
func (x *HtlcResolve) Reset() {
*x = HtlcResolve{}
if protoimpl.UnsafeEnabled {
mi := &file_cln_plugin_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HtlcResolve) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HtlcResolve) ProtoMessage() {}
func (x *HtlcResolve) ProtoReflect() protoreflect.Message {
mi := &file_cln_plugin_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HtlcResolve.ProtoReflect.Descriptor instead.
func (*HtlcResolve) Descriptor() ([]byte, []int) {
return file_cln_plugin_proto_rawDescGZIP(), []int{6}
}
func (x *HtlcResolve) GetPaymentKey() string {
if x != nil {
return x.PaymentKey
}
return ""
}
var File_cln_plugin_proto protoreflect.FileDescriptor
var file_cln_plugin_proto_rawDesc = []byte{
0x0a, 0x10, 0x63, 0x6c, 0x6e, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x22, 0x8c, 0x01, 0x0a, 0x0c, 0x48, 0x74, 0x6c, 0x63, 0x41, 0x63, 0x63, 0x65, 0x70,
0x74, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72,
0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x05, 0x6f, 0x6e, 0x69,
0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x4f, 0x6e, 0x69, 0x6f, 0x6e,
0x52, 0x05, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x04, 0x68, 0x74, 0x6c, 0x63, 0x18,
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x04, 0x68, 0x74,
0x6c, 0x63, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x74, 0x6f,
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x54,
0x6f, 0x22, 0xe2, 0x01, 0x0a, 0x05, 0x4f, 0x6e, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70,
0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61,
0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x63,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0e, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12,
0x21, 0x0a, 0x0c, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18,
0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x4d, 0x73,
0x61, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63,
0x6c, 0x74, 0x76, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x74, 0x76, 0x56, 0x61, 0x6c,
0x75, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x63,
0x72, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x65,
0x64, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x5f,
0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x65, 0x78,
0x74, 0x4f, 0x6e, 0x69, 0x6f, 0x6e, 0x22, 0xd7, 0x01, 0x0a, 0x04, 0x48, 0x74, 0x6c, 0x63, 0x12,
0x28, 0x0a, 0x10, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x68, 0x6f, 0x72, 0x74,
0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6d, 0x6f,
0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a,
0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c,
0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x0a, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x63,
0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74,
0x69, 0x76, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x63, 0x6c, 0x74, 0x76, 0x45,
0x78, 0x70, 0x69, 0x72, 0x79, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x21, 0x0a,
0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x06, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68,
0x22, 0xb9, 0x01, 0x0a, 0x0e, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72,
0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x04, 0x66, 0x61, 0x69,
0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x46, 0x61,
0x69, 0x6c, 0x48, 0x00, 0x52, 0x04, 0x66, 0x61, 0x69, 0x6c, 0x12, 0x2b, 0x0a, 0x08, 0x63, 0x6f,
0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x48,
0x74, 0x6c, 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63,
0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x6f, 0x6c,
0x76, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x52,
0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x00, 0x52, 0x07, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76,
0x65, 0x42, 0x09, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x22, 0x6c, 0x0a, 0x0c,
0x48, 0x74, 0x6c, 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x07,
0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,
0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x66,
0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48,
0x01, 0x52, 0x09, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x54, 0x6f, 0x88, 0x01, 0x01, 0x42,
0x0a, 0x0a, 0x08, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x0d, 0x0a, 0x0b, 0x5f,
0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x74, 0x6f, 0x22, 0x67, 0x0a, 0x08, 0x48, 0x74,
0x6c, 0x63, 0x46, 0x61, 0x69, 0x6c, 0x12, 0x29, 0x0a, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72,
0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48,
0x00, 0x52, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x12, 0x25, 0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x6f, 0x6e, 0x69,
0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x66, 0x61, 0x69, 0x6c,
0x75, 0x72, 0x65, 0x4f, 0x6e, 0x69, 0x6f, 0x6e, 0x42, 0x09, 0x0a, 0x07, 0x66, 0x61, 0x69, 0x6c,
0x75, 0x72, 0x65, 0x22, 0x2e, 0x0a, 0x0b, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c,
0x76, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74,
0x4b, 0x65, 0x79, 0x32, 0x3d, 0x0a, 0x09, 0x43, 0x6c, 0x6e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,
0x12, 0x30, 0x0a, 0x0a, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x0f,
0x2e, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x1a,
0x0d, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x28, 0x01,
0x30, 0x01, 0x42, 0x28, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x62, 0x72, 0x65, 0x65, 0x7a, 0x2f, 0x6c, 0x73, 0x70, 0x64, 0x2f, 0x63, 0x6c, 0x6e, 0x5f,
0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (
file_cln_plugin_proto_rawDescOnce sync.Once
file_cln_plugin_proto_rawDescData = file_cln_plugin_proto_rawDesc
)
func file_cln_plugin_proto_rawDescGZIP() []byte {
file_cln_plugin_proto_rawDescOnce.Do(func() {
file_cln_plugin_proto_rawDescData = protoimpl.X.CompressGZIP(file_cln_plugin_proto_rawDescData)
})
return file_cln_plugin_proto_rawDescData
}
var file_cln_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_cln_plugin_proto_goTypes = []interface{}{
(*HtlcAccepted)(nil), // 0: HtlcAccepted
(*Onion)(nil), // 1: Onion
(*Htlc)(nil), // 2: Htlc
(*HtlcResolution)(nil), // 3: HtlcResolution
(*HtlcContinue)(nil), // 4: HtlcContinue
(*HtlcFail)(nil), // 5: HtlcFail
(*HtlcResolve)(nil), // 6: HtlcResolve
}
var file_cln_plugin_proto_depIdxs = []int32{
1, // 0: HtlcAccepted.onion:type_name -> Onion
2, // 1: HtlcAccepted.htlc:type_name -> Htlc
5, // 2: HtlcResolution.fail:type_name -> HtlcFail
4, // 3: HtlcResolution.continue:type_name -> HtlcContinue
6, // 4: HtlcResolution.resolve:type_name -> HtlcResolve
3, // 5: ClnPlugin.HtlcStream:input_type -> HtlcResolution
0, // 6: ClnPlugin.HtlcStream:output_type -> HtlcAccepted
6, // [6:7] is the sub-list for method output_type
5, // [5:6] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
}
func init() { file_cln_plugin_proto_init() }
func file_cln_plugin_proto_init() {
if File_cln_plugin_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_cln_plugin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcAccepted); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_cln_plugin_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Onion); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_cln_plugin_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Htlc); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_cln_plugin_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcResolution); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_cln_plugin_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcContinue); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_cln_plugin_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcFail); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_cln_plugin_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HtlcResolve); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_cln_plugin_proto_msgTypes[3].OneofWrappers = []interface{}{
(*HtlcResolution_Fail)(nil),
(*HtlcResolution_Continue)(nil),
(*HtlcResolution_Resolve)(nil),
}
file_cln_plugin_proto_msgTypes[4].OneofWrappers = []interface{}{}
file_cln_plugin_proto_msgTypes[5].OneofWrappers = []interface{}{
(*HtlcFail_FailureMessage)(nil),
(*HtlcFail_FailureOnion)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_cln_plugin_proto_rawDesc,
NumEnums: 0,
NumMessages: 7,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_cln_plugin_proto_goTypes,
DependencyIndexes: file_cln_plugin_proto_depIdxs,
MessageInfos: file_cln_plugin_proto_msgTypes,
}.Build()
File_cln_plugin_proto = out.File
file_cln_plugin_proto_rawDesc = nil
file_cln_plugin_proto_goTypes = nil
file_cln_plugin_proto_depIdxs = nil
}

View File

@@ -1,46 +1,56 @@
syntax = "proto3";
option go_package="github.com/breez/lspd/cln_plugin";
option go_package="github.com/breez/lspd/cln_plugin/proto";
service ClnPlugin {
rpc HtlcStream(stream HtlcResolution) returns (stream HtlcAccepted);
}
message HtlcAccepted {
uint64 correlationid = 1;
string correlationid = 1;
Onion onion = 2;
HtlcOffer htlc = 3;
Htlc htlc = 3;
string forward_to = 4;
}
message Onion {
bytes payload = 1;
uint64 short_channel_id = 2;
uint64 forward_amount_msat = 3;
string payload = 1;
string short_channel_id = 2;
uint64 forward_msat = 3;
uint32 outgoing_cltv_value = 4;
string shared_secret = 5;
string next_onion = 6;
}
message HtlcOffer {
uint64 amount_msat = 2;
uint32 cltv_expiry_relative = 3;
message Htlc {
string short_channel_id = 1;
uint64 id = 2;
uint64 amount_msat = 3;
uint32 cltv_expiry = 4;
bytes payment_hash = 5;
uint32 cltv_expiry_relative = 5;
string payment_hash = 6;
}
message HtlcResolution {
uint64 correlationid = 1;
string correlationid = 1;
oneof outcome {
HtlcFail fail = 2;
HtlcContinue continue = 3;
HtlcContinueWith continue_with = 4;
HtlcResolve resolve = 4;
}
}
message HtlcFail {
bytes failure_message = 1;
}
message HtlcContinue {
optional string payload = 1;
optional string forward_to = 2;
}
message HtlcContinueWith {
bytes channel_id = 1;
bytes payload = 2;
message HtlcFail {
oneof failure {
string failure_message = 1;
string failure_onion = 2;
}
}
message HtlcResolve {
string payment_key = 1;
}

View File

@@ -1,10 +1,10 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.21.8
// - protoc v3.21.12
// source: cln_plugin.proto
package cln_plugin
package proto
import (
context "context"

View File

@@ -1 +0,0 @@
LISTEN_ADDRESS=<address the lsp cln plugin listens on (ip:port)><address the lsp cln plugin listens on (ip:port)>

View File

@@ -8,64 +8,103 @@ import (
"sync"
"time"
"github.com/breez/lspd/cln_plugin/proto"
grpc "google.golang.org/grpc"
)
var receiveWaitDelay = time.Millisecond * 200
// A subscription represents a grpc client that is connected to the server.
type subscription struct {
stream ClnPlugin_HtlcStreamServer
stream proto.ClnPlugin_HtlcStreamServer
done chan struct{}
err chan error
}
// Internal htlc_accepted message meant for the sendQueue.
type htlcAcceptedMsg struct {
id string
htlc *HtlcAccepted
timeout <-chan time.Time
}
// Internal htlc result message meant for the recvQueue.
type htlcResultMsg struct {
id string
result interface{}
}
type server struct {
ClnPluginServer
proto.ClnPluginServer
listenAddress string
subscriberTimeout time.Duration
grpcServer *grpc.Server
startMtx sync.Mutex
corrMtx sync.Mutex
mtx sync.Mutex
subscription *subscription
newSubscriber chan struct{}
started chan struct{}
startError chan error
done chan struct{}
correlations map[uint64]chan *HtlcResolution
index uint64
sendQueue chan *htlcAcceptedMsg
recvQueue chan *htlcResultMsg
}
func NewServer(listenAddress string) *server {
// Creates a new grpc server
func NewServer(listenAddress string, subscriberTimeout time.Duration) *server {
// TODO: Set a sane max queue size
return &server{
listenAddress: listenAddress,
newSubscriber: make(chan struct{}, 1),
correlations: make(map[uint64]chan *HtlcResolution),
index: 0,
subscriberTimeout: subscriberTimeout,
sendQueue: make(chan *htlcAcceptedMsg, 10000),
recvQueue: make(chan *htlcResultMsg, 10000),
started: make(chan struct{}),
startError: make(chan error, 1),
}
}
// Starts the grpc server. Blocks until the servver is stopped. WaitStarted can
// be called to ensure the server is started without errors if this function
// is run as a goroutine.
func (s *server) Start() error {
s.startMtx.Lock()
s.mtx.Lock()
if s.grpcServer != nil {
s.startMtx.Unlock()
s.mtx.Unlock()
return nil
}
lis, err := net.Listen("tcp", s.listenAddress)
if err != nil {
log.Printf("ERROR Server failed to listen: %v", err)
s.startMtx.Unlock()
s.startError <- err
s.mtx.Unlock()
return err
}
s.done = make(chan struct{})
s.newSubscriber = make(chan struct{})
s.grpcServer = grpc.NewServer()
s.startMtx.Unlock()
RegisterClnPluginServer(s.grpcServer, s)
s.mtx.Unlock()
proto.RegisterClnPluginServer(s.grpcServer, s)
log.Printf("Server starting to listen on %s.", s.listenAddress)
go s.listenHtlcRequests()
go s.listenHtlcResponses()
close(s.started)
return s.grpcServer.Serve(lis)
}
// Waits until the server has started, or errored during startup.
func (s *server) WaitStarted() error {
select {
case <-s.started:
return nil
case err := <-s.startError:
return err
}
}
// Stops all work from the grpc server immediately.
func (s *server) Stop() {
s.startMtx.Lock()
defer s.startMtx.Unlock()
s.mtx.Lock()
defer s.mtx.Unlock()
log.Printf("Server Stop() called.")
if s.grpcServer == nil {
return
@@ -76,36 +115,38 @@ func (s *server) Stop() {
s.grpcServer = nil
}
func (s *server) HtlcStream(stream ClnPlugin_HtlcStreamServer) error {
// Grpc method that is called when a new client subscribes. There can only be
// one subscriber active at a time. If there is an error receiving or sending
// from or to the subscriber, the subscription is closed.
func (s *server) HtlcStream(stream proto.ClnPlugin_HtlcStreamServer) error {
log.Printf("Got HTLC stream subscription request.")
s.startMtx.Lock()
s.mtx.Lock()
if s.subscription != nil {
s.startMtx.Unlock()
s.mtx.Unlock()
return fmt.Errorf("already subscribed")
}
sb := &subscription{
stream: stream,
done: make(chan struct{}),
err: make(chan error, 1),
}
s.subscription = sb
s.newSubscriber <- struct{}{}
s.startMtx.Unlock()
// Notify listeners that a new subscriber is active. Replace the chan with
// a new one immediately in case this subscriber is dropped later.
close(s.newSubscriber)
s.newSubscriber = make(chan struct{})
s.mtx.Unlock()
defer func() {
s.startMtx.Lock()
s.subscription = nil
close(sb.done)
s.startMtx.Unlock()
s.removeSubscriptionIfUnchanged(sb, nil)
}()
go func() {
<-stream.Context().Done()
log.Printf("HtlcStream context is done. Removing subscriber: %v", stream.Context().Err())
s.startMtx.Lock()
s.subscription = nil
close(sb.done)
s.startMtx.Unlock()
s.removeSubscriptionIfUnchanged(sb, stream.Context().Err())
}()
select {
@@ -115,82 +156,179 @@ func (s *server) HtlcStream(stream ClnPlugin_HtlcStreamServer) error {
case <-sb.done:
log.Printf("HTLC stream signalled done. Return EOF.")
return io.EOF
case err := <-sb.err:
log.Printf("HTLC stream signalled error. Return %v", err)
return err
}
}
func (s *server) Send(h *HtlcAccepted) *HtlcResolution {
sb := s.subscription
if sb == nil {
log.Printf("No subscribers available. Ignoring HtlcAccepted %+v", h)
return s.defaultResolution()
}
c := make(chan *HtlcResolution)
s.corrMtx.Lock()
s.index++
index := s.index
s.correlations[index] = c
s.corrMtx.Unlock()
h.Correlationid = index
defer func() {
s.corrMtx.Lock()
delete(s.correlations, index)
s.corrMtx.Unlock()
close(c)
}()
log.Printf("Sending HtlcAccepted: %+v", h)
err := sb.stream.Send(h)
if err != nil {
// TODO: Close the connection? Reset the subscriber?
log.Printf("Send() errored, Correlationid: %d: %v", index, err)
return s.defaultResolution()
// Enqueues a htlc_accepted message for send to the grpc client.
func (s *server) Send(id string, h *HtlcAccepted) {
s.sendQueue <- &htlcAcceptedMsg{
id: id,
htlc: h,
timeout: time.After(s.subscriberTimeout),
}
}
// Receives the next htlc resolution message from the grpc client. Returns id
// and message. Blocks until a message is available. Returns a nil message if
// the server is done.
func (s *server) Receive() (string, interface{}) {
select {
case <-s.done:
log.Printf("Signalled done while waiting for htlc resolution, Correlationid: %d, Ignoring: %+v", index, h)
return s.defaultResolution()
case resolution := <-c:
log.Printf("Got resolution, Correlationid: %d: %+v", index, h)
return resolution
return "", nil
case msg := <-s.recvQueue:
return msg.id, msg.result
}
}
func (s *server) recv() *HtlcResolution {
// Helper function that blocks until a message from a grpc client is received
// or the server stops. Either returns a received message, or nil if the server
// has stopped.
func (s *server) recv() *proto.HtlcResolution {
for {
// make a copy of the used fields, to make sure state updates don't
// surprise us. The newSubscriber chan is swapped whenever a new
// subscriber arrives.
s.mtx.Lock()
sb := s.subscription
ns := s.newSubscriber
s.mtx.Unlock()
if sb == nil {
log.Printf("Got no subscribers for receive. Waiting for subscriber.")
select {
case <-s.done:
log.Printf("Done signalled, stopping receive.")
return s.defaultResolution()
case <-s.newSubscriber:
return nil
case <-ns:
log.Printf("New subscription available for receive, continue receive.")
continue
}
}
// There is a subscription active. Attempt to receive a message.
r, err := sb.stream.Recv()
if err == nil {
log.Printf("Received HtlcResolution %+v", r)
return r
}
// TODO: close the subscription??
log.Printf("Recv() errored, waiting %v: %v", receiveWaitDelay, err)
// Receiving the message failed, so the subscription is broken. Remove
// it if it hasn't been updated already. We'll try receiving again in
// the next iteration of the for loop.
log.Printf("Recv() errored, removing subscription: %v", err)
s.removeSubscriptionIfUnchanged(sb, err)
}
}
// Stops and removes the subscription if this is the currently active
// subscription. If the subscription was changed in the meantime, this function
// does nothing.
func (s *server) removeSubscriptionIfUnchanged(sb *subscription, err error) {
s.mtx.Lock()
// If the subscription reference hasn't changed yet in the meantime, kill it.
if s.subscription == sb {
if err == nil {
close(sb.done)
} else {
sb.err <- err
}
s.subscription = nil
}
s.mtx.Unlock()
}
// Listens to sendQueue for htlc_accepted requests from cln. The message will be
// held until a subscriber is active, or the subscriber timeout expires. The
// messages are sent to the grpc client in fifo order.
func (s *server) listenHtlcRequests() {
for {
select {
case <-s.done:
log.Printf("Done signalled, stopping receive.")
return s.defaultResolution()
case <-time.After(receiveWaitDelay):
log.Printf("listenHtlcRequests received done. Stop listening.")
return
case msg := <-s.sendQueue:
s.handleHtlcAccepted(msg)
}
}
}
// Attempts to send a htlc_accepted message to the grpc client. The message will
// be held until a subscriber is active, or the subscriber timeout expires.
func (s *server) handleHtlcAccepted(msg *htlcAcceptedMsg) {
for {
s.mtx.Lock()
sb := s.subscription
ns := s.newSubscriber
s.mtx.Unlock()
// If there is no active subscription, wait until there is a new
// subscriber, or the message times out.
if sb == nil {
select {
case <-s.done:
log.Printf("handleHtlcAccepted received server done. Stop processing.")
return
case <-ns:
log.Printf("got a new subscriber. continue handleHtlcAccepted.")
continue
case <-msg.timeout:
log.Printf(
"WARNING: htlc with id '%s' timed out after '%v' waiting "+
"for grpc subscriber: %+v",
msg.id,
s.subscriberTimeout,
msg.htlc,
)
s.recvQueue <- &htlcResultMsg{
id: msg.id,
result: s.defaultResult(),
}
return
}
}
// There is a subscriber. Attempt to send the htlc_accepted message.
err := sb.stream.Send(&proto.HtlcAccepted{
Correlationid: msg.id,
Onion: &proto.Onion{
Payload: msg.htlc.Onion.Payload,
ShortChannelId: msg.htlc.Onion.ShortChannelId,
ForwardMsat: msg.htlc.Onion.ForwardMsat,
OutgoingCltvValue: msg.htlc.Onion.OutgoingCltvValue,
SharedSecret: msg.htlc.Onion.SharedSecret,
NextOnion: msg.htlc.Onion.NextOnion,
},
Htlc: &proto.Htlc{
ShortChannelId: msg.htlc.Htlc.ShortChannelId,
Id: msg.htlc.Htlc.Id,
AmountMsat: msg.htlc.Htlc.AmountMsat,
CltvExpiry: msg.htlc.Htlc.CltvExpiry,
CltvExpiryRelative: msg.htlc.Htlc.CltvExpiryRelative,
PaymentHash: msg.htlc.Htlc.PaymentHash,
},
ForwardTo: msg.htlc.ForwardTo,
})
// If there is no error, we're done.
if err == nil {
return
}
// If we end up here, there was an error sending the message to the
// grpc client.
log.Printf("Error sending htlc_accepted message to subscriber. "+
"Removing subscription: %v", err)
s.removeSubscriptionIfUnchanged(sb, err)
}
}
// Listens to htlc responses from the grpc client and appends them to the
// receive queue. The messages from the receive queue are read in the Receive
// function.
func (s *server) listenHtlcResponses() {
for {
select {
@@ -198,23 +336,77 @@ func (s *server) listenHtlcResponses() {
log.Printf("listenHtlcResponses received done. Stopping listening.")
return
default:
response := s.recv()
s.corrMtx.Lock()
correlation, ok := s.correlations[response.Correlationid]
s.corrMtx.Unlock()
if ok {
correlation <- response
} else {
log.Printf("Got HTLC resolution that could not be correlated: %+v", response)
resp := s.recv()
s.recvQueue <- &htlcResultMsg{
id: resp.Correlationid,
result: s.mapResult(resp.Outcome),
}
}
}
}
func (s *server) defaultResolution() *HtlcResolution {
return &HtlcResolution{
Outcome: &HtlcResolution_Continue{
Continue: &HtlcContinue{},
},
// Maps a grpc result to the corresponding result for cln. The cln message
// is a raw json message, so it's easiest to use a map directly.
func (s *server) mapResult(outcome interface{}) interface{} {
// result: continue
cont, ok := outcome.(*proto.HtlcResolution_Continue)
if ok {
result := map[string]interface{}{
"result": "continue",
}
if cont.Continue.ForwardTo != nil {
result["forward_to"] = *cont.Continue.ForwardTo
}
if cont.Continue.Payload != nil {
result["payload"] = *cont.Continue.Payload
}
return result
}
// result: fail
fail, ok := outcome.(*proto.HtlcResolution_Fail)
if ok {
result := map[string]interface{}{
"result": "fail",
}
fm, ok := fail.Fail.Failure.(*proto.HtlcFail_FailureMessage)
if ok {
result["failure_message"] = fm.FailureMessage
}
fo, ok := fail.Fail.Failure.(*proto.HtlcFail_FailureOnion)
if ok {
result["failure_onion"] = fo.FailureOnion
}
return result
}
// result: resolve
resolve, ok := outcome.(*proto.HtlcResolution_Resolve)
if ok {
result := map[string]interface{}{
"result": "resolve",
"payment_key": resolve.Resolve.PaymentKey,
}
return result
}
// On an unknown result we haven't implemented all possible cases from the
// grpc message. We don't understand what's going on, so we'll return
// result: continue.
log.Printf("Unexpected htlc resolution type %T: %+v", outcome, outcome)
return s.defaultResult()
}
// Returns a result: continue message.
func (s *server) defaultResult() interface{} {
return map[string]interface{}{
"result": "continue",
}
}

View File

@@ -30,7 +30,6 @@ type ClnLspNode struct {
isInitialized bool
mtx sync.Mutex
pluginBinary string
pluginFile string
pluginAddress string
}
@@ -43,7 +42,6 @@ type clnLspNodeRuntime struct {
func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode {
scriptDir := h.GetDirectory("lspd")
pluginFile := filepath.Join(scriptDir, "htlc.sh")
pluginBinary := *clnPluginExec
pluginPort, err := lntest.GetPort()
if err != nil {
@@ -52,7 +50,8 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode
pluginAddress := fmt.Sprintf("127.0.0.1:%d", pluginPort)
args := []string{
fmt.Sprintf("--plugin=%s", pluginFile),
fmt.Sprintf("--plugin=%s", pluginBinary),
fmt.Sprintf("--lsp.listen=%s", pluginAddress),
fmt.Sprintf("--fee-base=%d", lspBaseFeeMsat),
fmt.Sprintf("--fee-per-satoshi=%d", lspFeeRatePpm),
fmt.Sprintf("--cltv-delta=%d", lspCltvDelta),
@@ -79,7 +78,6 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode
logFilePath: logFilePath,
lspBase: lspbase,
pluginBinary: pluginBinary,
pluginFile: pluginFile,
pluginAddress: pluginAddress,
}
@@ -102,16 +100,6 @@ func (c *ClnLspNode) Start() {
Name: fmt.Sprintf("%s: lsp base", c.lspBase.name),
Fn: c.lspBase.Stop,
})
pluginContent := fmt.Sprintf(`#!/bin/bash
export LISTEN_ADDRESS=%s
%s`, c.pluginAddress, c.pluginBinary)
err = os.WriteFile(c.pluginFile, []byte(pluginContent), 0755)
if err != nil {
lntest.PerformCleanup(cleanups)
c.harness.T.Fatalf("failed create lsp cln plugin file: %v", err)
}
}
c.lightningNode.Start()