Skip to content

Commit

Permalink
server: Add ignore policy
Browse files Browse the repository at this point in the history
The ignore policy config allows the operator to set channels and/or
nodes that should not be managed by LPD. The goal for this setting is to
allow manual management of select channels/nodes.
  • Loading branch information
matheusd committed Sep 16, 2024
1 parent 4fb2e30 commit 95fa443
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 4 deletions.
21 changes: 19 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ type closeChanPolicyCfg struct {
MinWalletBalance float64 `long:"minwalletbalance" description:"The minimum wallet balance below which channels will start to be closed"`
}

type ignorePolicyCfg struct {
Channels []string `long:"channels" description:"List of channels to ignore for management purposes"`
Nodes []string `long:"nodes" description:"List of nodes to ignore for management purposes"`
}

type config struct {
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`

Expand Down Expand Up @@ -148,8 +153,9 @@ type config struct {
LNNodeAddrs []string `long:"lnnodeaddr" description:"Public address of the underlying LN node in the P2P network"`

// Policy Config.
OpenPolicy openChanPolicyCfg `group:"Open Channel Policy" namespace:"openpolicy"`
ClosePolicy closeChanPolicyCfg `group:"Close Channel Policy" namespace:"closepolicy"`
OpenPolicy openChanPolicyCfg `group:"Open Channel Policy" namespace:"openpolicy"`
ClosePolicy closeChanPolicyCfg `group:"Close Channel Policy" namespace:"closepolicy"`
IgnorePolicy ignorePolicyCfg `group:"Ignore Policy" namespace:"ignorepolicy"`

// The rest of the members of this struct are filled by loadConfig().

Expand Down Expand Up @@ -216,6 +222,15 @@ func (c *config) serverConfig() (*server.Config, error) {
createKey = []byte(c.OpenPolicy.Key)
}

ignoreNodes := make(map[string]struct{}, len(c.IgnorePolicy.Nodes))
for _, s := range c.IgnorePolicy.Nodes {
ignoreNodes[s] = struct{}{}
}
ignoreChannels := make(map[string]struct{}, len(c.IgnorePolicy.Channels))
for _, s := range c.IgnorePolicy.Channels {
ignoreChannels[s] = struct{}{}
}

return &server.Config{
ChainParams: c.activeNet.chainParams(),
LNRPCHost: c.LNRPCHost,
Expand All @@ -235,6 +250,8 @@ func (c *config) serverConfig() (*server.Config, error) {
CreateKey: createKey,
CloseCheckInterval: c.ClosePolicy.CheckInterval,
MinChanLifetime: c.ClosePolicy.MinChanLifetime,
IgnoreNodes: ignoreNodes,
IgnoreChannels: ignoreChannels,
}, nil
}

Expand Down
18 changes: 18 additions & 0 deletions dcrlnpd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,21 @@

; Minimum wallet balance, below which channels will begin to be closed.
; closepolicy.minwalletbalance = 1.0


[Ignore Policy]

; List of channel points to ignore for channel management purposes. Even if
; these are outbound, they will NOT be closed if they don't conform to the
; policy.
;
; ignorepolicy.channels = abcd...:1
; ignorepolicy.channels = ef01...:3

; List of nodes to ignore for channel management purposes. All channels that
; were opened to one of these will be ignored. Note that this also makes LPD
; reject requests to open channels to these, because the goal of this setting
; is to mark these nodes as manually managed.
;
; ignorepolicy.nodes = abcd...
; ignorepolicy.nodes = ef01...
38 changes: 36 additions & 2 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var (
errPeerUnconnected = fmt.Errorf("%w: peer not connected to LP node", errPolicy)
errNotEnoughUtxos = fmt.Errorf("%w: not enough utxos to create channel", errPolicy)
errTooManyPendingChans = fmt.Errorf("%w: node already has too many pending channels", errPolicy)
errIgnoredNode = fmt.Errorf("%w: node is in list of ignored nodes", errPolicy)
)

// Config holds the server config.
Expand Down Expand Up @@ -58,6 +59,8 @@ type Config struct {
MinChanLifetime time.Duration
CreateKey []byte
InvoiceExpiration time.Duration
IgnoreChannels map[string]struct{}
IgnoreNodes map[string]struct{}

// ChanInvoiceFeeRate is the fee rate to charge for creating a channel.
// in atoms/channel-size-atoms.
Expand Down Expand Up @@ -156,6 +159,12 @@ func (s *Server) canCreateChannel(ctx context.Context, wv waitingInvoice) error
return errTooLargeChan
}

// Check this node is not on the ignored list. Ignored nodes are
// manually managed.
if _, ok := s.cfg.IgnoreNodes[wv.TargetNode.String()]; ok {
return errIgnoredNode
}

// Verify if there are already opened channels to the target node. Note
// that this does NOT differentiate between LP-initiated vs remote node
// initiated channels.
Expand Down Expand Up @@ -541,6 +550,31 @@ type ManagementInfo struct {
Reclaimed dcrutil.Amount
}

// isChannelIgnored returns true if the channel should be ignored for
// management purposes (c is not outbound or one of the channels configured to
// be ignored).
func (s *Server) isChannelIgnored(c *lnrpc.Channel) bool {
if !c.Initiator {
s.log.Debugf("Ignoring management of %s due to not outbound",
c.ChannelPoint)
return true
}

if _, ok := s.cfg.IgnoreChannels[c.ChannelPoint]; ok {
s.log.Debugf("Ignoring management of %s due to ignored channel cfg",
c.ChannelPoint)
return true
}

if _, ok := s.cfg.IgnoreNodes[c.RemotePubkey]; ok {
s.log.Debugf("Ignoring management of %s due to ignored node cfg",
c.ChannelPoint)
return true
}

return false
}

// FetchManagedChannels returns the list of channels managed by the server.
func (s *Server) FetchManagedChannels(ctx context.Context) (res ManagementInfo, err error) {
// Fetch the current wallet balance and see if we actually need to
Expand Down Expand Up @@ -584,8 +618,8 @@ func (s *Server) FetchManagedChannels(ctx context.Context) (res ManagementInfo,
cid := lnwire.NewShortChanIDFromInt(c.ChanId)
cp := c.ChannelPoint

// Ignore channels where we are not the initiator.
if !c.Initiator {
// Skip if this channel is ignored for management.
if s.isChannelIgnored(c) {
res.UnmanagedChannels = append(res.UnmanagedChannels,
UnmanagedChannel{
ChanPoint: cp,
Expand Down
30 changes: 30 additions & 0 deletions server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ func TestManageChannels(t *testing.T) {
chanPoint0 := "0000000000000000000000000000000000000000000000000000000000000000:0"
chanPoint1 := "1111111111111111111111111111111111111111111111111111111111111111:1"
chanPoint2 := "2222222222222222222222222222222222222222222222222222222222222222:2"
chanPointIgnored := "3333333333333333333333333333333333333333333333333333333333333333:3"
ignoredRemoteId := "ignored-remote"

var nextCidTxIndex uint32
cidForLifetime := func(d time.Duration) uint64 {
blocks := uint64(d / chainParams.TargetTimePerBlock)
Expand All @@ -28,6 +31,7 @@ func TestManageChannels(t *testing.T) {
nextCidTxIndex += 1
return cid.ToUint64()
}

tests := []struct {
name string
wbal dcrutil.Amount
Expand Down Expand Up @@ -143,6 +147,30 @@ func TestManageChannels(t *testing.T) {
chanPoint0: {},
chanPoint2: {},
},
}, {
name: "ignored channel is not closed",
wbal: minWalletBal - 1,
chans: []*lnrpc.Channel{{
ChannelPoint: chanPointIgnored,
RemotePubkey: "remote",
ChanId: cidForLifetime(minLifetime + 1),
Capacity: int64(dcr),
LocalBalance: int64(dcr),
Initiator: true,
}},
wantClosed: map[string]struct{}{},
}, {
name: "channel from ignored node is not closed",
wbal: minWalletBal - 1,
chans: []*lnrpc.Channel{{
ChannelPoint: chanPoint0,
RemotePubkey: ignoredRemoteId,
ChanId: cidForLifetime(minLifetime + 1),
Capacity: int64(dcr),
LocalBalance: int64(dcr),
Initiator: true,
}},
wantClosed: map[string]struct{}{},
}}

for _, tc := range tests {
Expand Down Expand Up @@ -182,6 +210,8 @@ func TestManageChannels(t *testing.T) {
ChainParams: chainParams,
MinWalletBalance: minWalletBal,
MinChanLifetime: minLifetime,
IgnoreChannels: map[string]struct{}{chanPointIgnored: {}},
IgnoreNodes: map[string]struct{}{ignoredRemoteId: {}},
},
}

Expand Down

0 comments on commit 95fa443

Please sign in to comment.