• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 12986279612

27 Jan 2025 09:51AM UTC coverage: 57.652% (-1.1%) from 58.788%
12986279612

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19578 existing lines in 256 files now uncovered.

103448 of 179434 relevant lines covered (57.65%)

24884.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/chanrestore.go
1
package lnd
2

3
import (
4
        "fmt"
5
        "math"
6
        "net"
7

8
        "github.com/btcsuite/btcd/btcec/v2"
9
        "github.com/btcsuite/btcd/chaincfg"
10
        "github.com/btcsuite/btcd/chaincfg/chainhash"
11
        "github.com/btcsuite/btcd/wire"
12
        "github.com/lightningnetwork/lnd/chanbackup"
13
        "github.com/lightningnetwork/lnd/channeldb"
14
        "github.com/lightningnetwork/lnd/contractcourt"
15
        "github.com/lightningnetwork/lnd/keychain"
16
        "github.com/lightningnetwork/lnd/lnwire"
17
        "github.com/lightningnetwork/lnd/shachain"
18
)
19

20
const (
21
        // mainnetSCBLaunchBlock is the approximate block height of the bitcoin
22
        // mainnet chain of the date when SCBs first were released in lnd
23
        // (v0.6.0-beta). The block date is 4/15/2019, 10:54 PM UTC.
24
        mainnetSCBLaunchBlock = 571800
25

26
        // testnetSCBLaunchBlock is the approximate block height of the bitcoin
27
        // testnet3 chain of the date when SCBs first were released in lnd
28
        // (v0.6.0-beta). The block date is 4/16/2019, 08:04 AM UTC.
29
        testnetSCBLaunchBlock = 1489300
30
)
31

32
// chanDBRestorer is an implementation of the chanbackup.ChannelRestorer
33
// interface that is able to properly map a Single backup, into a
34
// channeldb.ChannelShell which is required to fully restore a channel. We also
35
// need the secret key chain in order obtain the prior shachain root so we can
36
// verify the DLP protocol as initiated by the remote node.
37
type chanDBRestorer struct {
38
        db *channeldb.ChannelStateDB
39

40
        secretKeys keychain.SecretKeyRing
41

42
        chainArb *contractcourt.ChainArbitrator
43
}
44

45
// openChannelShell maps the static channel back up into an open channel
46
// "shell". We say shell as this doesn't include all the information required
47
// to continue to use the channel, only the minimal amount of information to
48
// insert this shell channel back into the database.
49
func (c *chanDBRestorer) openChannelShell(backup chanbackup.Single) (
UNCOV
50
        *channeldb.ChannelShell, error) {
×
UNCOV
51

×
UNCOV
52
        var err error
×
UNCOV
53

×
UNCOV
54
        // Each of the keys in our local channel config only have their
×
UNCOV
55
        // locators populate, so we'll re-derive the raw key now as we'll need
×
UNCOV
56
        // it in order to carry out the DLP protocol.
×
UNCOV
57
        backup.LocalChanCfg.MultiSigKey, err = c.secretKeys.DeriveKey(
×
UNCOV
58
                backup.LocalChanCfg.MultiSigKey.KeyLocator,
×
UNCOV
59
        )
×
UNCOV
60
        if err != nil {
×
61
                return nil, fmt.Errorf("unable to derive multi sig key: %w",
×
62
                        err)
×
63
        }
×
UNCOV
64
        backup.LocalChanCfg.RevocationBasePoint, err = c.secretKeys.DeriveKey(
×
UNCOV
65
                backup.LocalChanCfg.RevocationBasePoint.KeyLocator,
×
UNCOV
66
        )
×
UNCOV
67
        if err != nil {
×
68
                return nil, fmt.Errorf("unable to derive revocation key: %w",
×
69
                        err)
×
70
        }
×
UNCOV
71
        backup.LocalChanCfg.PaymentBasePoint, err = c.secretKeys.DeriveKey(
×
UNCOV
72
                backup.LocalChanCfg.PaymentBasePoint.KeyLocator,
×
UNCOV
73
        )
×
UNCOV
74
        if err != nil {
×
75
                return nil, fmt.Errorf("unable to derive payment key: %w", err)
×
76
        }
×
UNCOV
77
        backup.LocalChanCfg.DelayBasePoint, err = c.secretKeys.DeriveKey(
×
UNCOV
78
                backup.LocalChanCfg.DelayBasePoint.KeyLocator,
×
UNCOV
79
        )
×
UNCOV
80
        if err != nil {
×
81
                return nil, fmt.Errorf("unable to derive delay key: %w", err)
×
82
        }
×
UNCOV
83
        backup.LocalChanCfg.HtlcBasePoint, err = c.secretKeys.DeriveKey(
×
UNCOV
84
                backup.LocalChanCfg.HtlcBasePoint.KeyLocator,
×
UNCOV
85
        )
×
UNCOV
86
        if err != nil {
×
87
                return nil, fmt.Errorf("unable to derive htlc key: %w", err)
×
88
        }
×
89

90
        // The shachain root that seeds RevocationProducer for this channel.
91
        // It currently has two possible formats.
UNCOV
92
        var revRoot *chainhash.Hash
×
UNCOV
93

×
UNCOV
94
        // If the PubKey field is non-nil, then this shachain root is using the
×
UNCOV
95
        // legacy non-ECDH scheme.
×
UNCOV
96
        if backup.ShaChainRootDesc.PubKey != nil {
×
UNCOV
97
                ltndLog.Debugf("Using legacy revocation producer format for "+
×
UNCOV
98
                        "channel point %v", backup.FundingOutpoint)
×
UNCOV
99

×
UNCOV
100
                // Obtain the private key for the shachain root from the
×
UNCOV
101
                // encoded public key.
×
UNCOV
102
                privKey, err := c.secretKeys.DerivePrivKey(
×
UNCOV
103
                        backup.ShaChainRootDesc,
×
UNCOV
104
                )
×
UNCOV
105
                if err != nil {
×
106
                        return nil, fmt.Errorf("could not derive private key "+
×
107
                                "for legacy channel revocation root format: "+
×
108
                                "%v", err)
×
109
                }
×
110

UNCOV
111
                revRoot, err = chainhash.NewHash(privKey.Serialize())
×
UNCOV
112
                if err != nil {
×
113
                        return nil, err
×
114
                }
×
UNCOV
115
        } else {
×
UNCOV
116
                ltndLog.Debugf("Using new ECDH revocation producer format "+
×
UNCOV
117
                        "for channel point %v", backup.FundingOutpoint)
×
UNCOV
118

×
UNCOV
119
                // This is the scheme in which the shachain root is derived via
×
UNCOV
120
                // an ECDH operation on the private key of ShaChainRootDesc and
×
UNCOV
121
                // our public multisig key.
×
UNCOV
122
                ecdh, err := c.secretKeys.ECDH(
×
UNCOV
123
                        backup.ShaChainRootDesc,
×
UNCOV
124
                        backup.LocalChanCfg.MultiSigKey.PubKey,
×
UNCOV
125
                )
×
UNCOV
126
                if err != nil {
×
127
                        return nil, fmt.Errorf("unable to derive shachain "+
×
128
                                "root: %v", err)
×
129
                }
×
130

UNCOV
131
                ch := chainhash.Hash(ecdh)
×
UNCOV
132
                revRoot = &ch
×
133
        }
134

UNCOV
135
        shaChainProducer := shachain.NewRevocationProducer(*revRoot)
×
UNCOV
136

×
UNCOV
137
        var chanType channeldb.ChannelType
×
UNCOV
138
        switch backup.Version {
×
139
        case chanbackup.DefaultSingleVersion:
×
140
                chanType = channeldb.SingleFunderBit
×
141

UNCOV
142
        case chanbackup.TweaklessCommitVersion:
×
UNCOV
143
                chanType = channeldb.SingleFunderTweaklessBit
×
144

145
        case chanbackup.AnchorsCommitVersion:
×
146
                chanType = channeldb.AnchorOutputsBit
×
147
                chanType |= channeldb.SingleFunderTweaklessBit
×
148

UNCOV
149
        case chanbackup.AnchorsZeroFeeHtlcTxCommitVersion:
×
UNCOV
150
                chanType = channeldb.ZeroHtlcTxFeeBit
×
UNCOV
151
                chanType |= channeldb.AnchorOutputsBit
×
UNCOV
152
                chanType |= channeldb.SingleFunderTweaklessBit
×
153

UNCOV
154
        case chanbackup.ScriptEnforcedLeaseVersion:
×
UNCOV
155
                chanType = channeldb.LeaseExpirationBit
×
UNCOV
156
                chanType |= channeldb.ZeroHtlcTxFeeBit
×
UNCOV
157
                chanType |= channeldb.AnchorOutputsBit
×
UNCOV
158
                chanType |= channeldb.SingleFunderTweaklessBit
×
159

UNCOV
160
        case chanbackup.SimpleTaprootVersion:
×
UNCOV
161
                chanType = channeldb.ZeroHtlcTxFeeBit
×
UNCOV
162
                chanType |= channeldb.AnchorOutputsBit
×
UNCOV
163
                chanType |= channeldb.SingleFunderTweaklessBit
×
UNCOV
164
                chanType |= channeldb.SimpleTaprootFeatureBit
×
165

166
        case chanbackup.TapscriptRootVersion:
×
167
                chanType = channeldb.ZeroHtlcTxFeeBit
×
168
                chanType |= channeldb.AnchorOutputsBit
×
169
                chanType |= channeldb.SingleFunderTweaklessBit
×
170
                chanType |= channeldb.SimpleTaprootFeatureBit
×
171
                chanType |= channeldb.TapscriptRootBit
×
172

173
        default:
×
174
                return nil, fmt.Errorf("unknown Single version: %w", err)
×
175
        }
176

UNCOV
177
        ltndLog.Infof("SCB Recovery: created channel shell for ChannelPoint"+
×
UNCOV
178
                "(%v), chan_type=%v", backup.FundingOutpoint, chanType)
×
UNCOV
179

×
UNCOV
180
        chanShell := channeldb.ChannelShell{
×
UNCOV
181
                NodeAddrs: backup.Addresses,
×
UNCOV
182
                Chan: &channeldb.OpenChannel{
×
UNCOV
183
                        ChanType:                chanType,
×
UNCOV
184
                        ChainHash:               backup.ChainHash,
×
UNCOV
185
                        IsInitiator:             backup.IsInitiator,
×
UNCOV
186
                        Capacity:                backup.Capacity,
×
UNCOV
187
                        FundingOutpoint:         backup.FundingOutpoint,
×
UNCOV
188
                        ShortChannelID:          backup.ShortChannelID,
×
UNCOV
189
                        IdentityPub:             backup.RemoteNodePub,
×
UNCOV
190
                        IsPending:               false,
×
UNCOV
191
                        LocalChanCfg:            backup.LocalChanCfg,
×
UNCOV
192
                        RemoteChanCfg:           backup.RemoteChanCfg,
×
UNCOV
193
                        RemoteCurrentRevocation: backup.RemoteNodePub,
×
UNCOV
194
                        RevocationStore:         shachain.NewRevocationStore(),
×
UNCOV
195
                        RevocationProducer:      shaChainProducer,
×
UNCOV
196
                        ThawHeight:              backup.LeaseExpiry,
×
UNCOV
197
                },
×
UNCOV
198
        }
×
UNCOV
199

×
UNCOV
200
        return &chanShell, nil
×
201
}
202

203
// RestoreChansFromSingles attempts to map the set of single channel backups to
204
// channel shells that will be stored persistently. Once these shells have been
205
// stored on disk, we'll be able to connect to the channel peer an execute the
206
// data loss recovery protocol.
207
//
208
// NOTE: Part of the chanbackup.ChannelRestorer interface.
UNCOV
209
func (c *chanDBRestorer) RestoreChansFromSingles(backups ...chanbackup.Single) error {
×
UNCOV
210
        channelShells := make([]*channeldb.ChannelShell, 0, len(backups))
×
UNCOV
211
        firstChanHeight := uint32(math.MaxUint32)
×
UNCOV
212
        for _, backup := range backups {
×
UNCOV
213
                chanShell, err := c.openChannelShell(backup)
×
UNCOV
214
                if err != nil {
×
215
                        return err
×
216
                }
×
217

218
                // Find the block height of the earliest channel in this backup.
UNCOV
219
                chanHeight := chanShell.Chan.ShortChanID().BlockHeight
×
UNCOV
220
                if chanHeight != 0 && chanHeight < firstChanHeight {
×
UNCOV
221
                        firstChanHeight = chanHeight
×
UNCOV
222
                }
×
223

UNCOV
224
                channelShells = append(channelShells, chanShell)
×
225
        }
226

227
        // In case there were only unconfirmed channels, we will have to scan
228
        // the chain beginning from the launch date of SCBs.
UNCOV
229
        if firstChanHeight == math.MaxUint32 {
×
230
                chainHash := channelShells[0].Chan.ChainHash
×
231
                switch {
×
232
                case chainHash.IsEqual(chaincfg.MainNetParams.GenesisHash):
×
233
                        firstChanHeight = mainnetSCBLaunchBlock
×
234

235
                case chainHash.IsEqual(chaincfg.TestNet3Params.GenesisHash):
×
236
                        firstChanHeight = testnetSCBLaunchBlock
×
237

238
                default:
×
239
                        // Worst case: We have no height hint and start at
×
240
                        // block 1. Should only happen for SCBs in regtest
×
241
                        // and simnet.
×
242
                        firstChanHeight = 1
×
243
                }
244
        }
245

246
        // If there were channels in the backup that were not confirmed at the
247
        // time of the backup creation, they won't have a block height in the
248
        // ShortChanID which would lead to an error in the chain watcher.
249
        // We want to at least set the funding broadcast height that the chain
250
        // watcher can use instead. We have two possible fallback values for
251
        // the broadcast height that we are going to try here.
UNCOV
252
        for _, chanShell := range channelShells {
×
UNCOV
253
                channel := chanShell.Chan
×
UNCOV
254

×
UNCOV
255
                switch {
×
256
                // Fallback case 1: This is an unconfirmed channel from an old
257
                // backup file where we didn't have any workaround in place and
258
                // the short channel ID is 0:0:0. Best we can do here is set the
259
                // funding broadcast height to a reasonable value that we
260
                // determined earlier.
261
                case channel.ShortChanID().BlockHeight == 0:
×
262
                        channel.SetBroadcastHeight(firstChanHeight)
×
263

264
                // Fallback case 2: It is extremely unlikely at this point that
265
                // a channel we are trying to restore has a coinbase funding TX.
266
                // Therefore we can be quite certain that if the TxIndex is
267
                // zero but the block height wasn't, it was an unconfirmed
268
                // channel where we used the BlockHeight to encode the funding
269
                // TX broadcast height. To not end up with an invalid short
270
                // channel ID that looks valid, we restore the "original"
271
                // unconfirmed one here.
UNCOV
272
                case channel.ShortChannelID.TxIndex == 0:
×
UNCOV
273
                        broadcastHeight := channel.ShortChannelID.BlockHeight
×
UNCOV
274
                        channel.SetBroadcastHeight(broadcastHeight)
×
UNCOV
275
                        channel.ShortChannelID.BlockHeight = 0
×
276
                }
277
        }
278

UNCOV
279
        ltndLog.Infof("Inserting %v SCB channel shells into DB",
×
UNCOV
280
                len(channelShells))
×
UNCOV
281

×
UNCOV
282
        // Now that we have all the backups mapped into a series of Singles,
×
UNCOV
283
        // we'll insert them all into the database.
×
UNCOV
284
        if err := c.db.RestoreChannelShells(channelShells...); err != nil {
×
UNCOV
285
                return err
×
UNCOV
286
        }
×
287

UNCOV
288
        ltndLog.Infof("Informing chain watchers of new restored channels")
×
UNCOV
289

×
UNCOV
290
        // Create a slice of channel points.
×
UNCOV
291
        chanPoints := make([]wire.OutPoint, 0, len(channelShells))
×
UNCOV
292

×
UNCOV
293
        // Finally, we'll need to inform the chain arbitrator of these new
×
UNCOV
294
        // channels so we'll properly watch for their ultimate closure on chain
×
UNCOV
295
        // and sweep them via the DLP.
×
UNCOV
296
        for _, restoredChannel := range channelShells {
×
UNCOV
297
                err := c.chainArb.WatchNewChannel(restoredChannel.Chan)
×
UNCOV
298
                if err != nil {
×
299
                        return err
×
300
                }
×
301

UNCOV
302
                chanPoints = append(
×
UNCOV
303
                        chanPoints, restoredChannel.Chan.FundingOutpoint,
×
UNCOV
304
                )
×
305
        }
306

307
        // With all the channels restored, we'll now re-send the blockbeat.
UNCOV
308
        c.chainArb.RedispatchBlockbeat(chanPoints)
×
UNCOV
309

×
UNCOV
310
        return nil
×
311
}
312

313
// A compile-time constraint to ensure chanDBRestorer implements
314
// chanbackup.ChannelRestorer.
315
var _ chanbackup.ChannelRestorer = (*chanDBRestorer)(nil)
316

317
// ConnectPeer attempts to connect to the target node at the set of available
318
// addresses. Once this method returns with a non-nil error, the connector
319
// should attempt to persistently connect to the target peer in the background
320
// as a persistent attempt.
321
//
322
// NOTE: Part of the chanbackup.PeerConnector interface.
UNCOV
323
func (s *server) ConnectPeer(nodePub *btcec.PublicKey, addrs []net.Addr) error {
×
UNCOV
324
        // Before we connect to the remote peer, we'll remove any connections
×
UNCOV
325
        // to ensure the new connection is created after this new link/channel
×
UNCOV
326
        // is known.
×
UNCOV
327
        if err := s.DisconnectPeer(nodePub); err != nil {
×
UNCOV
328
                ltndLog.Infof("Peer(%x) is already connected, proceeding "+
×
UNCOV
329
                        "with chan restore", nodePub.SerializeCompressed())
×
UNCOV
330
        }
×
331

332
        // For each of the known addresses, we'll attempt to launch a
333
        // persistent connection to the (pub, addr) pair. In the event that any
334
        // of them connect, all the other stale requests will be canceled.
UNCOV
335
        for _, addr := range addrs {
×
UNCOV
336
                netAddr := &lnwire.NetAddress{
×
UNCOV
337
                        IdentityKey: nodePub,
×
UNCOV
338
                        Address:     addr,
×
UNCOV
339
                }
×
UNCOV
340

×
UNCOV
341
                ltndLog.Infof("Attempting to connect to %v for SCB restore "+
×
UNCOV
342
                        "DLP", netAddr)
×
UNCOV
343

×
UNCOV
344
                // Attempt to connect to the peer using this full address. If
×
UNCOV
345
                // we're unable to connect to them, then we'll try the next
×
UNCOV
346
                // address in place of it.
×
UNCOV
347
                err := s.ConnectToPeer(netAddr, true, s.cfg.ConnectionTimeout)
×
UNCOV
348

×
UNCOV
349
                // If we're already connected to this peer, then we don't
×
UNCOV
350
                // consider this an error, so we'll exit here.
×
UNCOV
351
                if _, ok := err.(*errPeerAlreadyConnected); ok {
×
352
                        return nil
×
353

×
UNCOV
354
                } else if err != nil {
×
355
                        // Otherwise, something else happened, so we'll try the
×
356
                        // next address.
×
357
                        ltndLog.Errorf("unable to connect to %v to "+
×
358
                                "complete SCB restore: %v", netAddr, err)
×
359
                        continue
×
360
                }
361

362
                // If we connected no problem, then we can exit early as our
363
                // job here is done.
UNCOV
364
                return nil
×
365
        }
366

367
        return fmt.Errorf("unable to connect to peer %x for SCB restore",
×
368
                nodePub.SerializeCompressed())
×
369
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc