You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
chantools/cmd/chantools/dropchannelgraph.go

287 lines
8.8 KiB
Go

package main
import (
"bytes"
"encoding/hex"
"fmt"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/chantools/lnd"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/spf13/cobra"
)
var (
nodeBucket = []byte("graph-node")
edgeBucket = []byte("graph-edge")
graphMetaBucket = []byte("graph-meta")
)
type dropChannelGraphCommand struct {
ChannelDB string
NodeIdentityKey string
FixOnly bool
SingleChannel uint64
cmd *cobra.Command
}
func newDropChannelGraphCommand() *cobra.Command {
cc := &dropChannelGraphCommand{}
cc.cmd = &cobra.Command{
Use: "dropchannelgraph",
Short: "Remove all graph related data from a channel DB",
Long: `This command removes all graph data from a channel DB,
forcing the lnd node to do a full graph sync.
Or if a single channel is specified, that channel is purged from the graph
without removing any other data.
CAUTION: Running this command will make it impossible to use the channel DB
with an older version of lnd. Downgrading is not possible and you'll need to
run lnd ` + lndVersion + ` or later after using this command!'`,
Example: `chantools dropchannelgraph \
--channeldb ~/.lnd/data/graph/mainnet/channel.db \
--node_identity_key 03......
chantools dropchannelgraph \
--channeldb ~/.lnd/data/graph/mainnet/channel.db \
--single_channel 726607861215512345
--node_identity_key 03......`,
RunE: cc.Execute,
}
cc.cmd.Flags().StringVar(
&cc.ChannelDB, "channeldb", "", "lnd channel.db file to drop "+
"channels from",
)
cc.cmd.Flags().Uint64Var(
&cc.SingleChannel, "single_channel", 0, "the single channel "+
"identified by its short channel ID (CID) to remove "+
"from the graph",
)
cc.cmd.Flags().StringVar(
&cc.NodeIdentityKey, "node_identity_key", "", "your node's "+
"identity public key",
)
cc.cmd.Flags().BoolVar(
&cc.FixOnly, "fix_only", false, "fix an already empty graph "+
"by re-adding the own node's channels",
)
return cc.cmd
}
func (c *dropChannelGraphCommand) Execute(_ *cobra.Command, _ []string) error {
// Check that we have a channel DB.
if c.ChannelDB == "" {
return fmt.Errorf("channel DB is required")
}
db, err := lnd.OpenDB(c.ChannelDB, false)
if err != nil {
return fmt.Errorf("error opening rescue DB: %w", err)
}
defer func() { _ = db.Close() }()
if c.NodeIdentityKey == "" {
return fmt.Errorf("node identity key is required")
}
idKeyBytes, err := hex.DecodeString(c.NodeIdentityKey)
if err != nil {
return fmt.Errorf("error hex decoding node identity key: %w",
err)
}
idKey, err := btcec.ParsePubKey(idKeyBytes)
if err != nil {
return fmt.Errorf("error parsing node identity key: %w", err)
}
if c.SingleChannel != 0 {
log.Infof("Removing single channel %d", c.SingleChannel)
return db.ChannelGraph().DeleteChannelEdges(
true, false, c.SingleChannel,
)
}
// Drop all channels, then insert our own channels into the graph again.
if !c.FixOnly {
log.Infof("Dropping all graph related buckets")
rwTx, err := db.BeginReadWriteTx()
if err != nil {
return err
}
if err := rwTx.DeleteTopLevelBucket(nodeBucket); err != nil {
return err
}
if err := rwTx.DeleteTopLevelBucket(edgeBucket); err != nil {
return err
}
if err := rwTx.DeleteTopLevelBucket(graphMetaBucket); err != nil {
return err
}
if err := rwTx.Commit(); err != nil {
return err
}
}
return insertOwnNodeAndChannels(idKey, db)
}
func insertOwnNodeAndChannels(idKey *btcec.PublicKey, db *channeldb.DB) error {
openChannels, err := db.ChannelStateDB().FetchAllOpenChannels()
if err != nil {
return fmt.Errorf("error fetching open channels: %w", err)
}
graph := db.ChannelGraph()
for _, openChan := range openChannels {
edge, update, err := newChanAnnouncement(
idKey, openChan.IdentityPub,
&openChan.LocalChanCfg.MultiSigKey,
openChan.RemoteChanCfg.MultiSigKey.PubKey,
openChan.ShortChannelID, openChan.LocalChanCfg.MinHTLC,
openChan.LocalChanCfg.MaxPendingAmount,
openChan.Capacity, openChan.FundingOutpoint,
)
if err != nil {
return fmt.Errorf("error creating announcement: %w",
err)
}
if err := graph.AddChannelEdge(edge); err != nil {
log.Warnf("Not adding channel edge %v because of "+
"error: %v", edge.ChannelPoint, err)
}
if err := graph.UpdateEdgePolicy(update); err != nil {
log.Warnf("Not updating edge policy %v because of "+
"error: %v", update.ChannelID, err)
}
}
return nil
}
func newChanAnnouncement(localPubKey, remotePubKey *btcec.PublicKey,
localFundingKey *keychain.KeyDescriptor,
remoteFundingKey *btcec.PublicKey, shortChanID lnwire.ShortChannelID,
fwdMinHTLC, fwdMaxHTLC lnwire.MilliSatoshi, capacity btcutil.Amount,
channelPoint wire.OutPoint) (*channeldb.ChannelEdgeInfo,
*channeldb.ChannelEdgePolicy, error) {
chainHash := *chainParams.GenesisHash
// The unconditional section of the announcement is the ShortChannelID
// itself which compactly encodes the location of the funding output
// within the blockchain.
chanAnn := &lnwire.ChannelAnnouncement{
ShortChannelID: shortChanID,
Features: lnwire.NewRawFeatureVector(),
ChainHash: chainHash,
}
// The chanFlags field indicates which directed edge of the channel is
// being updated within the ChannelUpdateAnnouncement announcement
// below. A value of zero means it's the edge of the "first" node and 1
// being the other node.
var chanFlags lnwire.ChanUpdateChanFlags
// The lexicographical ordering of the two identity public keys of the
// nodes indicates which of the nodes is "first". If our serialized
// identity key is lower than theirs then we're the "first" node and
// second otherwise.
selfBytes := localPubKey.SerializeCompressed()
remoteBytes := remotePubKey.SerializeCompressed()
if bytes.Compare(selfBytes, remoteBytes) == -1 {
copy(chanAnn.NodeID1[:], localPubKey.SerializeCompressed())
copy(chanAnn.NodeID2[:], remotePubKey.SerializeCompressed())
copy(chanAnn.BitcoinKey1[:], localFundingKey.PubKey.SerializeCompressed())
copy(chanAnn.BitcoinKey2[:], remoteFundingKey.SerializeCompressed())
// If we're the first node then update the chanFlags to
// indicate the "direction" of the update.
chanFlags = 0
} else {
copy(chanAnn.NodeID1[:], remotePubKey.SerializeCompressed())
copy(chanAnn.NodeID2[:], localPubKey.SerializeCompressed())
copy(chanAnn.BitcoinKey1[:], remoteFundingKey.SerializeCompressed())
copy(chanAnn.BitcoinKey2[:], localFundingKey.PubKey.SerializeCompressed())
// If we're the second node then update the chanFlags to
// indicate the "direction" of the update.
chanFlags = 1
}
var featureBuf bytes.Buffer
if err := chanAnn.Features.Encode(&featureBuf); err != nil {
log.Errorf("unable to encode features: %w", err)
return nil, nil, err
}
edge := &channeldb.ChannelEdgeInfo{
ChannelID: chanAnn.ShortChannelID.ToUint64(),
ChainHash: chanAnn.ChainHash,
NodeKey1Bytes: chanAnn.NodeID1,
NodeKey2Bytes: chanAnn.NodeID2,
BitcoinKey1Bytes: chanAnn.BitcoinKey1,
BitcoinKey2Bytes: chanAnn.BitcoinKey2,
AuthProof: nil,
Features: featureBuf.Bytes(),
ExtraOpaqueData: chanAnn.ExtraOpaqueData,
Capacity: capacity,
ChannelPoint: channelPoint,
}
// Our channel update message flags will signal that we support the
// max_htlc field.
msgFlags := lnwire.ChanUpdateRequiredMaxHtlc
// We announce the channel with the default values. Some of
// these values can later be changed by crafting a new ChannelUpdate.
chanUpdateAnn := &lnwire.ChannelUpdate{
ShortChannelID: shortChanID,
ChainHash: chainHash,
Timestamp: uint32(time.Now().Unix()),
MessageFlags: msgFlags,
ChannelFlags: chanFlags,
TimeLockDelta: uint16(chainreg.DefaultBitcoinTimeLockDelta),
// We use the HtlcMinimumMsat that the remote party required us
// to use, as our ChannelUpdate will be used to carry HTLCs
// towards them.
HtlcMinimumMsat: fwdMinHTLC,
HtlcMaximumMsat: fwdMaxHTLC,
BaseFee: uint32(chainreg.DefaultBitcoinBaseFeeMSat),
FeeRate: uint32(chainreg.DefaultBitcoinFeeRate),
}
update := &channeldb.ChannelEdgePolicy{
SigBytes: chanUpdateAnn.Signature.ToSignatureBytes(),
ChannelID: chanAnn.ShortChannelID.ToUint64(),
LastUpdate: time.Now(),
MessageFlags: chanUpdateAnn.MessageFlags,
ChannelFlags: chanUpdateAnn.ChannelFlags,
TimeLockDelta: chanUpdateAnn.TimeLockDelta,
MinHTLC: chanUpdateAnn.HtlcMinimumMsat,
MaxHTLC: chanUpdateAnn.HtlcMaximumMsat,
FeeBaseMSat: lnwire.MilliSatoshi(
chanUpdateAnn.BaseFee,
),
FeeProportionalMillionths: lnwire.MilliSatoshi(
chanUpdateAnn.FeeRate,
),
ExtraOpaqueData: chanUpdateAnn.ExtraOpaqueData,
}
return edge, update, nil
}