diff --git a/README.md b/README.md index 59b8a22..2e80487 100644 --- a/README.md +++ b/README.md @@ -61,45 +61,47 @@ in `~/go/bin/linux_arm`. # Parameters ``` - -f, --config= config file path - -c, --connect= connect to lnd using host:port - -t, --tlscert= path to tls.cert to connect - --macaroon-dir= path to the macaroon directory - --macaroon-filename= macaroon filename - -n, --network= bitcoin network to use - --pfrom= channels with less than this inbound liquidity percentage will be considered as source channels - --pto= channels with less than this outbound liquidity percentage will be considered as target channels - -p, --perc= use this value as both pfrom and pto from above - -a, --amount= amount to rebalance - --rel-amount-to= calculate amount as the target channel capacity fraction (for example, 0.2 means you want to achieve at most 20% target channel local balance) - --rel-amount-from= calculate amount as the source channel capacity fraction (for example, 0.2 means you want to achieve at most 20% source channel remote balance) - -r, --econ-ratio= economical ratio for fee limit calculation as a multiple of target channel fee (for example, 0.5 means you want to pay at max half the fee you - might earn for routing out of the target channel) - --econ-ratio-max-ppm= limits the max fee ppm for a rebalance when using econ ratio - -F, --fee-limit-ppm= don't consider the target channel fee and use this max fee ppm instead (can rebalance at a loss, be careful) - -l, --lost-profit also consider the outbound channel fees when looking for profitable routes so that outbound_fee+inbound_fee < route_fee - -b, --probe-steps= if the payment fails at the last hop try to probe lower amount using this many steps - --allow-rapid-rebalance if a rebalance succeeds the route will be used for further rebalances until criteria for channels is not satifsied - --min-amount= if probing is enabled this will be the minimum amount to try - -i, --exclude-channel-in= don't use this channel as incoming (can be specified multiple times) - -o, --exclude-channel-out= don't use this channel as outgoing (can be specified multiple times) - -e, --exclude-channel= (DEPRECATED) don't use this channel at all (can be specified multiple times) - -d, --exclude-node= (DEPRECATED) don't use this node for routing (can be specified multiple times) - --exclude= don't use this node or your channel for routing (can be specified multiple times) - --to= try only this channel or node as target (should satisfy other constraints too; can be specified multiple times) - --from= try only this channel or node as source (should satisfy other constraints too; can be specified multiple times) - --fail-tolerance= if a channel failed before during this rebalance but chosen again by lnd, and the forward amount differs by less than this ppm, exclude the channel - --allow-unbalance-from let the source channel go below 50% local liquidity, use if you want to drain a channel; you should also set --pfrom to >50 - --allow-unbalance-to let the target channel go above 50% local liquidity, use if you want to refill a channel; you should also set --pto to >50 - -s, --stat= save successful rebalance information to the specified CSV file - --node-cache-filename= save and load other nodes information to this file, improves cold start performance - --node-cache-lifetime= nodes with last update older than this time (in minutes) will be removed from cache after loading it (default: 1440) - --node-cache-info show red and cyan 'x' characters in routes to indicate node cache misses and hits respectively - --timeout-rebalance= max rebalance session time in minutes - --timeout-attempt= max attempt time in minutes - --timeout-info= max general info query time (local channels, node id etc.) in seconds - --timeout-route= max channel selection and route query time in seconds - -v, --version show program version and exit + -f, --config= config file path + -c, --connect= connect to lnd using host:port + -t, --tlscert= path to tls.cert to connect + --macaroon-dir= path to the macaroon directory + --macaroon-filename= macaroon filename + -n, --network= bitcoin network to use + --pfrom= channels with less than this inbound liquidity percentage will be considered as source channels + --pto= channels with less than this outbound liquidity percentage will be considered as target channels + -p, --perc= use this value as both pfrom and pto from above + -a, --amount= amount to rebalance + --rel-amount-to= calculate amount as the target channel capacity fraction (for example, 0.2 means you want to achieve at most 20% target channel local balance) + --rel-amount-from= calculate amount as the source channel capacity fraction (for example, 0.2 means you want to achieve at most 20% source channel remote balance) + -r, --econ-ratio= economical ratio for fee limit calculation as a multiple of target channel fee (for example, 0.5 means you want to pay at max half the fee you + might earn for routing out of the target channel) + --econ-ratio-max-ppm= limits the max fee ppm for a rebalance when using econ ratio + -F, --fee-limit-ppm= don't consider the target channel fee and use this max fee ppm instead (can rebalance at a loss, be careful) + -l, --lost-profit also consider the outbound channel fees when looking for profitable routes so that outbound_fee+inbound_fee < route_fee + -b, --probe-steps= if the payment fails at the last hop try to probe lower amount using this many steps + --allow-rapid-rebalance if a rebalance succeeds the route will be used for further rebalances until criteria for channels is not satifsied + --min-amount= if probing is enabled this will be the minimum amount to try + -i, --exclude-channel-in= (DEPRECATED) don't use this channel as incoming (can be specified multiple times) + -o, --exclude-channel-out= (DEPRECATED) don't use this channel as outgoing (can be specified multiple times) + --exclude-from= don't use this node or channel as source (can be specified multiple times) + --exclude-to= don't use this node or channel as target (can be specified multiple times) + -e, --exclude-channel= (DEPRECATED) don't use this channel at all (can be specified multiple times) + -d, --exclude-node= (DEPRECATED) don't use this node for routing (can be specified multiple times) + --exclude= don't use this node or your channel for routing (can be specified multiple times) + --to= try only this channel or node as target (should satisfy other constraints too; can be specified multiple times) + --from= try only this channel or node as source (should satisfy other constraints too; can be specified multiple times) + --fail-tolerance= a payment that differs from the prior attempt by this ppm will be cancelled + --allow-unbalance-from let the source channel go below 50% local liquidity, use if you want to drain a channel; you should also set --pfrom to >50 + --allow-unbalance-to let the target channel go above 50% local liquidity, use if you want to refill a channel; you should also set --pto to >50 + -s, --stat= save successful rebalance information to the specified CSV file + --node-cache-filename= save and load other nodes information to this file, improves cold start performance + --node-cache-lifetime= nodes with last update older than this time (in minutes) will be removed from cache after loading it + --node-cache-info show red and cyan 'x' characters in routes to indicate node cache misses and hits respectively + --timeout-rebalance= max rebalance session time in minutes + --timeout-attempt= max attempt time in minutes + --timeout-info= max general info query time (local channels, node id etc.) in seconds + --timeout-route= max channel selection and route query time in seconds + -v, --version show program version and exit ``` Look in `config.json.sample` or `config.toml.sample` for corresponding keys, diff --git a/channels.go b/channels.go index 5a7aaf9..e9f4dc8 100644 --- a/channels.go +++ b/channels.go @@ -63,10 +63,11 @@ func parseNodeChannelIDs(ids []string) (chans map[uint64]struct{}, nodes [][]byt func (r *regolancer) getChannelCandidates(fromPerc, toPerc, amount int64) error { for _, c := range r.channels { + if _, ok := r.excludeBoth[c.ChanId]; ok { continue } - if _, ok := r.excludeIn[c.ChanId]; !ok { + if _, ok := r.excludeTo[c.ChanId]; !ok { if _, ok := r.toChannelId[c.ChanId]; ok || len(r.toChannelId) == 0 { if c.LocalBalance < c.Capacity*toPerc/100 { r.toChannels = append(r.toChannels, c) @@ -74,7 +75,7 @@ func (r *regolancer) getChannelCandidates(fromPerc, toPerc, amount int64) error } } - if _, ok := r.excludeOut[c.ChanId]; !ok { + if _, ok := r.excludeFrom[c.ChanId]; !ok { if _, ok := r.fromChannelId[c.ChanId]; ok || len(r.fromChannelId) == 0 { if c.RemoteBalance < c.Capacity*fromPerc/100 { r.fromChannels = append(r.fromChannels, c) @@ -194,3 +195,43 @@ func parseScid(chanId string) int64 { return int64(scId.ToUint64()) } + +func (r *regolancer) getChannelForPeer(ctx context.Context, node []byte) []*lnrpc.Channel { + + channels, err := r.lnClient.ListChannels(ctx, &lnrpc.ListChannelsRequest{ActiveOnly: true, PublicOnly: true, Peer: node}) + + if err != nil { + log.Fatalf("Error fetching channels when filtering for node \"%x\": %s", node, err) + } + + return channels.Channels + +} + +func (r *regolancer) filterChannels(ctx context.Context, nodeChannelIDs []string) (channels map[uint64]struct{}) { + + channels = map[uint64]struct{}{} + chans, nodes, err := parseNodeChannelIDs(nodeChannelIDs) + if err != nil { + log.Fatal("Error parsing node/channel list:", err) + } + // Needed bc of deprecated ExcludeChannelsIn feature + for id := range chans { + if _, ok := channels[id]; !ok { + channels[id] = struct{}{} + } + } + + for _, node := range nodes { + chans := r.getChannelForPeer(ctx, node) + + for _, c := range chans { + if _, ok := channels[c.ChanId]; !ok { + channels[c.ChanId] = struct{}{} + } + } + } + + return + +} diff --git a/main.go b/main.go index 6fb9feb..ef3cb41 100644 --- a/main.go +++ b/main.go @@ -38,8 +38,10 @@ type configParams struct { ProbeSteps int `short:"b" long:"probe-steps" description:"if the payment fails at the last hop try to probe lower amount using this many steps" json:"probe_steps" toml:"probe_steps"` AllowRapidRebalance bool `long:"allow-rapid-rebalance" description:"if a rebalance succeeds the route will be used for further rebalances until criteria for channels is not satifsied" json:"allow_rapid_rebalance" toml:"allow_rapid_rebalance"` MinAmount int64 `long:"min-amount" description:"if probing is enabled this will be the minimum amount to try" json:"min_amount" toml:"min_amount"` - ExcludeChannelsIn []string `short:"i" long:"exclude-channel-in" description:"don't use this channel as incoming (can be specified multiple times)" json:"exclude_channels_in" toml:"exclude_channels_in"` - ExcludeChannelsOut []string `short:"o" long:"exclude-channel-out" description:"don't use this channel as outgoing (can be specified multiple times)" json:"exclude_channels_out" toml:"exclude_channels_out"` + ExcludeChannelsIn []string `short:"i" long:"exclude-channel-in" description:"(DEPRECATED) don't use this channel as incoming (can be specified multiple times)" json:"exclude_channels_in" toml:"exclude_channels_in"` + ExcludeChannelsOut []string `short:"o" long:"exclude-channel-out" description:"(DEPRECATED) don't use this channel as outgoing (can be specified multiple times)" json:"exclude_channels_out" toml:"exclude_channels_out"` + ExcludeFrom []string `long:"exclude-from" description:"don't use this node or channel as source (can be specified multiple times)" json:"exclude_from" toml:"exclude_from"` + ExcludeTo []string `long:"exclude-to" description:"don't use this node or channel as target (can be specified multiple times)" json:"exclude_to" toml:"exclude_to"` ExcludeChannels []string `short:"e" long:"exclude-channel" description:"(DEPRECATED) don't use this channel at all (can be specified multiple times)" json:"exclude_channels" toml:"exclude_channels"` ExcludeNodes []string `short:"d" long:"exclude-node" description:"(DEPRECATED) don't use this node for routing (can be specified multiple times)" json:"exclude_nodes" toml:"exclude_nodes"` Exclude []string `long:"exclude" description:"don't use this node or your channel for routing (can be specified multiple times)" json:"exclude" toml:"exclude"` @@ -89,8 +91,8 @@ type regolancer struct { nodeCache map[string]cachedNodeInfo chanCache map[uint64]*lnrpc.ChannelEdge failureCache map[string]failedRoute - excludeIn map[uint64]struct{} - excludeOut map[uint64]struct{} + excludeTo map[uint64]struct{} + excludeFrom map[uint64]struct{} excludeBoth map[uint64]struct{} excludeNodes [][]byte statFilename string @@ -407,39 +409,43 @@ func preflightChecks(params *configParams) error { if (params.RelAmountFrom > 0 || params.RelAmountTo > 0) && params.AllowRapidRebalance { return fmt.Errorf("use either relative amounts or rapid rebalance but not both") - } if params.NodeCacheLifetime == 0 { params.NodeCacheLifetime = 1440 } - if len(params.ExcludeChannels) > 0 || len(params.ExcludeNodes) > 0 { log.Print(infoColor("--exclude-channel and exclude_channel parameter are deprecated, use --exclude or exclude parameter instead for both channels and nodes")) if len(params.Exclude) > 0 { return fmt.Errorf("can't use --exclude and --exclude-channel/--exclude-node (or config parameters) at the same time") } } - if params.AllowUnbalanceFrom || params.AllowUnbalanceTo { log.Print(infoColor("--allow-unbalance-from/to are deprecated and enabled by default, please remove them from your config or command line parameters")) } - + if len(params.ExcludeChannelsIn) > 0 { + log.Print(infoColor("--exclude-channel-in are deprecated use --exclude-to instead, please remove them from your config or command line parameters")) + if len(params.ExcludeTo) > 0 { + return fmt.Errorf("can't use --exclude-to and --exclude-channel-in (or config parameters) at the same time") + } + } + if len(params.ExcludeChannelsOut) > 0 { + log.Print(infoColor("--exclude-channel-out are deprecated use --exclude-from instead, please remove them from your config or command line parameters")) + if len(params.ExcludeFrom) > 0 { + return fmt.Errorf("can't use --exclude-from and --exclude-channel-out (or config parameters) at the same time") + } + } if params.TimeoutAttempt == 0 { params.TimeoutAttempt = 5 } - if params.TimeoutRebalance == 0 { params.TimeoutRebalance = 360 } - if params.TimeoutInfo == 0 { params.TimeoutInfo = 30 } - if params.TimeoutRoute == 0 { params.TimeoutRoute = 30 } - return nil } @@ -506,67 +512,33 @@ func main() { if err != nil { log.Fatal("Error listing own channels: ", err) } - if len(params.From) > 0 { - chans, nodes, err := parseNodeChannelIDs(params.From) - if err != nil { - log.Fatal("Error parsing source node/channel list:", err) - } - - r.fromChannelId = chans - - for _, node := range nodes { - - channels, err := r.lnClient.ListChannels(infoCtx, &lnrpc.ListChannelsRequest{ActiveOnly: true, PublicOnly: true, Peer: node}) - - if err != nil { - log.Fatalf("Error fetching channels when filtering for source node \"%x\": %s", node, err) - } - - for _, c := range channels.Channels { - if _, ok := r.fromChannelId[c.ChanId]; !ok { - r.fromChannelId[c.ChanId] = struct{}{} - } - } - - } + if len(params.From) > 0 { + r.fromChannelId = r.filterChannels(infoCtx, params.From) if len(r.fromChannelId) == 0 { log.Fatal("No source nodes/channels selected, check if the ID is correct and node is online") } - } if len(params.To) > 0 { - chans, nodes, err := parseNodeChannelIDs(params.To) - if err != nil { - log.Fatal("Error parsing target node/channel list:", err) - } - - r.toChannelId = chans - - for _, node := range nodes { - - channels, err := r.lnClient.ListChannels(infoCtx, &lnrpc.ListChannelsRequest{ActiveOnly: true, PublicOnly: true, Peer: node}) - - if err != nil { - log.Fatalf("Error fetching channels when filtering for target node \"%x\": %s", node, err) - } - - for _, c := range channels.Channels { - if _, ok := r.toChannelId[c.ChanId]; !ok { - r.toChannelId[c.ChanId] = struct{}{} - } - } - } - + r.toChannelId = r.filterChannels(infoCtx, params.To) if len(r.toChannelId) == 0 { log.Fatal("No target nodes/channels selected, check if the ID is correct and node is online") } } - r.excludeIn = makeChanSet(convertChanStringToInt(params.ExcludeChannelsIn)) - r.excludeOut = makeChanSet(convertChanStringToInt(params.ExcludeChannelsOut)) - r.excludeBoth = makeChanSet(convertChanStringToInt(params.ExcludeChannels)) + if len(params.ExcludeFrom) > 0 { + r.excludeFrom = r.filterChannels(infoCtx, params.ExcludeFrom) + } else { + r.excludeFrom = makeChanSet(convertChanStringToInt(params.ExcludeChannelsOut)) + } + + if len(params.ExcludeTo) > 0 { + r.excludeTo = r.filterChannels(infoCtx, params.ExcludeTo) + } else { + r.excludeTo = makeChanSet(convertChanStringToInt(params.ExcludeChannelsIn)) + } + r.excludeBoth = makeChanSet(convertChanStringToInt(params.ExcludeChannels)) err = r.makeNodeList(params.ExcludeNodes) if err != nil { log.Fatal("Error parsing excluded node list: ", err)