mirror of
https://github.com/aljazceru/lspd.git
synced 2025-12-19 06:44:23 +01:00
305 lines
9.2 KiB
Go
305 lines
9.2 KiB
Go
package lsps2
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
|
|
"github.com/breez/lspd/common"
|
|
"github.com/breez/lspd/lsps0"
|
|
"github.com/breez/lspd/lsps0/codes"
|
|
"github.com/breez/lspd/lsps0/status"
|
|
)
|
|
|
|
var SupportedVersion uint32 = 1
|
|
|
|
type GetVersionsRequest struct {
|
|
}
|
|
|
|
type GetVersionsResponse struct {
|
|
Versions []uint32 `json:"versions"`
|
|
}
|
|
|
|
type GetInfoRequest struct {
|
|
Version uint32 `json:"version"`
|
|
Token *string `json:"token,omitempty"`
|
|
}
|
|
|
|
type GetInfoResponse struct {
|
|
OpeningFeeParamsMenu []*OpeningFeeParams `json:"opening_fee_params_menu"`
|
|
MinPaymentSizeMsat uint64 `json:"min_payment_size_msat,string"`
|
|
MaxPaymentSizeMsat uint64 `json:"max_payment_size_msat,string"`
|
|
}
|
|
|
|
type OpeningFeeParams struct {
|
|
MinFeeMsat uint64 `json:"min_fee_msat,string"`
|
|
Proportional uint32 `json:"proportional"`
|
|
ValidUntil string `json:"valid_until"`
|
|
MinLifetime uint32 `json:"min_lifetime"`
|
|
MaxClientToSelfDelay uint32 `json:"max_client_to_self_delay"`
|
|
Promise string `json:"promise"`
|
|
}
|
|
|
|
type BuyRequest struct {
|
|
Version uint32 `json:"version"`
|
|
OpeningFeeParams OpeningFeeParams `json:"opening_fee_params"`
|
|
PaymentSizeMsat *uint64 `json:"payment_size_msat,string,omitempty"`
|
|
}
|
|
|
|
type BuyResponse struct {
|
|
JitChannelScid string `json:"jit_channel_scid"`
|
|
LspCltvExpiryDelta uint32 `json:"lsp_cltv_expiry_delta"`
|
|
ClientTrustsLsp bool `json:"client_trusts_lsp"`
|
|
}
|
|
|
|
type Lsps2Server interface {
|
|
GetVersions(ctx context.Context, request *GetVersionsRequest) (*GetVersionsResponse, error)
|
|
GetInfo(ctx context.Context, request *GetInfoRequest) (*GetInfoResponse, error)
|
|
Buy(ctx context.Context, request *BuyRequest) (*BuyResponse, error)
|
|
}
|
|
type server struct {
|
|
openingService common.OpeningService
|
|
nodesService common.NodesService
|
|
node *common.Node
|
|
store Lsps2Store
|
|
}
|
|
|
|
type OpeningMode int
|
|
|
|
const (
|
|
OpeningMode_NoMppVarInvoice OpeningMode = 1
|
|
OpeningMode_MppFixedInvoice OpeningMode = 2
|
|
)
|
|
|
|
func NewLsps2Server(
|
|
openingService common.OpeningService,
|
|
nodesService common.NodesService,
|
|
node *common.Node,
|
|
store Lsps2Store,
|
|
) Lsps2Server {
|
|
return &server{
|
|
openingService: openingService,
|
|
nodesService: nodesService,
|
|
node: node,
|
|
store: store,
|
|
}
|
|
}
|
|
|
|
func (s *server) GetVersions(
|
|
ctx context.Context,
|
|
request *GetVersionsRequest,
|
|
) (*GetVersionsResponse, error) {
|
|
return &GetVersionsResponse{
|
|
Versions: []uint32{SupportedVersion},
|
|
}, nil
|
|
}
|
|
|
|
func (s *server) GetInfo(
|
|
ctx context.Context,
|
|
request *GetInfoRequest,
|
|
) (*GetInfoResponse, error) {
|
|
if request.Version != uint32(SupportedVersion) {
|
|
return nil, status.New(codes.Code(1), "unsupported_version").Err()
|
|
}
|
|
|
|
if request.Token == nil || *request.Token == "" {
|
|
return nil, status.New(codes.Code(2), "unrecognized_or_stale_token").Err()
|
|
}
|
|
|
|
node, err := s.nodesService.GetNode(*request.Token)
|
|
if err == common.ErrNodeNotFound {
|
|
return nil, status.New(codes.Code(2), "unrecognized_or_stale_token").Err()
|
|
}
|
|
if err != nil {
|
|
log.Printf("Lsps2Server.GetInfo: nodesService.GetNode(%s) err: %v", *request.Token, err)
|
|
return nil, status.New(codes.InternalError, "internal error").Err()
|
|
}
|
|
if node.NodeConfig.NodePubkey != s.node.NodeConfig.NodePubkey {
|
|
log.Printf(
|
|
"Lsps2Server.GetInfo: Got token '%s' on node '%s', but was meant for node '%s'",
|
|
*request.Token,
|
|
s.node.NodeConfig.NodePubkey,
|
|
node.NodeConfig.NodePubkey,
|
|
)
|
|
return nil, status.New(codes.Code(2), "unrecognized_or_stale_token").Err()
|
|
}
|
|
|
|
m, err := s.openingService.GetFeeParamsMenu(*request.Token, node.PrivateKey)
|
|
if err == common.ErrNodeNotFound {
|
|
return nil, status.New(codes.Code(2), "unrecognized_or_stale_token").Err()
|
|
}
|
|
if err != nil {
|
|
log.Printf("Lsps2Server.GetInfo: openingService.GetFeeParamsMenu(%s) err: %v", *request.Token, err)
|
|
return nil, status.New(codes.InternalError, "internal error").Err()
|
|
}
|
|
|
|
err = s.store.SavePromises(ctx, &SavePromises{
|
|
Menu: m,
|
|
Token: *request.Token,
|
|
})
|
|
if err != nil {
|
|
log.Printf("Lsps2Server.GetInfo: store.SavePromises(%+v, %s) err: %v", m, *request.Token, err)
|
|
return nil, status.New(codes.InternalError, "internal error").Err()
|
|
}
|
|
|
|
menu := []*OpeningFeeParams{}
|
|
for _, p := range m {
|
|
menu = append(menu, &OpeningFeeParams{
|
|
MinFeeMsat: p.MinFeeMsat,
|
|
Proportional: p.Proportional,
|
|
ValidUntil: p.ValidUntil,
|
|
MinLifetime: p.MinLifetime,
|
|
MaxClientToSelfDelay: p.MaxClientToSelfDelay,
|
|
Promise: p.Promise,
|
|
})
|
|
}
|
|
return &GetInfoResponse{
|
|
OpeningFeeParamsMenu: menu,
|
|
MinPaymentSizeMsat: node.NodeConfig.MinPaymentSizeMsat,
|
|
MaxPaymentSizeMsat: node.NodeConfig.MaxPaymentSizeMsat,
|
|
}, nil
|
|
}
|
|
|
|
func (s *server) Buy(
|
|
ctx context.Context,
|
|
request *BuyRequest,
|
|
) (*BuyResponse, error) {
|
|
if request.Version != uint32(SupportedVersion) {
|
|
return nil, status.New(codes.Code(1), "unsupported_version").Err()
|
|
}
|
|
|
|
params := &common.OpeningFeeParams{
|
|
MinFeeMsat: request.OpeningFeeParams.MinFeeMsat,
|
|
Proportional: request.OpeningFeeParams.Proportional,
|
|
ValidUntil: request.OpeningFeeParams.ValidUntil,
|
|
MinLifetime: request.OpeningFeeParams.MinLifetime,
|
|
MaxClientToSelfDelay: request.OpeningFeeParams.MaxClientToSelfDelay,
|
|
Promise: request.OpeningFeeParams.Promise,
|
|
}
|
|
paramsValid := s.openingService.ValidateOpeningFeeParams(
|
|
params,
|
|
s.node.PublicKey,
|
|
)
|
|
if !paramsValid {
|
|
return nil, status.New(codes.Code(2), "invalid_opening_fee_params").Err()
|
|
}
|
|
|
|
var mode OpeningMode
|
|
if request.PaymentSizeMsat == nil || *request.PaymentSizeMsat == 0 {
|
|
mode = OpeningMode_NoMppVarInvoice
|
|
} else {
|
|
mode = OpeningMode_MppFixedInvoice
|
|
if *request.PaymentSizeMsat < s.node.NodeConfig.MinPaymentSizeMsat {
|
|
return nil, status.New(codes.Code(3), "payment_size_too_small").Err()
|
|
}
|
|
if *request.PaymentSizeMsat > s.node.NodeConfig.MaxPaymentSizeMsat {
|
|
return nil, status.New(codes.Code(4), "payment_size_too_large").Err()
|
|
}
|
|
|
|
openingFee, err := computeOpeningFee(
|
|
*request.PaymentSizeMsat,
|
|
request.OpeningFeeParams.Proportional,
|
|
request.OpeningFeeParams.MinFeeMsat,
|
|
)
|
|
if err == ErrOverflow {
|
|
return nil, status.New(codes.Code(4), "payment_size_too_large").Err()
|
|
}
|
|
if err != nil {
|
|
log.Printf(
|
|
"Lsps2Server.Buy: computeOpeningFee(%d, %d, %d) err: %v",
|
|
*request.PaymentSizeMsat,
|
|
request.OpeningFeeParams.Proportional,
|
|
request.OpeningFeeParams.MinFeeMsat,
|
|
err,
|
|
)
|
|
return nil, status.New(codes.InternalError, "internal error").Err()
|
|
}
|
|
|
|
if openingFee >= *request.PaymentSizeMsat {
|
|
return nil, status.New(codes.Code(3), "payment_size_too_small").Err()
|
|
}
|
|
|
|
// NOTE: There's an option here to check for sufficient inbound liquidity as well.
|
|
}
|
|
|
|
// TODO: Restrict buying only one channel at a time?
|
|
peerId, ok := ctx.Value(lsps0.PeerContextKey).(string)
|
|
if !ok {
|
|
log.Printf("Lsps2Server.Buy: Error: No peer id found on context.")
|
|
return nil, status.New(codes.InternalError, "internal error").Err()
|
|
}
|
|
|
|
// Note, the odds to generate an already existing scid is about 10e-9 if you
|
|
// have 100_000 channels with aliases. And about 10e-11 for 10_000 channels.
|
|
// We call that negligable, and we'll assume the generated scid is unique.
|
|
// (to calculate the odds, use the birthday paradox)
|
|
scid, err := newScid()
|
|
if err != nil {
|
|
log.Printf("Lsps2Server.Buy: error generating new scid err: %v", err)
|
|
return nil, status.New(codes.InternalError, "internal error").Err()
|
|
}
|
|
|
|
// RegisterBuy errors if the scid is not unique. But the node could have
|
|
// 'actual' scids to collide with the newly generated scid. These are not in
|
|
// our database.
|
|
err = s.store.RegisterBuy(ctx, &RegisterBuy{
|
|
LspId: s.node.NodeConfig.NodePubkey,
|
|
PeerId: peerId,
|
|
Scid: *scid,
|
|
OpeningFeeParams: *params,
|
|
PaymentSizeMsat: request.PaymentSizeMsat,
|
|
Mode: mode,
|
|
})
|
|
if err != nil {
|
|
log.Printf("Lsps2Server.Buy: store.RegisterBuy err: %v", err)
|
|
return nil, status.New(codes.InternalError, "internal error").Err()
|
|
}
|
|
|
|
return &BuyResponse{
|
|
JitChannelScid: scid.ToString(),
|
|
LspCltvExpiryDelta: s.node.NodeConfig.TimeLockDelta,
|
|
ClientTrustsLsp: false,
|
|
}, nil
|
|
}
|
|
|
|
func RegisterLsps2Server(s lsps0.ServiceRegistrar, l Lsps2Server) {
|
|
s.RegisterService(
|
|
&lsps0.ServiceDesc{
|
|
ServiceName: "lsps2",
|
|
HandlerType: (*Lsps2Server)(nil),
|
|
Methods: []lsps0.MethodDesc{
|
|
{
|
|
MethodName: "lsps2.get_versions",
|
|
Handler: func(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
|
|
in := new(GetVersionsRequest)
|
|
if err := dec(in); err != nil {
|
|
return nil, err
|
|
}
|
|
return srv.(Lsps2Server).GetVersions(ctx, in)
|
|
},
|
|
},
|
|
{
|
|
MethodName: "lsps2.get_info",
|
|
Handler: func(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
|
|
in := new(GetInfoRequest)
|
|
if err := dec(in); err != nil {
|
|
return nil, err
|
|
}
|
|
return srv.(Lsps2Server).GetInfo(ctx, in)
|
|
},
|
|
},
|
|
{
|
|
MethodName: "lsps2.buy",
|
|
Handler: func(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
|
|
in := new(BuyRequest)
|
|
if err := dec(in); err != nil {
|
|
return nil, err
|
|
}
|
|
return srv.(Lsps2Server).Buy(ctx, in)
|
|
},
|
|
},
|
|
},
|
|
},
|
|
l,
|
|
)
|
|
}
|