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

lightningnetwork / lnd / 12583319996

02 Jan 2025 01:38PM UTC coverage: 57.522% (-1.1%) from 58.598%
12583319996

Pull #9361

github

starius
fn/ContextGuard: use context.AfterFunc to wait

Simplifies context cancellation handling by using context.AfterFunc instead of a
goroutine to wait for context cancellation. This approach avoids the overhead of
a goroutine during the waiting period.

For ctxQuitUnsafe, since g.quit is closed only in the Quit method (which also
cancels all associated contexts), waiting on context cancellation ensures the
same behavior without unnecessary dependency on g.quit.

Added a test to ensure that the Create method does not launch any goroutines.
Pull Request #9361: fn: optimize context guard

102587 of 178344 relevant lines covered (57.52%)

24734.33 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) (
50
        *channeldb.ChannelShell, error) {
×
51

×
52
        var err error
×
53

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

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

×
100
                // Obtain the private key for the shachain root from the
×
101
                // encoded public key.
×
102
                privKey, err := c.secretKeys.DerivePrivKey(
×
103
                        backup.ShaChainRootDesc,
×
104
                )
×
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

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

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

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

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

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

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

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

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

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

160
        case chanbackup.SimpleTaprootVersion:
×
161
                chanType = channeldb.ZeroHtlcTxFeeBit
×
162
                chanType |= channeldb.AnchorOutputsBit
×
163
                chanType |= channeldb.SingleFunderTweaklessBit
×
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

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

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

×
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.
209
func (c *chanDBRestorer) RestoreChansFromSingles(backups ...chanbackup.Single) error {
×
210
        channelShells := make([]*channeldb.ChannelShell, 0, len(backups))
×
211
        firstChanHeight := uint32(math.MaxUint32)
×
212
        for _, backup := range backups {
×
213
                chanShell, err := c.openChannelShell(backup)
×
214
                if err != nil {
×
215
                        return err
×
216
                }
×
217

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

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.
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.
252
        for _, chanShell := range channelShells {
×
253
                channel := chanShell.Chan
×
254

×
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.
272
                case channel.ShortChannelID.TxIndex == 0:
×
273
                        broadcastHeight := channel.ShortChannelID.BlockHeight
×
274
                        channel.SetBroadcastHeight(broadcastHeight)
×
275
                        channel.ShortChannelID.BlockHeight = 0
×
276
                }
277
        }
278

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

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

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

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

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

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

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

×
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.
323
func (s *server) ConnectPeer(nodePub *btcec.PublicKey, addrs []net.Addr) error {
×
324
        // Before we connect to the remote peer, we'll remove any connections
×
325
        // to ensure the new connection is created after this new link/channel
×
326
        // is known.
×
327
        if err := s.DisconnectPeer(nodePub); err != nil {
×
328
                ltndLog.Infof("Peer(%x) is already connected, proceeding "+
×
329
                        "with chan restore", nodePub.SerializeCompressed())
×
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.
335
        for _, addr := range addrs {
×
336
                netAddr := &lnwire.NetAddress{
×
337
                        IdentityKey: nodePub,
×
338
                        Address:     addr,
×
339
                }
×
340

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

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

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

×
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.
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