package dump import ( "bytes" "encoding/hex" "fmt" "net" "github.com/btcsuite/btcd/btcec/v2" "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" ) const ( lndInternalDerivationPath = "m/1017'/%d'/%d'/0/%d" ) // 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 a lnd channel backup. // See `chanbackup.Single` for information about the fields. type BackupSingle struct { Version chanbackup.SingleBackupVersion IsInitiator bool ChainHash string FundingOutpoint string ShortChannelID lnwire.ShortChannelID RemoteNodePub string Addresses []net.Addr Capacity btcutil.Amount LocalChanCfg ChannelConfig RemoteChanCfg ChannelConfig ShaChainRootDesc KeyDescriptor } // OpenChannel is the information we want to dump from an open channel in lnd's // channel DB. See `channeldb.OpenChannel` for information about the fields. type OpenChannel struct { ChanType channeldb.ChannelType ChainHash chainhash.Hash FundingOutpoint string ShortChannelID lnwire.ShortChannelID IsPending bool IsInitiator bool ChanStatus channeldb.ChannelStatus FundingBroadcastHeight uint32 NumConfsRequired uint16 ChannelFlags lnwire.FundingFlag ThawHeight uint32 IdentityPub string Capacity btcutil.Amount TotalMSatSent lnwire.MilliSatoshi TotalMSatReceived lnwire.MilliSatoshi PerCommitPoint string LocalChanCfg ChannelConfig RemoteChanCfg ChannelConfig LocalCommitment channeldb.ChannelCommitment RemoteCommitment channeldb.ChannelCommitment LocalCommitmentDebug ChannelDebugInfo RemoteCommitmentDebug ChannelDebugInfo RemoteCurrentRevocation string RemoteNextRevocation string FundingTxn string LocalShutdownScript lnwire.DeliveryAddress 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 NextLocalCommitHeight uint64 RemoteCommitTailHeight uint64 LastRemoteCommitSecret string LocalUnrevokedCommitPoint string HistoricalChannel *OpenChannel } // ChannelConfig is the information we want to dump from a channel // configuration. See `channeldb.ChannelConfig` for more information about the // fields. type ChannelConfig struct { channeldb.ChannelConstraints MultiSigKey KeyDescriptor RevocationBasePoint KeyDescriptor PaymentBasePoint KeyDescriptor DelayBasePoint KeyDescriptor HtlcBasePoint KeyDescriptor } // KeyDescriptor is the information we want to dump from a key descriptor. See // `keychain.KeyDescriptor` for more information about the fields. type KeyDescriptor struct { Path string PubKey string } // OpenChannelDump converts the open channels in the given channel DB into a // dumpable format. func OpenChannelDump(channels []*channeldb.OpenChannel, params *chaincfg.Params) ([]OpenChannel, error) { dumpChannels := make([]OpenChannel, len(channels)) for idx, channel := range channels { openChan, err := openChannelDump(channel, params) if err != nil { return nil, fmt.Errorf("error converting to dump "+ "format: %w", err) } 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 } } 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.WitnessScriptToSign(), ), ToLocalAddr: toLocalAddr.String(), ToRemoteScript: hex.EncodeToString( toRemoteScript.WitnessScriptToSign(), ), 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, ChainHash: channel.ChainHash, ClosingTXID: channel.ClosingTXID.String(), RemotePub: PubKeyToString(channel.RemotePub), Capacity: channel.Capacity, CloseHeight: channel.CloseHeight, SettledBalance: channel.SettledBalance, TimeLockedBalance: channel.TimeLockedBalance, CloseType: fmt.Sprintf( "%d", channel.CloseType, ), IsPending: channel.IsPending, RemoteCurrentRevocation: PubKeyToString( channel.RemoteCurrentRevocation, ), RemoteNextRevocation: PubKeyToString( channel.RemoteNextRevocation, ), LocalChanConfig: ToChannelConfig( params, channel.LocalChanConfig, ), NextLocalCommitHeight: nextLocalHeight, RemoteCommitTailHeight: remoteTailHeight, LastRemoteCommitSecret: lastRemoteSecret, LocalUnrevokedCommitPoint: PubKeyToString( localUnrevokedCommitPoint, ), HistoricalChannel: historicalChannel, } } return dumpChannels, nil } // BackupDump converts the given multi backup into a dumpable format. func BackupDump(multi *chanbackup.Multi, params *chaincfg.Params) []BackupSingle { dumpSingles := make([]BackupSingle, len(multi.StaticBackups)) for idx, single := range multi.StaticBackups { dumpSingles[idx] = BackupSingle{ Version: single.Version, IsInitiator: single.IsInitiator, ChainHash: single.ChainHash.String(), FundingOutpoint: single.FundingOutpoint.String(), ShortChannelID: single.ShortChannelID, RemoteNodePub: PubKeyToString( single.RemoteNodePub, ), Addresses: single.Addresses, Capacity: single.Capacity, LocalChanCfg: ToChannelConfig( params, single.LocalChanCfg, ), RemoteChanCfg: ToChannelConfig( params, single.RemoteChanCfg, ), ShaChainRootDesc: ToKeyDescriptor( params, single.ShaChainRootDesc, ), } } return dumpSingles } func ToChannelConfig(params *chaincfg.Params, cfg channeldb.ChannelConfig) ChannelConfig { return ChannelConfig{ ChannelConstraints: cfg.ChannelConstraints, MultiSigKey: ToKeyDescriptor(params, cfg.MultiSigKey), RevocationBasePoint: ToKeyDescriptor( params, cfg.RevocationBasePoint, ), PaymentBasePoint: ToKeyDescriptor( params, cfg.PaymentBasePoint, ), DelayBasePoint: ToKeyDescriptor( params, cfg.DelayBasePoint, ), HtlcBasePoint: ToKeyDescriptor(params, cfg.HtlcBasePoint), } } func ToKeyDescriptor(params *chaincfg.Params, desc keychain.KeyDescriptor) KeyDescriptor { return KeyDescriptor{ Path: fmt.Sprintf( lndInternalDerivationPath, params.HDCoinType, desc.Family, desc.Index, ), PubKey: PubKeyToString(desc.PubKey), } } func PubKeyToString(pubkey *btcec.PublicKey) string { if pubkey == nil { return "" } return hex.EncodeToString(pubkey.SerializeCompressed()) }