mirror of
https://github.com/aljazceru/lspd.git
synced 2026-02-03 13:24:25 +01:00
use mempool client for fee estimation
This commit is contained in:
@@ -76,25 +76,41 @@ func (c *ClnClient) IsConnected(destination []byte) (bool, error) {
|
||||
|
||||
func (c *ClnClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) {
|
||||
pubkey := hex.EncodeToString(req.Destination)
|
||||
minConf := uint16(req.TargetConf)
|
||||
if req.IsZeroConf {
|
||||
minConf = 0
|
||||
}
|
||||
|
||||
minConfs := uint16(req.MinConfs)
|
||||
var minDepth *uint16
|
||||
if req.IsZeroConf {
|
||||
var d uint16 = 0
|
||||
minDepth = &d
|
||||
}
|
||||
|
||||
var rate *glightning.FeeRate
|
||||
if req.FeeSatPerVByte != nil {
|
||||
rate = &glightning.FeeRate{
|
||||
Rate: uint(*req.FeeSatPerVByte * 1000),
|
||||
Style: glightning.PerKb,
|
||||
}
|
||||
} else if req.TargetConf != nil {
|
||||
if *req.TargetConf < 3 {
|
||||
rate = &glightning.FeeRate{
|
||||
Directive: glightning.Urgent,
|
||||
}
|
||||
} else if *req.TargetConf < 30 {
|
||||
rate = &glightning.FeeRate{
|
||||
Directive: glightning.Normal,
|
||||
}
|
||||
} else {
|
||||
rate = &glightning.FeeRate{
|
||||
Directive: glightning.Slow,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fundResult, err := c.client.FundChannelExt(
|
||||
pubkey,
|
||||
glightning.NewSat(int(req.CapacitySat)),
|
||||
&glightning.FeeRate{
|
||||
Directive: glightning.Slow,
|
||||
},
|
||||
rate,
|
||||
!req.IsPrivate,
|
||||
&minConf,
|
||||
&minConfs,
|
||||
glightning.NewMsat(0),
|
||||
minDepth,
|
||||
glightning.NewSat(0),
|
||||
|
||||
42
intercept.go
42
intercept.go
@@ -2,12 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/breez/lspd/chain"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
@@ -34,6 +36,7 @@ var (
|
||||
)
|
||||
|
||||
var payHashGroup singleflight.Group
|
||||
var feeEstimator chain.FeeEstimator
|
||||
|
||||
type interceptResult struct {
|
||||
action interceptAction
|
||||
@@ -236,12 +239,41 @@ func openChannel(client LightningClient, config *NodeConfig, paymentHash, destin
|
||||
if capacity == config.PublicChannelAmount {
|
||||
capacity++
|
||||
}
|
||||
|
||||
var targetConf *uint32
|
||||
confStr := "<nil>"
|
||||
var feeEstimation *float64
|
||||
feeStr := "<nil>"
|
||||
if feeEstimator != nil {
|
||||
fee, err := feeEstimator.EstimateFeeRate(
|
||||
context.Background(),
|
||||
chain.FeeStrategyMinimum,
|
||||
)
|
||||
if err == nil {
|
||||
feeEstimation = &fee.SatPerVByte
|
||||
feeStr = fmt.Sprintf("%.5f", *feeEstimation)
|
||||
} else {
|
||||
log.Printf("Error estimating chain fee, fallback to target conf: %v", err)
|
||||
targetConf = &config.TargetConf
|
||||
confStr = fmt.Sprintf("%v", *targetConf)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf(
|
||||
"Opening zero conf channel. Destination: %x, capacity: %v, fee: %s, targetConf: %s",
|
||||
destination,
|
||||
capacity,
|
||||
feeStr,
|
||||
confStr,
|
||||
)
|
||||
channelPoint, err := client.OpenChannel(&OpenChannelRequest{
|
||||
Destination: destination,
|
||||
CapacitySat: uint64(capacity),
|
||||
TargetConf: 6,
|
||||
IsPrivate: true,
|
||||
IsZeroConf: true,
|
||||
Destination: destination,
|
||||
CapacitySat: uint64(capacity),
|
||||
MinConfs: 6,
|
||||
IsPrivate: true,
|
||||
IsZeroConf: true,
|
||||
FeeSatPerVByte: feeEstimation,
|
||||
TargetConf: targetConf,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("client.OpenChannelSync(%x, %v) error: %v", destination, capacity, err)
|
||||
|
||||
@@ -110,6 +110,8 @@ func newLspd(h *lntest.TestHarness, name string, lnd *string, cln *string, envEx
|
||||
nodes,
|
||||
fmt.Sprintf("DATABASE_URL=%s", postgresBackend.ConnectionString()),
|
||||
fmt.Sprintf("LISTEN_ADDRESS=%s", grpcAddress),
|
||||
fmt.Sprintf("USE_MEMPOOL_FEE_ESTIMATION=true"),
|
||||
fmt.Sprintf("MEMPOOL_API_BASE_URL=https://mempool.space/api/v1/"),
|
||||
}
|
||||
|
||||
env = append(env, envExt...)
|
||||
|
||||
@@ -16,12 +16,14 @@ type GetChannelResult struct {
|
||||
}
|
||||
|
||||
type OpenChannelRequest struct {
|
||||
Destination []byte
|
||||
CapacitySat uint64
|
||||
MinHtlcMsat uint64
|
||||
TargetConf uint32
|
||||
IsPrivate bool
|
||||
IsZeroConf bool
|
||||
Destination []byte
|
||||
CapacitySat uint64
|
||||
MinHtlcMsat uint64
|
||||
IsPrivate bool
|
||||
IsZeroConf bool
|
||||
MinConfs uint32
|
||||
FeeSatPerVByte *float64
|
||||
TargetConf *uint32
|
||||
}
|
||||
|
||||
type LightningClient interface {
|
||||
|
||||
@@ -96,16 +96,23 @@ func (c *LndClient) IsConnected(destination []byte) (bool, error) {
|
||||
}
|
||||
|
||||
func (c *LndClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) {
|
||||
channelPoint, err := c.client.OpenChannelSync(context.Background(), &lnrpc.OpenChannelRequest{
|
||||
lnReq := &lnrpc.OpenChannelRequest{
|
||||
NodePubkey: req.Destination,
|
||||
LocalFundingAmount: int64(req.CapacitySat),
|
||||
TargetConf: int32(req.TargetConf),
|
||||
PushSat: 0,
|
||||
Private: req.IsPrivate,
|
||||
CommitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
ZeroConf: req.IsZeroConf,
|
||||
})
|
||||
MinConfs: int32(req.MinConfs),
|
||||
}
|
||||
|
||||
if req.FeeSatPerVByte != nil {
|
||||
lnReq.SatPerVbyte = uint64(*req.FeeSatPerVByte)
|
||||
} else if req.TargetConf != nil {
|
||||
lnReq.TargetConf = int32(*req.TargetConf)
|
||||
}
|
||||
|
||||
channelPoint, err := c.client.OpenChannelSync(context.Background(), lnReq)
|
||||
if err != nil {
|
||||
log.Printf("LND: client.OpenChannelSync(%x, %v) error: %v", req.Destination, req.CapacitySat, err)
|
||||
return nil, fmt.Errorf("LND: OpenChannel() error: %w", err)
|
||||
|
||||
12
main.go
12
main.go
@@ -9,6 +9,7 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/breez/lspd/mempool"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
)
|
||||
|
||||
@@ -33,6 +34,17 @@ func main() {
|
||||
log.Fatalf("need at least one node configured in NODES.")
|
||||
}
|
||||
|
||||
useMempool := os.Getenv("USE_MEMPOOL_FEE_ESTIMATION") == "true"
|
||||
if useMempool {
|
||||
mempoolUrl := os.Getenv("MEMPOOL_API_BASE_URL")
|
||||
feeEstimator, err = mempool.NewMempoolClient(mempoolUrl)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initialize mempool client: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("using mempool api for fee estimation: %v", mempoolUrl)
|
||||
}
|
||||
|
||||
var interceptors []HtlcInterceptor
|
||||
for _, node := range nodes {
|
||||
var interceptor HtlcInterceptor
|
||||
|
||||
@@ -17,4 +17,6 @@ CHANNELMISMATCH_NOTIFICATION_TO='["Name1 <user1@domain.com>"]'
|
||||
CHANNELMISMATCH_NOTIFICATION_CC='["Name2 <user2@domain.com>","Name3 <user3@domain.com>"]'
|
||||
CHANNELMISMATCH_NOTIFICATION_FROM="Name4 <user4@domain.com>"
|
||||
|
||||
USE_MEMPOOL_FEE_ESTIMATION=true
|
||||
MEMPOOL_API_BASE_URL=https://mempool.space/api/v1/
|
||||
NODES='[ { "name": "<LSP NAME>", "nodePubkey": "<LIGHTNING NODE PUBKEY>", "lspdPrivateKey": "<LSPD PRIVATE KEY>", "token": "<ACCESS TOKEN>", "host": "<HOSTNAME:PORT for lightning clients>", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "lnd": { "address": "<HOSTNAME:PORT>", "cert": "<LND_CERT base64>", "macaroon": "<LND_MACAROON hex>" } }, { "name": "<LSP NAME>", "nodePubkey": "<LIGHTNING NODE PUBKEY>", "lspdPrivateKey": "<LSPD PRIVATE KEY>", "token": "<ACCESS TOKEN>", "host": "<HOSTNAME:PORT for lightning clients>", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "cln": { "pluginAddress": "<address the lsp cln plugin listens on (ip:port)>", "socketPath": "<path to the cln lightning-rpc socket file>" } } ]'
|
||||
|
||||
@@ -127,7 +127,7 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest
|
||||
outPoint, err = node.client.OpenChannel(&OpenChannelRequest{
|
||||
CapacitySat: node.nodeConfig.ChannelAmount,
|
||||
Destination: pubkey,
|
||||
TargetConf: node.nodeConfig.TargetConf,
|
||||
TargetConf: &node.nodeConfig.TargetConf,
|
||||
MinHtlcMsat: node.nodeConfig.MinHtlcMsat,
|
||||
IsPrivate: node.nodeConfig.ChannelPrivate,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user