mirror of https://github.com/guggero/chantools
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.
287 lines
8.8 KiB
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/guggero/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 v0.16.0-beta 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 dump "+
|
|
"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
|
|
}
|