return opening_fee_params on channel info call

This commit is contained in:
Jesse de Wit
2023-05-11 15:37:28 +02:00
parent 27f7f6b3c1
commit a16e87748b
4 changed files with 135 additions and 1 deletions

3
basetypes/time.go Normal file
View File

@@ -0,0 +1,3 @@
package basetypes
var TIME_FORMAT string = "2006-01-02T15:04:05.999Z"

View File

@@ -66,6 +66,22 @@ type NodeConfig struct {
// The channel can be closed if not used this duration in seconds.
MaxInactiveDuration uint64 `json:"maxInactiveDuration,string"`
// The validity duration of an opening params promise.
FeeValidityDuration uint64 `json:"feeValidityDuration,string"`
// Maximum number of blocks that the client is allowed to set its
// `to_self_delay` parameter.
MaxClientToSelfDelay uint64 `json:"maxClientToSelfDelay,string"`
// Multiplication factor to calculate the minimum fee for a JIT channel open.
// The resulting fee after multiplying sat/vbyte by the multiplication factor
// is denominated in millisat.
// e.g. if you expect to publish 500 bytes onchain with the given sat/vbyte
// fee rate, and take a margin of 20%, the fee multiplication factor should
// be 500 * 1.2 * 1000 = 600000. With 20 sat/vbyte, the resulting minimum fee
// would be 600000 * 20 = 12000000msat = 12000sat.
FeeMultiplicationFactor uint64 `json:"feeMultiplicationFactor,string"`
// Set this field to connect to an LND node.
Lnd *LndConfig `json:"lnd,omitempty"`

View File

@@ -117,7 +117,8 @@ func main() {
address := os.Getenv("LISTEN_ADDRESS")
certMagicDomain := os.Getenv("CERTMAGIC_DOMAIN")
s, err := NewGrpcServer(nodes, address, certMagicDomain, interceptStore)
cachedEstimator := chain.NewCachedFeeEstimator(feeEstimator)
s, err := NewGrpcServer(nodes, address, certMagicDomain, interceptStore, feeStrategy, cachedEstimator)
if err != nil {
log.Fatalf("failed to initialize grpc server: %v", err)
}

114
server.go
View File

@@ -2,15 +2,20 @@ package main
import (
"context"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"log"
"math"
"net"
"strings"
"time"
"github.com/breez/lspd/basetypes"
"github.com/breez/lspd/btceclegacy"
"github.com/breez/lspd/chain"
"github.com/breez/lspd/cln"
"github.com/breez/lspd/config"
"github.com/breez/lspd/interceptor"
@@ -27,12 +32,15 @@ import (
"google.golang.org/grpc/status"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/caddyserver/certmagic"
"github.com/lightningnetwork/lnd/lnwire"
)
var TIME_FORMAT string = "2006-01-02T15:04:05.999Z"
type server struct {
lspdrpc.ChannelOpenerServer
address string
@@ -41,6 +49,8 @@ type server struct {
s *grpc.Server
nodes map[string]*node
store interceptor.InterceptStore
feeStrategy chain.FeeStrategy
feeEstimator chain.FeeEstimator
}
type node struct {
@@ -59,6 +69,11 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo
return nil, err
}
params, err := s.createOpeningParams(ctx, node)
if err != nil {
return nil, err
}
return &lspdrpc.ChannelInformationReply{
Name: node.nodeConfig.Name,
Pubkey: node.nodeConfig.NodePubkey,
@@ -73,9 +88,104 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo
ChannelMinimumFeeMsat: int64(node.nodeConfig.ChannelMinimumFeeMsat),
LspPubkey: node.publicKey.SerializeCompressed(), // TODO: Is the publicKey different from the ecies public key?
MaxInactiveDuration: int64(node.nodeConfig.MaxInactiveDuration),
OpeningFeeParamsMenu: []*lspdrpc.OpeningFeeParams{params},
}, nil
}
func (s *server) createOpeningParams(
ctx context.Context,
node *node,
) (*lspdrpc.OpeningFeeParams, error) {
// Get a fee estimate.
estimate, err := s.feeEstimator.EstimateFeeRate(ctx, s.feeStrategy)
if err != nil {
log.Printf("Failed to get fee estimate: %v", err)
return nil, fmt.Errorf("failed to get fee estimate")
}
// Multiply the fee estiimate by the configured multiplication factor.
minFeeMsat := estimate.SatPerVByte *
float64(node.nodeConfig.FeeMultiplicationFactor)
// Make sure the fee is not lower than the minimum fee.
minFeeMsat = math.Max(minFeeMsat, float64(node.nodeConfig.ChannelMinimumFeeMsat))
validUntil := time.Now().UTC().Add(
time.Second * time.Duration(node.nodeConfig.FeeValidityDuration),
)
params := &lspdrpc.OpeningFeeParams{
MinMsat: uint64(minFeeMsat),
// Proportional is ppm, so divide by 100.
Proportional: uint32(node.nodeConfig.ChannelFeePermyriad / 100),
ValidUntil: validUntil.Format(basetypes.TIME_FORMAT),
// MaxInactiveDuration is in seconds, so divide by 600 for blocks.
MaxIdleTime: uint32(node.nodeConfig.MaxInactiveDuration / 600),
MaxClientToSelfDelay: uint32(node.nodeConfig.MaxClientToSelfDelay),
}
promise, err := createPromise(node, params)
if err != nil {
log.Printf("Failed to create promise: %v", err)
}
params.Promise = *promise
return params, nil
}
func createPromise(node *node, params *lspdrpc.OpeningFeeParams) (*string, error) {
// First hash all the values in the params in a fixed order.
items := []interface{}{
params.MinMsat,
params.Proportional,
params.ValidUntil,
params.MaxIdleTime,
params.MaxClientToSelfDelay,
}
blob, err := json.Marshal(items)
if err != nil {
return nil, err
}
hash := sha256.Sum256(blob)
// Sign the hash with the private key of the LSP id.
sig, err := ecdsa.SignCompact(node.privateKey, hash[:], true)
if err != nil {
return nil, err
}
// The promise is the hex encoded hash of the signature.
result := sha256.Sum256(sig)
promise := hex.EncodeToString(result[:])
return &promise, nil
}
func validateOpeningFeeParams(node *node, params *lspdrpc.OpeningFeeParams) bool {
if params == nil {
return false
}
promise, err := createPromise(node, params)
if err != nil {
return false
}
if *promise != params.Promise {
return false
}
t, err := time.Parse(basetypes.TIME_FORMAT, params.ValidUntil)
if err != nil {
return false
}
if time.Now().UTC().After(t) {
return false
}
return true
}
func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymentRequest) (*lspdrpc.RegisterPaymentReply, error) {
node, err := getNode(ctx)
if err != nil {
@@ -270,6 +380,8 @@ func NewGrpcServer(
address string,
certmagicDomain string,
store interceptor.InterceptStore,
feeStrategy chain.FeeStrategy,
feeEstimator chain.FeeEstimator,
) (*server, error) {
if len(configs) == 0 {
return nil, fmt.Errorf("no nodes supplied")
@@ -329,6 +441,8 @@ func NewGrpcServer(
certmagicDomain: certmagicDomain,
nodes: nodes,
store: store,
feeStrategy: feeStrategy,
feeEstimator: feeEstimator,
}, nil
}