Skip to content

Commit

Permalink
feat: show pending channels on dashboard (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
ardevd authored Feb 14, 2024
1 parent f0739e2 commit f997808
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 30 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/sonarqube.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ jobs:
- name: Analyze with SonarQube

# You can pin the exact commit or the version.
# uses: SonarSource/sonarqube-scan-action@v1.1.0
uses: SonarSource/sonarqube-scan-action@7295e71c9583053f5bf40e9d4068a0c974603ec8
# uses: SonarSource/sonarqube-scan-action@master
uses: SonarSource/sonarqube-scan-action@master
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} # Needed to get PR information
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Generate a token on SonarQube, add it to the secrets of this repo with the name SONAR_TOKEN (Settings > Secrets > Actions > add new repository secret)
Expand Down
16 changes: 13 additions & 3 deletions internal/lnd/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package lnd
import "github.com/charmbracelet/bubbles/list"

type NodeData struct {
NodeInfo Node
Channels []Channel
Payments []Payment
NodeInfo Node
Channels []Channel
PendingChannels []PendingChannel
Payments []Payment
}

func (n NodeData) GetChannelsAsListItems(onlyOffline bool) []list.Item {
Expand All @@ -19,6 +20,15 @@ func (n NodeData) GetChannelsAsListItems(onlyOffline bool) []list.Item {
return channelItems
}

func (n NodeData) GetPendingChannelsAsListItems() []list.Item {
var pendingChannelItems []list.Item
for _, pendingChannel := range n.PendingChannels {
pendingChannelItems = append(pendingChannelItems, pendingChannel)
}

return pendingChannelItems
}

func (n NodeData) GetPaymentsAsListItems() []list.Item {
var paymentItems []list.Item
for _, payment := range n.Payments {
Expand Down
51 changes: 51 additions & 0 deletions internal/lnd/pending_channel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package lnd

import (
"fmt"

"github.com/btcsuite/btcd/btcutil"
)

// ChannelType represents the type of Lightning network channel.
type PendingChannelType int

const (
// PendingOpen indicates a pending channel opening.
PendingOpen PendingChannelType = iota
// CooperativeClosure indicates a cooperative channel closure.
CooperativeClosure
// ForceClosure indicates a forceful channel closure.
ForceClosure
)

type PendingChannel struct {
Capacity btcutil.Amount
LocalBalance btcutil.Amount
RecoveredBalance btcutil.Amount
LimboBalance btcutil.Amount
BlocksUntilMaturity int32
Type PendingChannelType
Alias string
}

func (c PendingChannel) FilterValue() string {
return c.Alias
}

func (c PendingChannel) Title() string {
return c.Alias
}

func (c PendingChannel) Description() string {
switch c.Type {
case PendingOpen:
return "Opening"
case CooperativeClosure:
return "Closing"
case ForceClosure:
return fmt.Sprintf("Force closing (%d sats in limbo)",
int(c.LimboBalance.ToUnit(btcutil.AmountSatoshi)))
}

return ""
}
52 changes: 37 additions & 15 deletions internal/tui/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type dashboardComponent int
const (
channels dashboardComponent = iota
payments
pendingChannels
nodeinfo
messageTools
channelTools
Expand All @@ -33,10 +34,13 @@ func InitDashboard(service *lndclient.GrpcLndServices, nodeData lnd.NodeData) *D

func (m *DashboardModel) initData(width, height int) {

defaultList := list.New([]list.Item{}, list.NewDefaultDelegate(), width, height/2)
adjustedHeight := height + height/3
adjustedCompressedHeight := height + height/2
defaultList := list.New([]list.Item{}, list.NewDefaultDelegate(), width, adjustedHeight/2)
compressedList := list.New([]list.Item{}, list.NewDefaultDelegate(), width, adjustedCompressedHeight/5)
defaultList.SetShowHelp(true)

m.lists = []list.Model{defaultList, defaultList}
m.lists = []list.Model{defaultList, compressedList, compressedList}
m.forms = []*huh.Form{m.generatePaymentToolsForm(), m.generateChannelToolsForm(), m.generateMessageToolsForm()}

m.lists[channels].Title = "Channels"
Expand All @@ -50,6 +54,9 @@ func (m *DashboardModel) initData(width, height int) {
m.lists[payments].Title = "Latest Payments"
m.lists[payments].SetItems(m.nodeData.GetPaymentsAsListItems())

m.lists[pendingChannels].Title = "Pending Channels"
m.lists[pendingChannels].SetItems(m.nodeData.GetPendingChannelsAsListItems())

m.base = *NewBaseModel(m)
}

Expand Down Expand Up @@ -122,12 +129,30 @@ func (m DashboardModel) Init() tea.Cmd {
return nil
}

func (m DashboardModel) getCompressedListViews() string {
s := m.styles
switch m.focused {
case payments:
return lipgloss.JoinVertical(lipgloss.Center,
s.FocusedStyle.Render(m.lists[payments].View()),
s.BorderedStyle.Render(m.lists[pendingChannels].View()))
case pendingChannels:
return lipgloss.JoinVertical(lipgloss.Center,
s.BorderedStyle.Render(m.lists[payments].View()),
s.FocusedStyle.Render(m.lists[pendingChannels].View()))
default:
return lipgloss.JoinVertical(lipgloss.Center,
s.BorderedStyle.Render(m.lists[payments].View()),
s.BorderedStyle.Render(m.lists[pendingChannels].View()))
}

}

func (m DashboardModel) View() string {
s := m.styles

if m.loaded {
channelsView := m.lists[channels].View()
paymentsView := m.lists[payments].View()

var listsView string
switch m.focused {
Expand All @@ -136,21 +161,14 @@ func (m DashboardModel) View() string {
listsView = lipgloss.JoinHorizontal(
lipgloss.Center,
s.FocusedStyle.Render(channelsView),
s.BorderedStyle.Render(paymentsView),
)

case payments:
listsView = lipgloss.JoinHorizontal(
lipgloss.Center,
s.BorderedStyle.Render(channelsView),
s.FocusedStyle.Render(paymentsView),
m.getCompressedListViews(),
)

default:
listsView = lipgloss.JoinHorizontal(
lipgloss.Center,
s.BorderedStyle.Render(channelsView),
s.BorderedStyle.Render(paymentsView),
m.getCompressedListViews(),
)
}

Expand Down Expand Up @@ -264,10 +282,10 @@ func (m *DashboardModel) handleFormClick(component dashboardComponent) (tea.Mode
m.forms[0] = m.generatePaymentToolsForm()
case messageTools:
if m.forms[2].GetString("messages") == OPTION_MESSAGE_SIGN {

i = newSignMessageModel(m.lndService, &m.base)
} else {

i = newVerifyMessageModel(m.lndService, &m.base)
}
m.forms[2] = m.generateMessageToolsForm()
Expand All @@ -285,8 +303,10 @@ func (m *DashboardModel) Prev() {
m.focused = messageTools
case payments:
m.focused = channels
case paymentTools:
case pendingChannels:
m.focused = payments
case paymentTools:
m.focused = pendingChannels
case channelTools:
m.focused = paymentTools
case messageTools:
Expand All @@ -299,6 +319,8 @@ func (m *DashboardModel) Next() {
case channels:
m.focused = payments
case payments:
m.focused = pendingChannels
case pendingChannels:
m.focused = paymentTools
case paymentTools:
m.focused = channelTools
Expand Down
84 changes: 74 additions & 10 deletions internal/tui/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/ardevd/flash/internal/lnd"
tea "github.com/charmbracelet/bubbletea"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/routing/route"
)

var windowSizeMsg tea.WindowSizeMsg
Expand All @@ -16,13 +17,16 @@ var Models []tea.Model

// Message types
type DataLoaded lnd.NodeData

// Payments
type paymentSettled struct{}
type paymentExpired struct{}
type paymentCreated struct{}
type paymentError struct{}

// Channel
type updateChannelPolicy struct{}

func updateChannelPolicyMsg() tea.Msg {
return updateChannelPolicy{}
}
Expand All @@ -45,29 +49,89 @@ func GetData(service *lndclient.GrpcLndServices, ctx context.Context) lnd.NodeDa
nodeData.Payments = paymentsSlice

// Load Channels
nodeData.Channels = GetChannelListItems(service, ctx)
nodeData.Channels = getChannelListItems(service, ctx)

// Load Pending channels
nodeData.PendingChannels = getPendingChannels(service, ctx)

// Load node data
nodeData.NodeInfo = lnd.GetDataFromAPI(service, ctx)

return nodeData
}

func GetChannelListItems(service *lndclient.GrpcLndServices, ctx context.Context) []lnd.Channel {
// Get list of pending channels
func getPendingChannels(service *lndclient.GrpcLndServices, ctx context.Context) []lnd.PendingChannel {
var pendingChannels []lnd.PendingChannel
channels, err := service.Client.PendingChannels(ctx)
if err != nil {
log.Fatal(err)
}

// Force close channels
for _, fc := range channels.PendingForceClose {
remotePeerAlias := getNodeAliasFromPubKey(service, ctx, fc.PubKeyBytes)

pendingChannel := lnd.PendingChannel{
Capacity: fc.Capacity,
LocalBalance: fc.LocalBalance,
RecoveredBalance: fc.RecoveredBalance,
LimboBalance: fc.LimboBalance,
BlocksUntilMaturity: fc.BlocksUntilMaturity,
Type: lnd.ForceClosure,
Alias: remotePeerAlias,
}
pendingChannels = append(pendingChannels, pendingChannel)
}

// Cooperative closing channels
for _, fc := range channels.WaitingClose {
remotePeerAlias := getNodeAliasFromPubKey(service, ctx, fc.PubKeyBytes)

pendingChannel := lnd.PendingChannel{
Capacity: fc.Capacity,
LocalBalance: fc.LocalBalance,
Type: lnd.CooperativeClosure,
Alias: remotePeerAlias,
}
pendingChannels = append(pendingChannels, pendingChannel)
}

// Pending channel opens
for _, fc := range channels.PendingOpen {
remotePeerAlias := getNodeAliasFromPubKey(service, ctx, fc.PubKeyBytes)

pendingChannel := lnd.PendingChannel{
Capacity: fc.Capacity,
LocalBalance: fc.LocalBalance,
Type: lnd.CooperativeClosure,
Alias: remotePeerAlias,
}
pendingChannels = append(pendingChannels, pendingChannel)
}

return pendingChannels
}

func getNodeAliasFromPubKey(service *lndclient.GrpcLndServices, ctx context.Context, pubKey route.Vertex) string {
node, err := service.Client.GetNodeInfo(ctx, pubKey, true)
if err != nil {
// TODO: add logging
return ""
}

return node.Alias
}

func getChannelListItems(service *lndclient.GrpcLndServices, ctx context.Context) []lnd.Channel {
var channels []lnd.Channel
infos, err := service.Client.ListChannels(ctx, false, false)
if err != nil {
log.Fatal(err)
}

for _, chanInfo := range infos {
remotePeerAlias := ""
channelNode, err := service.Client.GetNodeInfo(ctx, chanInfo.PubKeyBytes, true)
if err != nil {
// TODO: add logging
} else {
remotePeerAlias = channelNode.Alias
}
remotePeerAlias := getNodeAliasFromPubKey(service, ctx, chanInfo.PubKeyBytes)

channels = append(channels, lnd.Channel{Info: chanInfo, Alias: remotePeerAlias})
}
Expand All @@ -86,6 +150,6 @@ const (
OPTION_PAYMENT_SEND = "send"
OPTION_MESSAGE_SIGN = "sign"
OPTION_MESSAGE_VERIFY = "verify"
OPTION_CHANNEL_OPEN = "open"
OPTION_CHANNEL_OPEN = "open"
OPTION_CONNECT_TO_PEER = "connect"
)

0 comments on commit f997808

Please sign in to comment.