Merge pull request #9390 from NishantBansal2003/append-channel

Enhance `lncli` listchannels command with the chan_id and short_chan_id (human readable format)
This commit is contained in:
Oliver Gugger 2025-01-15 07:19:27 -06:00 committed by GitHub
commit 572784a6a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 269 additions and 13 deletions

View File

@ -21,6 +21,7 @@ import (
"github.com/jessevdk/go-flags" "github.com/jessevdk/go-flags"
"github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/signal"
@ -50,6 +51,14 @@ var (
customDataPattern = regexp.MustCompile( customDataPattern = regexp.MustCompile(
`"custom_channel_data":\s*"([0-9a-f]+)"`, `"custom_channel_data":\s*"([0-9a-f]+)"`,
) )
chanIDPattern = regexp.MustCompile(
`"chan_id":\s*"(\d+)"`,
)
channelPointPattern = regexp.MustCompile(
`"channel_point":\s*"([0-9a-fA-F]+:[0-9]+)"`,
)
) )
// replaceCustomData replaces the custom channel data hex string with the // replaceCustomData replaces the custom channel data hex string with the
@ -86,6 +95,96 @@ func replaceCustomData(jsonBytes []byte) []byte {
return buf.Bytes() return buf.Bytes()
} }
// replaceAndAppendScid replaces the chan_id with scid and appends the human
// readable string representation of scid.
func replaceAndAppendScid(jsonBytes []byte) []byte {
// If there's nothing to replace, return the original JSON.
if !chanIDPattern.Match(jsonBytes) {
return jsonBytes
}
replacedBytes := chanIDPattern.ReplaceAllFunc(
jsonBytes, func(match []byte) []byte {
// Extract the captured scid group from the match.
chanID := chanIDPattern.FindStringSubmatch(
string(match),
)[1]
scid, err := strconv.ParseUint(chanID, 10, 64)
if err != nil {
return match
}
// Format a new JSON field for the scid (chan_id),
// including both its numeric representation and its
// string representation (scid_str).
scidStr := lnwire.NewShortChanIDFromInt(scid).
AltString()
updatedField := fmt.Sprintf(
`"scid": "%d", "scid_str": "%s"`, scid, scidStr,
)
// Replace the entire match with the new structure.
return []byte(updatedField)
},
)
var buf bytes.Buffer
err := json.Indent(&buf, replacedBytes, "", " ")
if err != nil {
// If we can't indent the JSON, it likely means the replacement
// data wasn't correct, so we return the original JSON.
return jsonBytes
}
return buf.Bytes()
}
// appendChanID appends the chan_id which is computed using the outpoint
// of the funding transaction (the txid, and output index).
func appendChanID(jsonBytes []byte) []byte {
// If there's nothing to replace, return the original JSON.
if !channelPointPattern.Match(jsonBytes) {
return jsonBytes
}
replacedBytes := channelPointPattern.ReplaceAllFunc(
jsonBytes, func(match []byte) []byte {
chanPoint := channelPointPattern.FindStringSubmatch(
string(match),
)[1]
chanOutpoint, err := wire.NewOutPointFromString(
chanPoint,
)
if err != nil {
return match
}
// Format a new JSON field computed from the
// channel_point (chan_id).
chanID := lnwire.NewChanIDFromOutPoint(*chanOutpoint)
updatedField := fmt.Sprintf(
`"channel_point": "%s", "chan_id": "%s"`,
chanPoint, chanID.String(),
)
// Replace the entire match with the new structure.
return []byte(updatedField)
},
)
var buf bytes.Buffer
err := json.Indent(&buf, replacedBytes, "", " ")
if err != nil {
// If we can't indent the JSON, it likely means the replacement
// data wasn't correct, so we return the original JSON.
return jsonBytes
}
return buf.Bytes()
}
func getContext() context.Context { func getContext() context.Context {
shutdownInterceptor, err := signal.Intercept() shutdownInterceptor, err := signal.Intercept()
if err != nil { if err != nil {
@ -120,8 +219,15 @@ func printRespJSON(resp proto.Message) {
return return
} }
// Replace custom_channel_data in the JSON.
jsonBytesReplaced := replaceCustomData(jsonBytes) jsonBytesReplaced := replaceCustomData(jsonBytes)
// Replace chan_id with scid, and append scid_str and scid fields.
jsonBytesReplaced = replaceAndAppendScid(jsonBytesReplaced)
// Append the chan_id field to the JSON.
jsonBytesReplaced = appendChanID(jsonBytesReplaced)
fmt.Printf("%s\n", jsonBytesReplaced) fmt.Printf("%s\n", jsonBytesReplaced)
} }

View File

@ -127,10 +127,9 @@ func TestReplaceCustomData(t *testing.T) {
t.Parallel() t.Parallel()
testCases := []struct { testCases := []struct {
name string name string
data string data string
replaceData string expected string
expected string
}{ }{
{ {
name: "no replacement necessary", name: "no replacement necessary",
@ -139,10 +138,10 @@ func TestReplaceCustomData(t *testing.T) {
}, },
{ {
name: "valid json with replacement", name: "valid json with replacement",
data: "{\"foo\":\"bar\",\"custom_channel_data\":\"" + data: `{"foo":"bar","custom_channel_data":"` +
hex.EncodeToString([]byte( hex.EncodeToString([]byte(
"{\"bar\":\"baz\"}", `{"bar":"baz"}`,
)) + "\"}", )) + `"}`,
expected: `{ expected: `{
"foo": "bar", "foo": "bar",
"custom_channel_data": { "custom_channel_data": {
@ -152,10 +151,10 @@ func TestReplaceCustomData(t *testing.T) {
}, },
{ {
name: "valid json with replacement and space", name: "valid json with replacement and space",
data: "{\"foo\":\"bar\",\"custom_channel_data\": \"" + data: `{"foo":"bar","custom_channel_data": "` +
hex.EncodeToString([]byte( hex.EncodeToString([]byte(
"{\"bar\":\"baz\"}", `{"bar":"baz"}`,
)) + "\"}", )) + `"}`,
expected: `{ expected: `{
"foo": "bar", "foo": "bar",
"custom_channel_data": { "custom_channel_data": {
@ -178,9 +177,11 @@ func TestReplaceCustomData(t *testing.T) {
"\"custom_channel_data\":\"a\"", "\"custom_channel_data\":\"a\"",
}, },
{ {
name: "valid json, invalid hex, just formatted", name: "valid json, invalid hex, just formatted",
data: "{\"custom_channel_data\":\"f\"}", data: `{"custom_channel_data":"f"}`,
expected: "{\n \"custom_channel_data\": \"f\"\n}", expected: `{
"custom_channel_data": "f"
}`,
}, },
} }
@ -191,3 +192,139 @@ func TestReplaceCustomData(t *testing.T) {
}) })
} }
} }
// TestReplaceAndAppendScid tests whether chan_id is replaced with scid and
// scid_str in the JSON console output.
func TestReplaceAndAppendScid(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
data string
expected string
}{
{
name: "no replacement necessary",
data: "foo",
expected: "foo",
},
{
name: "valid json with replacement",
data: `{"foo":"bar","chan_id":"829031767408640"}`,
expected: `{
"foo": "bar",
"scid": "829031767408640",
"scid_str": "754x1x0"
}`,
},
{
name: "valid json with replacement and space",
data: `{"foo":"bar","chan_id": "829031767408640"}`,
expected: `{
"foo": "bar",
"scid": "829031767408640",
"scid_str": "754x1x0"
}`,
},
{
name: "doesn't match pattern, returned identical",
data: "this ain't even json, and no chan_id " +
"either",
expected: "this ain't even json, and no chan_id " +
"either",
},
{
name: "invalid json",
data: "this ain't json, " +
"\"chan_id\":\"18446744073709551616\"",
expected: "this ain't json, " +
"\"chan_id\":\"18446744073709551616\"",
},
{
name: "valid json, invalid uint, just formatted",
data: `{"chan_id":"18446744073709551616"}`,
expected: `{
"chan_id": "18446744073709551616"
}`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := replaceAndAppendScid([]byte(tc.data))
require.Equal(t, tc.expected, string(result))
})
}
}
// TestAppendChanID tests whether chan_id (BOLT02) is appended
// to the JSON console output.
func TestAppendChanID(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
data string
expected string
}{
{
name: "no amendment necessary",
data: "foo",
expected: "foo",
},
{
name: "valid json with amendment",
data: `{"foo":"bar","channel_point":"6ab312e3b744e` +
`1b80a33a6541697df88766515c31c08e839bf11dc` +
`9fcc036a19:0"}`,
expected: `{
"foo": "bar",
"channel_point": "6ab312e3b744e1b80a33a6541697df88766515c31c` +
`08e839bf11dc9fcc036a19:0",
"chan_id": "196a03cc9fdc11bf39e8081cc315657688df971654a` +
`6330ab8e144b7e312b36a"
}`,
},
{
name: "valid json with amendment and space",
data: `{"foo":"bar","channel_point": "6ab312e3b744e` +
`1b80a33a6541697df88766515c31c08e839bf11dc` +
`9fcc036a19:0"}`,
expected: `{
"foo": "bar",
"channel_point": "6ab312e3b744e1b80a33a6541697df88766515c31c` +
`08e839bf11dc9fcc036a19:0",
"chan_id": "196a03cc9fdc11bf39e8081cc315657688df971654a` +
`6330ab8e144b7e312b36a"
}`,
},
{
name: "doesn't match pattern, returned identical",
data: "this ain't even json, and no channel_point " +
"either",
expected: "this ain't even json, and no channel_point" +
" either",
},
{
name: "invalid json",
data: "this ain't json, " +
"\"channel_point\":\"f:0\"",
expected: "this ain't json, " +
"\"channel_point\":\"f:0\"",
},
{
name: "valid json with invalid outpoint, formatted",
data: `{"channel_point":"f:0"}`,
expected: `{
"channel_point": "f:0"
}`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := appendChanID([]byte(tc.data))
require.Equal(t, tc.expected, string(result))
})
}
}

View File

@ -111,6 +111,12 @@
now update the channel policy if the edge was not found in the graph now update the channel policy if the edge was not found in the graph
database if the `create_missing_edge` flag is set. database if the `create_missing_edge` flag is set.
* [Enhance](https://github.com/lightningnetwork/lnd/pull/9390) the
`lncli listchannels` output by adding the human readable short
channel id and the channel id defined in BOLT02. Moreover change
the misnomer of `chan_id` which was describing the short channel
id to `scid` to represent what it really is.
# Improvements # Improvements
## Functional Updates ## Functional Updates
@ -282,6 +288,7 @@ The underlying functionality between those two options remain the same.
* hieblmi * hieblmi
* Jesse de Wit * Jesse de Wit
* Keagan McClelland * Keagan McClelland
* Nishant Bansal
* Oliver Gugger * Oliver Gugger
* Pins * Pins
* Viktor Tigerström * Viktor Tigerström

View File

@ -56,6 +56,12 @@ func (c ShortChannelID) String() string {
return fmt.Sprintf("%d:%d:%d", c.BlockHeight, c.TxIndex, c.TxPosition) return fmt.Sprintf("%d:%d:%d", c.BlockHeight, c.TxIndex, c.TxPosition)
} }
// AltString generates a human-readable representation of the channel ID
// with 'x' as a separator.
func (c ShortChannelID) AltString() string {
return fmt.Sprintf("%dx%dx%d", c.BlockHeight, c.TxIndex, c.TxPosition)
}
// Record returns a TLV record that can be used to encode/decode a // Record returns a TLV record that can be used to encode/decode a
// ShortChannelID to/from a TLV stream. // ShortChannelID to/from a TLV stream.
func (c *ShortChannelID) Record() tlv.Record { func (c *ShortChannelID) Record() tlv.Record {