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

lightningnetwork / lnd / 12312390362

13 Dec 2024 08:44AM UTC coverage: 57.458% (+8.5%) from 48.92%
12312390362

Pull #9343

github

ellemouton
fn: rework the ContextGuard and add tests

In this commit, the ContextGuard struct is re-worked such that the
context that its new main WithCtx method provides is cancelled in sync
with a parent context being cancelled or with it's quit channel being
cancelled. Tests are added to assert the behaviour. In order for the
close of the quit channel to be consistent with the cancelling of the
derived context, the quit channel _must_ be contained internal to the
ContextGuard so that callers are only able to close the channel via the
exposed Quit method which will then take care to first cancel any
derived context that depend on the quit channel before returning.
Pull Request #9343: fn: expand the ContextGuard and add tests

101853 of 177264 relevant lines covered (57.46%)

24972.93 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/lightningnetwork/lnd/chanbackup"
12
        "github.com/lightningnetwork/lnd/channeldb"
13
        "github.com/lightningnetwork/lnd/contractcourt"
14
        "github.com/lightningnetwork/lnd/keychain"
15
        "github.com/lightningnetwork/lnd/lnwire"
16
        "github.com/lightningnetwork/lnd/shachain"
17
)
18

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

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

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

39
        secretKeys keychain.SecretKeyRing
40

41
        chainArb *contractcourt.ChainArbitrator
42
}
43

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

×
51
        var err error
×
52

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
199
        return &chanShell, nil
×
200
}
201

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

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

223
                channelShells = append(channelShells, chanShell)
×
224
        }
225

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

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

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

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

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

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

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

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

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

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

299
        return nil
×
300
}
301

302
// A compile-time constraint to ensure chanDBRestorer implements
303
// chanbackup.ChannelRestorer.
304
var _ chanbackup.ChannelRestorer = (*chanDBRestorer)(nil)
305

306
// ConnectPeer attempts to connect to the target node at the set of available
307
// addresses. Once this method returns with a non-nil error, the connector
308
// should attempt to persistently connect to the target peer in the background
309
// as a persistent attempt.
310
//
311
// NOTE: Part of the chanbackup.PeerConnector interface.
312
func (s *server) ConnectPeer(nodePub *btcec.PublicKey, addrs []net.Addr) error {
×
313
        // Before we connect to the remote peer, we'll remove any connections
×
314
        // to ensure the new connection is created after this new link/channel
×
315
        // is known.
×
316
        if err := s.DisconnectPeer(nodePub); err != nil {
×
317
                ltndLog.Infof("Peer(%v) is already connected, proceeding "+
×
318
                        "with chan restore", nodePub.SerializeCompressed())
×
319
        }
×
320

321
        // For each of the known addresses, we'll attempt to launch a
322
        // persistent connection to the (pub, addr) pair. In the event that any
323
        // of them connect, all the other stale requests will be canceled.
324
        for _, addr := range addrs {
×
325
                netAddr := &lnwire.NetAddress{
×
326
                        IdentityKey: nodePub,
×
327
                        Address:     addr,
×
328
                }
×
329

×
330
                ltndLog.Infof("Attempting to connect to %v for SCB restore "+
×
331
                        "DLP", netAddr)
×
332

×
333
                // Attempt to connect to the peer using this full address. If
×
334
                // we're unable to connect to them, then we'll try the next
×
335
                // address in place of it.
×
336
                err := s.ConnectToPeer(netAddr, true, s.cfg.ConnectionTimeout)
×
337

×
338
                // If we're already connected to this peer, then we don't
×
339
                // consider this an error, so we'll exit here.
×
340
                if _, ok := err.(*errPeerAlreadyConnected); ok {
×
341
                        return nil
×
342

×
343
                } else if err != nil {
×
344
                        // Otherwise, something else happened, so we'll try the
×
345
                        // next address.
×
346
                        ltndLog.Errorf("unable to connect to %v to "+
×
347
                                "complete SCB restore: %v", netAddr, err)
×
348
                        continue
×
349
                }
350

351
                // If we connected no problem, then we can exit early as our
352
                // job here is done.
353
                return nil
×
354
        }
355

356
        return fmt.Errorf("unable to connect to peer %x for SCB restore",
×
357
                nodePub.SerializeCompressed())
×
358
}
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