From bae1bff7e2e97e46b3b7325b952d8a0aff93c833 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Sep 2023 10:57:03 +0200 Subject: [PATCH] dump+dumpchannels: add additional debug info to channels --- cmd/chantools/dumpchannels.go | 25 ++- dump/dump.go | 280 ++++++++++++++++++++++++++-------- 2 files changed, 240 insertions(+), 65 deletions(-) diff --git a/cmd/chantools/dumpchannels.go b/cmd/chantools/dumpchannels.go index 5c4db4d..29e4d38 100644 --- a/cmd/chantools/dumpchannels.go +++ b/cmd/chantools/dumpchannels.go @@ -107,7 +107,30 @@ func dumpClosedChannelInfo(chanDb *channeldb.ChannelStateDB) error { return err } - dumpChannels, err := dump.ClosedChannelDump(channels, chainParams) + historicalChannels := make([]*channeldb.OpenChannel, len(channels)) + for idx := range channels { + closedChan := channels[idx] + histChan, err := chanDb.FetchHistoricalChannel( + &closedChan.ChanPoint, + ) + switch err { + // The channel was closed in a pre-historic version of lnd. + // Ignore the error. + case channeldb.ErrNoHistoricalBucket: + case channeldb.ErrChannelNotFound: + + case nil: + historicalChannels[idx] = histChan + + // Non-nil error not due to older versions of lnd. + default: + return err + } + } + + dumpChannels, err := dump.ClosedChannelDump( + channels, historicalChannels, chainParams, + ) if err != nil { return fmt.Errorf("error converting to dump format: %w", err) } diff --git a/dump/dump.go b/dump/dump.go index e6cdaee..5907cb9 100644 --- a/dump/dump.go +++ b/dump/dump.go @@ -10,10 +10,12 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" "github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" ) @@ -21,14 +23,14 @@ const ( lndInternalDerivationPath = "m/1017'/%d'/%d'/0/%d" ) -// BackupSingle is the information we want to dump from an lnd channel backup +// BackupMulti is the information we want to dump from a lnd channel backup // multi file. See `chanbackup.Multi` for information about the fields. type BackupMulti struct { Version chanbackup.MultiBackupVersion StaticBackups []BackupSingle } -// BackupSingle is the information we want to dump from an lnd channel backup. +// BackupSingle is the information we want to dump from a lnd channel backup. // See `chanbackup.Single` for information about the fields. type BackupSingle struct { Version chanbackup.SingleBackupVersion @@ -57,6 +59,7 @@ type OpenChannel struct { FundingBroadcastHeight uint32 NumConfsRequired uint16 ChannelFlags lnwire.FundingFlag + ThawHeight uint32 IdentityPub string Capacity btcutil.Amount TotalMSatSent lnwire.MilliSatoshi @@ -66,6 +69,8 @@ type OpenChannel struct { RemoteChanCfg ChannelConfig LocalCommitment channeldb.ChannelCommitment RemoteCommitment channeldb.ChannelCommitment + LocalCommitmentDebug ChannelDebugInfo + RemoteCommitmentDebug ChannelDebugInfo RemoteCurrentRevocation string RemoteNextRevocation string FundingTxn string @@ -73,24 +78,38 @@ type OpenChannel struct { RemoteShutdownScript lnwire.DeliveryAddress } +// ChannelDebugInfo is a struct that holds additional information about an open +// or pending channel that is useful for debugging. +type ChannelDebugInfo struct { + ToLocalScript string + ToLocalAddr string + ToRemoteScript string + ToRemoteAddr string +} + // ClosedChannel is the information we want to dump from a closed channel in // lnd's channel DB. See `channeldb.ChannelCloseSummary` for information about // the fields. type ClosedChannel struct { - ChanPoint string - ShortChanID lnwire.ShortChannelID - ChainHash chainhash.Hash - ClosingTXID string - RemotePub string - Capacity btcutil.Amount - CloseHeight uint32 - SettledBalance btcutil.Amount - TimeLockedBalance btcutil.Amount - CloseType string - IsPending bool - RemoteCurrentRevocation string - RemoteNextRevocation string - LocalChanConfig ChannelConfig + ChanPoint string + ShortChanID lnwire.ShortChannelID + ChainHash chainhash.Hash + ClosingTXID string + RemotePub string + Capacity btcutil.Amount + CloseHeight uint32 + SettledBalance btcutil.Amount + TimeLockedBalance btcutil.Amount + CloseType string + IsPending bool + RemoteCurrentRevocation string + RemoteNextRevocation string + LocalChanConfig ChannelConfig + NextLocalCommitHeight uint64 + RemoteCommitTailHeight uint64 + LastRemoteCommitSecret string + LocalUnrevokedCommitPoint string + HistoricalChannel *OpenChannel } // ChannelConfig is the information we want to dump from a channel @@ -119,68 +138,194 @@ func OpenChannelDump(channels []*channeldb.OpenChannel, dumpChannels := make([]OpenChannel, len(channels)) for idx, channel := range channels { - var buf bytes.Buffer - if channel.FundingTxn != nil { - err := channel.FundingTxn.Serialize(&buf) - if err != nil { - return nil, err - } + openChan, err := openChannelDump(channel, params) + if err != nil { + return nil, fmt.Errorf("error converting to dump "+ + "format: %w", err) } - revPreimage, err := channel.RevocationProducer.AtIndex( - channel.LocalCommitment.CommitHeight, - ) + dumpChannels[idx] = *openChan + } + return dumpChannels, nil +} + +func openChannelDump(channel *channeldb.OpenChannel, + params *chaincfg.Params) (*OpenChannel, error) { + + var buf bytes.Buffer + if channel.FundingTxn != nil { + err := channel.FundingTxn.Serialize(&buf) if err != nil { return nil, err } - perCommitPoint := input.ComputeCommitmentPoint(revPreimage[:]) - - dumpChannels[idx] = OpenChannel{ - ChanType: channel.ChanType, - ChainHash: channel.ChainHash, - FundingOutpoint: channel.FundingOutpoint.String(), - ShortChannelID: channel.ShortChannelID, - IsPending: channel.IsPending, - IsInitiator: channel.IsInitiator, - ChanStatus: channel.ChanStatus(), - FundingBroadcastHeight: channel.FundingBroadcastHeight, - NumConfsRequired: channel.NumConfsRequired, - ChannelFlags: channel.ChannelFlags, - IdentityPub: PubKeyToString( - channel.IdentityPub, - ), - Capacity: channel.Capacity, - TotalMSatSent: channel.TotalMSatSent, - TotalMSatReceived: channel.TotalMSatReceived, - PerCommitPoint: PubKeyToString(perCommitPoint), - LocalChanCfg: ToChannelConfig( - params, channel.LocalChanCfg, - ), - RemoteChanCfg: ToChannelConfig( - params, channel.RemoteChanCfg, - ), - LocalCommitment: channel.LocalCommitment, - RemoteCommitment: channel.RemoteCommitment, - RemoteCurrentRevocation: PubKeyToString( - channel.RemoteCurrentRevocation, - ), - RemoteNextRevocation: PubKeyToString( - channel.RemoteNextRevocation, - ), - FundingTxn: hex.EncodeToString(buf.Bytes()), - LocalShutdownScript: channel.LocalShutdownScript, - RemoteShutdownScript: channel.RemoteShutdownScript, - } } - return dumpChannels, nil + revPreimage, err := channel.RevocationProducer.AtIndex( + channel.LocalCommitment.CommitHeight, + ) + if err != nil { + return nil, err + } + perCommitPoint := input.ComputeCommitmentPoint(revPreimage[:]) + + openChan := &OpenChannel{ + ChanType: channel.ChanType, + ChainHash: channel.ChainHash, + FundingOutpoint: channel.FundingOutpoint.String(), + ShortChannelID: channel.ShortChannelID, + IsPending: channel.IsPending, + IsInitiator: channel.IsInitiator, + ChanStatus: channel.ChanStatus(), + FundingBroadcastHeight: channel.FundingBroadcastHeight, + NumConfsRequired: channel.NumConfsRequired, + ChannelFlags: channel.ChannelFlags, + ThawHeight: channel.ThawHeight, + IdentityPub: PubKeyToString( + channel.IdentityPub, + ), + Capacity: channel.Capacity, + TotalMSatSent: channel.TotalMSatSent, + TotalMSatReceived: channel.TotalMSatReceived, + PerCommitPoint: PubKeyToString(perCommitPoint), + LocalChanCfg: ToChannelConfig( + params, channel.LocalChanCfg, + ), + RemoteChanCfg: ToChannelConfig( + params, channel.RemoteChanCfg, + ), + LocalCommitment: channel.LocalCommitment, + RemoteCommitment: channel.RemoteCommitment, + RemoteCurrentRevocation: PubKeyToString( + channel.RemoteCurrentRevocation, + ), + RemoteNextRevocation: PubKeyToString( + channel.RemoteNextRevocation, + ), + FundingTxn: hex.EncodeToString(buf.Bytes()), + LocalShutdownScript: channel.LocalShutdownScript, + RemoteShutdownScript: channel.RemoteShutdownScript, + } + + localDebug, err := CollectDebugInfo( + channel, perCommitPoint, true, channel.IsInitiator, params, + ) + if err != nil { + return nil, fmt.Errorf("error collecting local debug info: %w", + err) + } + + remoteDebug, err := CollectDebugInfo( + channel, channel.RemoteCurrentRevocation, false, + !channel.IsInitiator, params, + ) + if err != nil { + return nil, fmt.Errorf("error collecting remote debug info: %w", + err) + } + + openChan.LocalCommitmentDebug = *localDebug + openChan.RemoteCommitmentDebug = *remoteDebug + + return openChan, nil +} + +// CollectDebugInfo collects the additional debug information for the given +// channel. +func CollectDebugInfo(channel *channeldb.OpenChannel, + commitPoint *btcec.PublicKey, ourCommit, initiator bool, + params *chaincfg.Params) (*ChannelDebugInfo, error) { + + chanType := channel.ChanType + ourChanCfg := &channel.LocalChanCfg + theirChanCfg := &channel.RemoteChanCfg + leaseExpiry := channel.ThawHeight + + keyRing := lnwallet.DeriveCommitmentKeys( + commitPoint, ourCommit, chanType, ourChanCfg, theirChanCfg, + ) + + // First, we create the script for the delayed "pay-to-self" output. + // This output has 2 main redemption clauses: either we can redeem the + // output after a relative block delay, or the remote node can claim + // the funds with the revocation key if we broadcast a revoked + // commitment transaction. + toLocalScript, err := lnwallet.CommitScriptToSelf( + chanType, initiator, keyRing.ToLocalKey, keyRing.RevocationKey, + uint32(ourChanCfg.CsvDelay), leaseExpiry, + ) + if err != nil { + return nil, err + } + + // Next, we create the script paying to the remote. + toRemoteScript, _, err := lnwallet.CommitScriptToRemote( + chanType, initiator, keyRing.ToRemoteKey, leaseExpiry, + ) + if err != nil { + return nil, err + } + + toLocalPkScript, err := txscript.ParsePkScript(toLocalScript.PkScript) + if err != nil { + return nil, err + } + toLocalAddr, err := toLocalPkScript.Address(params) + if err != nil { + return nil, err + } + + toRemotePkScript, err := txscript.ParsePkScript(toRemoteScript.PkScript) + if err != nil { + return nil, err + } + toRemoteAddr, err := toRemotePkScript.Address(params) + if err != nil { + return nil, err + } + + return &ChannelDebugInfo{ + ToLocalScript: hex.EncodeToString(toLocalScript.WitnessScript), + ToLocalAddr: toLocalAddr.String(), + ToRemoteScript: hex.EncodeToString( + toRemoteScript.WitnessScript, + ), + ToRemoteAddr: toRemoteAddr.String(), + }, nil } // ClosedChannelDump converts the closed channels in the given channel DB into a // dumpable format. func ClosedChannelDump(channels []*channeldb.ChannelCloseSummary, + historicalChannels []*channeldb.OpenChannel, params *chaincfg.Params) ([]ClosedChannel, error) { dumpChannels := make([]ClosedChannel, len(channels)) for idx, channel := range channels { + var ( + nextLocalHeight, remoteTailHeight uint64 + lastRemoteSecret string + localUnrevokedCommitPoint *btcec.PublicKey + historicalChannel *OpenChannel + ) + + if channel.LastChanSyncMsg != nil { + msg := channel.LastChanSyncMsg + nextLocalHeight = msg.NextLocalCommitHeight + remoteTailHeight = msg.RemoteCommitTailHeight + lastRemoteSecret = hex.EncodeToString( + msg.LastRemoteCommitSecret[:], + ) + localUnrevokedCommitPoint = msg.LocalUnrevokedCommitPoint + } + + histChan := historicalChannels[idx] + if histChan != nil { + openChan, err := openChannelDump(histChan, params) + if err != nil { + return nil, fmt.Errorf("error converting to "+ + "dump format: %w", err) + } + historicalChannel = openChan + } + dumpChannels[idx] = ClosedChannel{ ChanPoint: channel.ChanPoint.String(), ShortChanID: channel.ShortChanID, @@ -204,6 +349,13 @@ func ClosedChannelDump(channels []*channeldb.ChannelCloseSummary, LocalChanConfig: ToChannelConfig( params, channel.LocalChanConfig, ), + NextLocalCommitHeight: nextLocalHeight, + RemoteCommitTailHeight: remoteTailHeight, + LastRemoteCommitSecret: lastRemoteSecret, + LocalUnrevokedCommitPoint: PubKeyToString( + localUnrevokedCommitPoint, + ), + HistoricalChannel: historicalChannel, } } return dumpChannels, nil