cmd/lncli: update listchannels output fields

Added scid_str as a string representation of chan_id,
replacing chan_id with scid, and including chan_id(BOLT02)
in lncli listchannels output.

Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
This commit is contained in:
Nishant Bansal 2025-01-15 17:58:06 +05:30
parent 7f8e89f3b4
commit 103a194e5c
No known key found for this signature in database
GPG key ID: EAE292198F6C5B98
2 changed files with 256 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

@ -129,7 +129,6 @@ func TestReplaceCustomData(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
data string data string
replaceData string
expected string expected string
}{ }{
{ {
@ -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": {
@ -179,8 +178,10 @@ func TestReplaceCustomData(t *testing.T) {
}, },
{ {
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))
})
}
}