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

lightningnetwork / lnd / 12343072627

15 Dec 2024 11:09PM UTC coverage: 57.504% (-1.1%) from 58.636%
12343072627

Pull #9315

github

yyforyongyu
contractcourt: offer outgoing htlc one block earlier before its expiry

We need to offer the outgoing htlc one block earlier to make sure when
the expiry height hits, the sweeper will not miss sweeping it in the
same block. This also means the outgoing contest resolver now only does
one thing - watch for preimage spend till height expiry-1, which can
easily be moved into the timeout resolver instead in the future.
Pull Request #9315: Implement `blockbeat`

1445 of 2007 new or added lines in 26 files covered. (72.0%)

19246 existing lines in 249 files now uncovered.

102342 of 177975 relevant lines covered (57.5%)

24772.24 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) (
UNCOV
49
        *channeldb.ChannelShell, error) {
×
UNCOV
50

×
UNCOV
51
        var err error
×
UNCOV
52

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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