Skip to content

Commit

Permalink
Merge pull request #182 from lightninglabs/sweepremoteclosed-ancient
Browse files Browse the repository at this point in the history
sweepremoteclosed: add commit points for ancient channels of LNBIG
  • Loading branch information
guggero authored Feb 10, 2025
2 parents 74b748e + 343cb8d commit 5657a7a
Show file tree
Hide file tree
Showing 12 changed files with 12,770 additions and 26 deletions.
37 changes: 33 additions & 4 deletions cmd/chantools/rescueclosed.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ var (

type cacheEntry struct {
privKey *btcec.PrivateKey
pubKey *btcec.PublicKey
keyDesc *keychain.KeyDescriptor
}

type rescueClosedCommand struct {
Expand Down Expand Up @@ -403,7 +403,7 @@ func addrInCache(numKeys uint32, addr string,
for i := range numKeys {
cacheEntry := cache[i]
hashedPubKey := btcutil.Hash160(
cacheEntry.pubKey.SerializeCompressed(),
cacheEntry.keyDesc.PubKey.SerializeCompressed(),
)
equal := subtle.ConstantTimeCompare(
targetPubKeyHash, hashedPubKey,
Expand Down Expand Up @@ -431,7 +431,7 @@ func addrInCache(numKeys uint32, addr string,
// corresponds to the target pubKeyHash of the given address.
for i := range numKeys {
cacheEntry := cache[i]
basePoint := cacheEntry.pubKey
basePoint := cacheEntry.keyDesc.PubKey
tweakedPubKey := input.TweakPubKey(basePoint, perCommitPoint)
tweakBytes := input.SingleTweakBytes(perCommitPoint, basePoint)
tweakedPrivKey := input.TweakPrivKey(
Expand Down Expand Up @@ -460,6 +460,29 @@ func addrInCache(numKeys uint32, addr string,
return "", errAddrNotFound
}

func keyInCache(numKeys uint32, targetPubKeyHash []byte,
perCommitPoint *btcec.PublicKey) (*keychain.KeyDescriptor, []byte,
error) {

for i := range numKeys {
cacheEntry := cache[i]
basePoint := cacheEntry.keyDesc.PubKey
tweakedPubKey := input.TweakPubKey(basePoint, perCommitPoint)
tweakBytes := input.SingleTweakBytes(perCommitPoint, basePoint)
hashedPubKey := btcutil.Hash160(
tweakedPubKey.SerializeCompressed(),
)
equal := subtle.ConstantTimeCompare(
targetPubKeyHash, hashedPubKey,
)
if equal == 1 {
return cacheEntry.keyDesc, tweakBytes, nil
}
}

return nil, nil, errAddrNotFound
}

func fillCache(numKeys uint32, extendedKey *hdkeychain.ExtendedKey) error {
cache = make([]*cacheEntry, numKeys)

Expand All @@ -484,7 +507,13 @@ func fillCache(numKeys uint32, extendedKey *hdkeychain.ExtendedKey) error {
}
cache[i] = &cacheEntry{
privKey: privKey,
pubKey: pubKey,
keyDesc: &keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyPaymentBase,
Index: i,
},
PubKey: pubKey,
},
}

if i > 0 && i%10000 == 0 {
Expand Down
16 changes: 15 additions & 1 deletion cmd/chantools/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const (
// version is the current version of the tool. It is set during build.
// NOTE: When changing this, please also update the version in the
// download link shown in the README.
version = "0.13.5"
version = "0.13.6"
na = "n/a"

// lndVersion is the current version of lnd that we support. This is
Expand Down Expand Up @@ -231,6 +231,7 @@ type inputFlags struct {
PendingChannels string
FromSummary string
FromChannelDB string
FromChannelDump string
}

func newInputFlags(cmd *cobra.Command) *inputFlags {
Expand All @@ -250,6 +251,10 @@ func newInputFlags(cmd *cobra.Command) *inputFlags {
cmd.Flags().StringVar(&f.FromChannelDB, "fromchanneldb", "", "channel "+
"input is in the format of an lnd channel.db file",
)
cmd.Flags().StringVar(
&f.FromChannelDump, "fromchanneldump", "", "channel "+
"input is in the format of a channel dump file",
)

return f
}
Expand Down Expand Up @@ -283,6 +288,15 @@ func (f *inputFlags) parseInputType() ([]*dataformat.SummaryEntry, error) {
target = &dataformat.ChannelDBFile{DB: db.ChannelStateDB()}
return target.AsSummaryEntries()

case f.FromChannelDump != "":
content, err = readInput(f.FromChannelDump)
if err != nil {
return nil, fmt.Errorf("error reading channel dump: %w",
err)
}

return dataformat.ExtractSummaryFromDump(string(content))

default:
return nil, errors.New("an input file must be specified")
}
Expand Down
150 changes: 150 additions & 0 deletions cmd/chantools/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ import (
"os"
"time"

"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/chantools/btc"
"github.com/lightninglabs/chantools/dataformat"
"github.com/lightninglabs/chantools/lnd"
"github.com/spf13/cobra"
)

type summaryCommand struct {
APIURL string

Ancient bool
AncientStats string

inputs *inputFlags
cmd *cobra.Command
}
Expand All @@ -35,18 +40,36 @@ chantools summary --fromchanneldb ~/.lnd/data/graph/mainnet/channel.db`,
&cc.APIURL, "apiurl", defaultAPIURL, "API URL to use (must "+
"be esplora compatible)",
)
cc.cmd.Flags().BoolVar(
&cc.Ancient, "ancient", false, "Create summary of ancient "+
"channel closes with un-swept outputs",
)
cc.cmd.Flags().StringVar(
&cc.AncientStats, "ancientstats", "", "Create summary of "+
"ancient channel closes with un-swept outputs and "+
"print stats for the given list of channels",
)

cc.inputs = newInputFlags(cc.cmd)

return cc.cmd
}

func (c *summaryCommand) Execute(_ *cobra.Command, _ []string) error {
if c.AncientStats != "" {
return summarizeAncientChannelOutputs(c.APIURL, c.AncientStats)
}

// Parse channel entries from any of the possible input files.
entries, err := c.inputs.parseInputType()
if err != nil {
return err
}

if c.Ancient {
return summarizeAncientChannels(c.APIURL, entries)
}

return summarizeChannels(c.APIURL, entries)
}

Expand Down Expand Up @@ -90,3 +113,130 @@ func summarizeChannels(apiURL string,
log.Infof("Writing result to %s", fileName)
return os.WriteFile(fileName, summaryBytes, 0644)
}

func summarizeAncientChannels(apiURL string,
channels []*dataformat.SummaryEntry) error {

api := newExplorerAPI(apiURL)

var results []*ancientChannel
for _, target := range channels {
if target.ClosingTX == nil {
continue
}

closeTx := target.ClosingTX
if !closeTx.ForceClose {
continue
}

if closeTx.AllOutsSpent {
continue
}

if closeTx.OurAddr != "" {
log.Infof("Channel %s has potential funds: %d in %s",
target.ChannelPoint, target.LocalBalance,
closeTx.OurAddr)
}

if target.LocalUnrevokedCommitPoint == "" {
log.Warnf("Channel %s has no unrevoked commit point",
target.ChannelPoint)
continue
}

if closeTx.ToRemoteAddr == "" {
log.Warnf("Close TX %s has no remote address",
closeTx.TXID)
continue
}

addr, err := lnd.ParseAddress(closeTx.ToRemoteAddr, chainParams)
if err != nil {
return fmt.Errorf("error parsing address %s of %s: %w",
closeTx.ToRemoteAddr, closeTx.TXID, err)
}

if _, ok := addr.(*btcutil.AddressWitnessPubKeyHash); !ok {
log.Infof("Channel close %s has non-p2wkh output: %s",
closeTx.TXID, closeTx.ToRemoteAddr)
continue
}

tx, err := api.Transaction(closeTx.TXID)
if err != nil {
return fmt.Errorf("error fetching transaction %s: %w",
closeTx.TXID, err)
}

for idx, txOut := range tx.Vout {
if txOut.Outspend.Spent {
continue
}

if txOut.ScriptPubkeyAddr == closeTx.ToRemoteAddr {
results = append(results, &ancientChannel{
OP: fmt.Sprintf("%s:%d", closeTx.TXID,
idx),
Addr: closeTx.ToRemoteAddr,
CP: target.LocalUnrevokedCommitPoint,
})
}
}
}

summaryBytes, err := json.MarshalIndent(results, "", " ")
if err != nil {
return err
}
fileName := fmt.Sprintf("results/summary-ancient-%s.json",
time.Now().Format("2006-01-02-15-04-05"))
log.Infof("Writing result to %s", fileName)
return os.WriteFile(fileName, summaryBytes, 0644)
}

func summarizeAncientChannelOutputs(apiURL, ancientFile string) error {
jsonBytes, err := os.ReadFile(ancientFile)
if err != nil {
return fmt.Errorf("error reading file %s: %w", ancientFile, err)
}

var ancients []ancientChannel
err = json.Unmarshal(jsonBytes, &ancients)
if err != nil {
return fmt.Errorf("error unmarshalling ancient channels: %w",
err)
}

var (
api = newExplorerAPI(apiURL)
numUnspents uint32
unspentSats uint64
)
for _, channel := range ancients {
unspents, err := api.Unspent(channel.Addr)
if err != nil {
return fmt.Errorf("error fetching unspents for %s: %w",
channel.Addr, err)
}

if len(unspents) > 1 {
log.Infof("Address %s has multiple unspents",
channel.Addr)
}
for _, unspent := range unspents {
if unspent.Outspend.Spent {
continue
}

numUnspents++
unspentSats += unspent.Value
}
}

log.Infof("Found %d unspent outputs with %d sats", numUnspents,
unspentSats)

return nil
}
Loading

0 comments on commit 5657a7a

Please sign in to comment.