Skip to content

Commit

Permalink
premium: enable flexible settings with bbolt db
Browse files Browse the repository at this point in the history
Remove policy.conf management and switch to bbolt db for managing
premium settings. Provide APIs for cln and lnd to interact with
premium.

- Implement Observer for premium updates
- Poller detects updates and performs polling
  • Loading branch information
YusukeShimizu committed Dec 31, 2024
1 parent 8e569e8 commit 5711fb6
Show file tree
Hide file tree
Showing 43 changed files with 3,800 additions and 2,579 deletions.
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use flake
10 changes: 9 additions & 1 deletion clightning/clightning.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"time"

"github.com/elementsproject/peerswap/log"
"github.com/elementsproject/peerswap/premium"

"github.com/btcsuite/btcd/chaincfg"
"github.com/elementsproject/glightning/gbitcoin"
Expand Down Expand Up @@ -46,6 +47,10 @@ var methods = []peerswaprpcMethod{
&ReloadPolicyFile{},
&GetRequestedSwaps{},
&ListConfig{},
&GetPremiumRateRequest{},
&SetPremiumRateRequest{},
&GetDefaultPremiumRateRequest{},
&SetDefaultPremiumRateRequest{},
}

var devmethods = []peerswaprpcMethod{}
Expand All @@ -70,6 +75,7 @@ type ClightningClient struct {
requestedSwaps *swap.RequestedSwapsPrinter
policy PolicyReloader
pollService *poll.Service
ps *premium.Setting

gbitcoin *gbitcoin.Bitcoin
bitcoinChain *onchain.BitcoinOnChain
Expand Down Expand Up @@ -326,14 +332,16 @@ func (cl *ClightningClient) GetPreimage() (lightning.Preimage, error) {
func (cl *ClightningClient) SetupClients(liquidWallet wallet.Wallet,
swaps *swap.SwapService,
policy PolicyReloader, requestedSwaps *swap.RequestedSwapsPrinter,
bitcoin *gbitcoin.Bitcoin, bitcoinChain *onchain.BitcoinOnChain, pollService *poll.Service) {
bitcoin *gbitcoin.Bitcoin, bitcoinChain *onchain.BitcoinOnChain, pollService *poll.Service,
ps *premium.Setting) {
cl.liquidWallet = liquidWallet
cl.requestedSwaps = requestedSwaps
cl.swaps = swaps
cl.policy = policy
cl.gbitcoin = bitcoin
cl.pollService = pollService
cl.bitcoinChain = bitcoinChain
cl.ps = ps
if cl.bitcoinChain != nil {
cl.bitcoinNetwork = bitcoinChain.GetChain()
}
Expand Down
241 changes: 225 additions & 16 deletions clightning/clightning_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/elementsproject/peerswap/log"
"github.com/elementsproject/peerswap/peerswaprpc"
"github.com/elementsproject/peerswap/premium"

"github.com/elementsproject/glightning/glightning"
"github.com/elementsproject/glightning/jrpc2"
Expand Down Expand Up @@ -628,11 +629,13 @@ func (l *ListPeers) Call() (jrpc2.Result, error) {
SatsOut: ReceiverSatsOut,
SatsIn: ReceiverSatsIn,
},
PaidFee: paidFees,
BTCSwapInPremiumRatePPM: p.BTCSwapInPremiumRatePPM,
BTCSwapOutPremiumRatePPM: p.BTCSwapOutPremiumRatePPM,
LBTCSwapInPremiumRatePPM: p.LBTCSwapInPremiumRatePPM,
LBTCSwapOutPremiumRatePPM: p.LBTCSwapOutPremiumRatePPM,
PaidFee: paidFees,
PeerPremium: &Premium{
BTCSwapInPremiumRatePPM: p.BTCSwapInPremiumRatePPM,
BTCSwapOutPremiumRatePPM: p.BTCSwapOutPremiumRatePPM,
LBTCSwapInPremiumRatePPM: p.LBTCSwapInPremiumRatePPM,
LBTCSwapOutPremiumRatePPM: p.LBTCSwapOutPremiumRatePPM,
},
}
channels, err := l.cl.glightning.ListChannelsBySource(peer.Id)
if err != nil {
Expand Down Expand Up @@ -1102,6 +1105,208 @@ func (c ListConfig) LongDescription() string {
return c.Description()
}

func toPremiumAssetType(asset string) premium.AssetType {
switch strings.ToUpper(asset) {
case "BTC":
return premium.BTC
case "LBTC":
return premium.LBTC
default:
return premium.AsserUnspecified
}
}

func toPremiumOperationType(operation string) premium.OperationType {
switch strings.ToUpper(operation) {
case "SWAP_IN":
return premium.SwapIn
case "SWAP_OUT":
return premium.SwapOut
default:
return premium.OperationUnspecified
}
}

type GetPremiumRateRequest struct {
cl *ClightningClient
PeerID string `json:"peer_id"`
Asset string `json:"asset"`
OperationType string `json:"operation"`
}

func (c *GetPremiumRateRequest) Name() string {
return "peerswap-getpremiumrate"
}

func (c *GetPremiumRateRequest) New() interface{} {
return &GetPremiumRateRequest{
cl: c.cl,
}
}

func (c *GetPremiumRateRequest) Call() (jrpc2.Result, error) {
if !c.cl.isReady {
return nil, ErrWaitingForReady
}
e, err := c.cl.ps.GetRate(c.PeerID, toPremiumAssetType(c.Asset),
toPremiumOperationType(c.OperationType))
if err != nil {
return nil, fmt.Errorf("error getting premium rate: %v", err)
}
return e.PremiumRatePPM().Value(), nil
}

func (c *GetPremiumRateRequest) Get(client *ClightningClient) jrpc2.ServerMethod {
return &GetPremiumRateRequest{
cl: client,
}
}

func (c GetPremiumRateRequest) Description() string {
return "Get the premium rate for a peer"
}

func (c GetPremiumRateRequest) LongDescription() string {
return c.Description()
}

type SetPremiumRateRequest struct {
cl *ClightningClient
PeerID string `json:"peer_id"`
Asset string `json:"asset"`
OperationType string `json:"operation"`
PremiumRatePPM int64 `json:"premium_rate_ppm"`
}

func (c *SetPremiumRateRequest) Name() string {
return "peerswap-setpremiumrate"
}

func (c *SetPremiumRateRequest) New() interface{} {
return &SetPremiumRateRequest{
cl: c.cl,
}
}

func (c *SetPremiumRateRequest) Call() (jrpc2.Result, error) {
if !c.cl.isReady {
return nil, ErrWaitingForReady
}
rate, err := premium.NewPremiumRate(toPremiumAssetType(c.Asset),
toPremiumOperationType(c.OperationType), premium.NewPPM(c.PremiumRatePPM))
if err != nil {
return nil, fmt.Errorf("error creating premium rate: %v", err)
}
err = c.cl.ps.SetRate(c.PeerID, rate)
if err != nil {
return nil, fmt.Errorf("error setting premium rate: %v", err)
}
return rate, nil
}

func (c *SetPremiumRateRequest) Get(client *ClightningClient) jrpc2.ServerMethod {
return &SetPremiumRateRequest{
cl: client,
}
}

func (c SetPremiumRateRequest) Description() string {
return "Set the premium rate for a peer"
}

func (c SetPremiumRateRequest) LongDescription() string {
return c.Description()
}

type SetDefaultPremiumRateRequest struct {
cl *ClightningClient
Asset string `json:"asset"`
OperationType string `json:"operation"`
PremiumRatePPM int64 `json:"premium_rate_ppm"`
}

func (c *SetDefaultPremiumRateRequest) Name() string {
return "peerswap-setdefaultpremiumrate"
}

func (c *SetDefaultPremiumRateRequest) New() interface{} {
return &SetDefaultPremiumRateRequest{
cl: c.cl,
}
}

func (c *SetDefaultPremiumRateRequest) Call() (jrpc2.Result, error) {
if !c.cl.isReady {
return nil, ErrWaitingForReady
}
rate, err := premium.NewPremiumRate(toPremiumAssetType(c.Asset),
toPremiumOperationType(c.OperationType), premium.NewPPM(c.PremiumRatePPM))
if err != nil {
return nil, fmt.Errorf("error creating premium rate: %v", err)
}
err = c.cl.ps.SetDefaultRate(rate)
if err != nil {
return nil, fmt.Errorf("error setting default premium rate: %v", err)
}
return rate, nil
}

func (c *SetDefaultPremiumRateRequest) Get(client *ClightningClient) jrpc2.ServerMethod {
return &SetDefaultPremiumRateRequest{
cl: client,
}
}

func (c SetDefaultPremiumRateRequest) Description() string {
return "Set the default premium rate"
}

func (c SetDefaultPremiumRateRequest) LongDescription() string {
return c.Description()
}

type GetDefaultPremiumRateRequest struct {
cl *ClightningClient
Asset string `json:"asset"`
OperationType string `json:"operation"`
}

func (c *GetDefaultPremiumRateRequest) Name() string {
return "peerswap-getdefaultpremiumrate"
}

func (c *GetDefaultPremiumRateRequest) New() interface{} {
return &GetDefaultPremiumRateRequest{
cl: c.cl,
}
}

func (c *GetDefaultPremiumRateRequest) Call() (jrpc2.Result, error) {
if !c.cl.isReady {
return nil, ErrWaitingForReady
}
rate, err := c.cl.ps.GetDefaultRate(toPremiumAssetType(c.Asset),
toPremiumOperationType(c.OperationType))
if err != nil {
return nil, fmt.Errorf("error getting default premium rate: %v", err)
}
return rate.PremiumRatePPM().Value(), nil
}

func (c *GetDefaultPremiumRateRequest) Get(client *ClightningClient) jrpc2.ServerMethod {
return &GetDefaultPremiumRateRequest{
cl: client,
}
}

func (c GetDefaultPremiumRateRequest) Description() string {
return "Get the default premium rate"
}

func (c GetDefaultPremiumRateRequest) LongDescription() string {
return c.Description()
}

type PeerSwapPeerChannel struct {
ChannelId string `json:"short_channel_id"`
LocalBalance uint64 `json:"local_balance"`
Expand All @@ -1116,18 +1321,22 @@ type SwapStats struct {
SatsIn uint64 `json:"total_sats_swapped_in"`
}

type Premium struct {
BTCSwapInPremiumRatePPM int64 `json:"btc_swap_in_premium_rate_ppm"`
BTCSwapOutPremiumRatePPM int64 `json:"btc_swap_out_premium_rate_ppm"`
LBTCSwapInPremiumRatePPM int64 `json:"lbtc_swap_in_premium_rate_ppm"`
LBTCSwapOutPremiumRatePPM int64 `json:"lbtc_swap_out_premium_rate_ppm"`
}

type PeerSwapPeer struct {
NodeId string `json:"nodeid"`
SwapsAllowed bool `json:"swaps_allowed"`
SupportedAssets []string `json:"supported_assets"`
Channels []*PeerSwapPeerChannel `json:"channels"`
AsSender *SwapStats `json:"sent,omitempty"`
AsReceiver *SwapStats `json:"received,omitempty"`
PaidFee uint64 `json:"total_fee_paid,omitempty"`
BTCSwapInPremiumRatePPM int64 `json:"btc_swap_in_premium_rate_ppm"`
BTCSwapOutPremiumRatePPM int64 `json:"btc_swap_out_premium_rate_ppm"`
LBTCSwapInPremiumRatePPM int64 `json:"lbtc_swap_in_premium_rate_ppm"`
LBTCSwapOutPremiumRatePPM int64 `json:"lbtc_swap_out_premium_rate_ppm"`
NodeId string `json:"nodeid"`
SwapsAllowed bool `json:"swaps_allowed"`
SupportedAssets []string `json:"supported_assets"`
Channels []*PeerSwapPeerChannel `json:"channels"`
AsSender *SwapStats `json:"sent,omitempty"`
AsReceiver *SwapStats `json:"received,omitempty"`
PaidFee uint64 `json:"total_fee_paid,omitempty"`
PeerPremium *Premium `json:"premium,omitempty"`
}

// checkFeatures checks if a node runs the peerswap Plugin
Expand Down
10 changes: 8 additions & 2 deletions cmd/peerswap-plugin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/elementsproject/peerswap/onchain"
"github.com/elementsproject/peerswap/policy"
"github.com/elementsproject/peerswap/poll"
"github.com/elementsproject/peerswap/premium"
"github.com/elementsproject/peerswap/swap"
"github.com/elementsproject/peerswap/txwatcher"
"github.com/elementsproject/peerswap/wallet"
Expand Down Expand Up @@ -327,6 +328,10 @@ func run(ctx context.Context, lightningPlugin *clightning.ClightningClient) erro

// Manager for send message retry.
mesmgr := messages.NewManager()
ps, err := premium.NewSetting(swapDb)
if err != nil {
return err
}

swapServices := swap.NewSwapServices(swapStore,
requestedSwapStore,
Expand All @@ -342,6 +347,7 @@ func run(ctx context.Context, lightningPlugin *clightning.ClightningClient) erro
liquidOnChainService,
liquidOnChainService,
liquidTxWatcher,
ps,
)
swapService := swap.NewSwapService(swapServices)

Expand Down Expand Up @@ -370,12 +376,12 @@ func run(ctx context.Context, lightningPlugin *clightning.ClightningClient) erro
if err != nil {
return err
}
pollService := poll.NewService(1*time.Hour, 2*time.Hour, pollStore, lightningPlugin, pol, lightningPlugin, supportedAssets)
pollService := poll.NewService(1*time.Hour, 2*time.Hour, pollStore, lightningPlugin, pol, lightningPlugin, supportedAssets, ps)
pollService.Start()
defer pollService.Stop()

sp := swap.NewRequestedSwapsPrinter(requestedSwapStore)
lightningPlugin.SetupClients(liquidRpcWallet, swapService, pol, sp, bitcoinCli, bitcoinOnChainService, pollService)
lightningPlugin.SetupClients(liquidRpcWallet, swapService, pol, sp, bitcoinCli, bitcoinOnChainService, pollService, ps)

// We are ready to accept and handle requests.
// FIXME: Once we reworked the recovery service (non-blocking) we want to
Expand Down
Loading

0 comments on commit 5711fb6

Please sign in to comment.