use mempool client for fee estimation

This commit is contained in:
Jesse de Wit
2023-01-30 13:47:27 +01:00
parent 17a3dc1d94
commit bfb25ae4bb
8 changed files with 97 additions and 24 deletions

View File

@@ -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),

View File

@@ -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)

View File

@@ -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...)

View File

@@ -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 {

View File

@@ -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
View File

@@ -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

View File

@@ -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>" } } ]'

View 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,
})