github.com/decred/dcrlnd@v0.7.6/chanrestore.go (about) 1 package dcrlnd 2 3 import ( 4 "fmt" 5 "math" 6 "net" 7 8 "github.com/decred/dcrd/chaincfg/chainhash" 9 "github.com/decred/dcrd/chaincfg/v3" 10 "github.com/decred/dcrd/dcrec/secp256k1/v4" 11 "github.com/decred/dcrlnd/chanbackup" 12 "github.com/decred/dcrlnd/channeldb" 13 "github.com/decred/dcrlnd/contractcourt" 14 "github.com/decred/dcrlnd/keychain" 15 "github.com/decred/dcrlnd/lnwire" 16 "github.com/decred/dcrlnd/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: %v", err) 61 } 62 backup.LocalChanCfg.RevocationBasePoint, err = c.secretKeys.DeriveKey( 63 backup.LocalChanCfg.RevocationBasePoint.KeyLocator, 64 ) 65 if err != nil { 66 return nil, fmt.Errorf("unable to derive revocation key: %v", err) 67 } 68 backup.LocalChanCfg.PaymentBasePoint, err = c.secretKeys.DeriveKey( 69 backup.LocalChanCfg.PaymentBasePoint.KeyLocator, 70 ) 71 if err != nil { 72 return nil, fmt.Errorf("unable to derive payment key: %v", err) 73 } 74 backup.LocalChanCfg.DelayBasePoint, err = c.secretKeys.DeriveKey( 75 backup.LocalChanCfg.DelayBasePoint.KeyLocator, 76 ) 77 if err != nil { 78 return nil, fmt.Errorf("unable to derive delay key: %v", err) 79 } 80 backup.LocalChanCfg.HtlcBasePoint, err = c.secretKeys.DeriveKey( 81 backup.LocalChanCfg.HtlcBasePoint.KeyLocator, 82 ) 83 if err != nil { 84 return nil, fmt.Errorf("unable to derive htlc key: %v", err) 85 } 86 87 // The shachain root that seeds RevocationProducer for this channel. 88 // It currently has two possible formats. 89 var revRoot *chainhash.Hash 90 91 // If the PubKey field is non-nil, then this shachain root is using the 92 // legacy non-ECDH scheme. 93 if backup.ShaChainRootDesc.PubKey != nil { 94 ltndLog.Debugf("Using legacy revocation producer format for "+ 95 "channel point %v", backup.FundingOutpoint) 96 97 // Obtain the private key for the shachain root from the 98 // encoded public key. 99 privKey, err := c.secretKeys.DerivePrivKey( 100 backup.ShaChainRootDesc, 101 ) 102 if err != nil { 103 return nil, fmt.Errorf("could not derive private key "+ 104 "for legacy channel revocation root format: "+ 105 "%v", err) 106 } 107 108 revRoot, err = chainhash.NewHash(privKey.Serialize()) 109 if err != nil { 110 return nil, err 111 } 112 } else { 113 ltndLog.Debugf("Using new ECDH revocation producer format "+ 114 "for channel point %v", backup.FundingOutpoint) 115 116 // This is the scheme in which the shachain root is derived via 117 // an ECDH operation on the private key of ShaChainRootDesc and 118 // our public multisig key. 119 ecdh, err := c.secretKeys.ECDH( 120 backup.ShaChainRootDesc, 121 backup.LocalChanCfg.MultiSigKey.PubKey, 122 ) 123 if err != nil { 124 return nil, fmt.Errorf("unable to derive shachain "+ 125 "root: %v", err) 126 } 127 128 ch := chainhash.Hash(ecdh) 129 revRoot = &ch 130 } 131 132 shaChainProducer := shachain.NewRevocationProducer(shachain.ShaHash(*revRoot)) 133 134 var chanType channeldb.ChannelType 135 switch backup.Version { 136 137 case chanbackup.DefaultSingleVersion: 138 chanType = channeldb.SingleFunderBit 139 140 case chanbackup.TweaklessCommitVersion: 141 chanType = channeldb.SingleFunderTweaklessBit 142 143 case chanbackup.AnchorsCommitVersion: 144 chanType = channeldb.AnchorOutputsBit 145 chanType |= channeldb.SingleFunderTweaklessBit 146 147 case chanbackup.AnchorsZeroFeeHtlcTxCommitVersion: 148 chanType = channeldb.ZeroHtlcTxFeeBit 149 chanType |= channeldb.AnchorOutputsBit 150 chanType |= channeldb.SingleFunderTweaklessBit 151 152 case chanbackup.ScriptEnforcedLeaseVersion: 153 chanType = channeldb.LeaseExpirationBit 154 chanType |= channeldb.ZeroHtlcTxFeeBit 155 chanType |= channeldb.AnchorOutputsBit 156 chanType |= channeldb.SingleFunderTweaklessBit 157 158 default: 159 return nil, fmt.Errorf("unknown Single version: %v", err) 160 } 161 162 ltndLog.Infof("SCB Recovery: created channel shell for ChannelPoint"+ 163 "(%v), chan_type=%v", backup.FundingOutpoint, chanType) 164 165 chanShell := channeldb.ChannelShell{ 166 NodeAddrs: backup.Addresses, 167 Chan: &channeldb.OpenChannel{ 168 ChanType: chanType, 169 ChainHash: backup.ChainHash, 170 IsInitiator: backup.IsInitiator, 171 Capacity: backup.Capacity, 172 FundingOutpoint: backup.FundingOutpoint, 173 ShortChannelID: backup.ShortChannelID, 174 IdentityPub: backup.RemoteNodePub, 175 IsPending: false, 176 LocalChanCfg: backup.LocalChanCfg, 177 RemoteChanCfg: backup.RemoteChanCfg, 178 RemoteCurrentRevocation: backup.RemoteNodePub, 179 RevocationStore: shachain.NewRevocationStore(), 180 RevocationProducer: shaChainProducer, 181 ThawHeight: backup.LeaseExpiry, 182 }, 183 } 184 185 return &chanShell, nil 186 } 187 188 // RestoreChansFromSingles attempts to map the set of single channel backups to 189 // channel shells that will be stored persistently. Once these shells have been 190 // stored on disk, we'll be able to connect to the channel peer an execute the 191 // data loss recovery protocol. 192 // 193 // NOTE: Part of the chanbackup.ChannelRestorer interface. 194 func (c *chanDBRestorer) RestoreChansFromSingles(backups ...chanbackup.Single) error { 195 channelShells := make([]*channeldb.ChannelShell, 0, len(backups)) 196 firstChanHeight := uint32(math.MaxUint32) 197 for _, backup := range backups { 198 chanShell, err := c.openChannelShell(backup) 199 if err != nil { 200 return err 201 } 202 203 // Find the block height of the earliest channel in this backup. 204 chanHeight := chanShell.Chan.ShortChanID().BlockHeight 205 if chanHeight != 0 && chanHeight < firstChanHeight { 206 firstChanHeight = chanHeight 207 } 208 209 channelShells = append(channelShells, chanShell) 210 } 211 212 // In case there were only unconfirmed channels, we will have to scan 213 // the chain beginning from the launch date of SCBs. 214 if firstChanHeight == math.MaxUint32 { 215 chainHash := channelShells[0].Chan.ChainHash 216 switch { 217 case chainHash.IsEqual(&chaincfg.MainNetParams().GenesisHash): 218 firstChanHeight = mainnetSCBLaunchBlock 219 220 case chainHash.IsEqual(&chaincfg.TestNet3Params().GenesisHash): 221 firstChanHeight = testnetSCBLaunchBlock 222 223 default: 224 // Worst case: We have no height hint and start at 225 // block 1. Should only happen for SCBs in regtest, 226 // simnet and litecoin. 227 firstChanHeight = 1 228 } 229 } 230 231 // If there were channels in the backup that were not confirmed at the 232 // time of the backup creation, they won't have a block height in the 233 // ShortChanID which would lead to an error in the chain watcher. 234 // We want to at least set the funding broadcast height that the chain 235 // watcher can use instead. We have two possible fallback values for 236 // the broadcast height that we are going to try here. 237 for _, chanShell := range channelShells { 238 channel := chanShell.Chan 239 240 switch { 241 // Fallback case 1: This is an unconfirmed channel from an old 242 // backup file where we didn't have any workaround in place and 243 // the short channel ID is 0:0:0. Best we can do here is set the 244 // funding broadcast height to a reasonable value that we 245 // determined earlier. 246 case channel.ShortChanID().BlockHeight == 0: 247 channel.FundingBroadcastHeight = firstChanHeight 248 249 // Fallback case 2: It is extremely unlikely at this point that 250 // a channel we are trying to restore has a coinbase funding TX. 251 // Therefore we can be quite certain that if the TxIndex is 252 // zero but the block height wasn't, it was an unconfirmed 253 // channel where we used the BlockHeight to encode the funding 254 // TX broadcast height. To not end up with an invalid short 255 // channel ID that looks valid, we restore the "original" 256 // unconfirmed one here. 257 case channel.ShortChannelID.TxIndex == 0: 258 broadcastHeight := channel.ShortChannelID.BlockHeight 259 channel.FundingBroadcastHeight = broadcastHeight 260 channel.ShortChannelID.BlockHeight = 0 261 } 262 } 263 264 ltndLog.Infof("Inserting %v SCB channel shells into DB", 265 len(channelShells)) 266 267 // Now that we have all the backups mapped into a series of Singles, 268 // we'll insert them all into the database. 269 if err := c.db.RestoreChannelShells(channelShells...); err != nil { 270 return err 271 } 272 273 ltndLog.Infof("Informing chain watchers of new restored channels") 274 275 // Finally, we'll need to inform the chain arbitrator of these new 276 // channels so we'll properly watch for their ultimate closure on chain 277 // and sweep them via the DLP. 278 for _, restoredChannel := range channelShells { 279 err := c.chainArb.WatchNewChannel(restoredChannel.Chan) 280 if err != nil { 281 return err 282 } 283 } 284 285 return nil 286 } 287 288 // A compile-time constraint to ensure chanDBRestorer implements 289 // chanbackup.ChannelRestorer. 290 var _ chanbackup.ChannelRestorer = (*chanDBRestorer)(nil) 291 292 // ConnectPeer attempts to connect to the target node at the set of available 293 // addresses. Once this method returns with a non-nil error, the connector 294 // should attempt to persistently connect to the target peer in the background 295 // as a persistent attempt. 296 // 297 // NOTE: Part of the chanbackup.PeerConnector interface. 298 func (s *server) ConnectPeer(nodePub *secp256k1.PublicKey, addrs []net.Addr) error { 299 // Before we connect to the remote peer, we'll remove any connections 300 // to ensure the new connection is created after this new link/channel 301 // is known. 302 if err := s.DisconnectPeer(nodePub); err != nil { 303 ltndLog.Infof("Peer(%v) is already connected, proceeding "+ 304 "with chan restore", nodePub.SerializeCompressed()) 305 } 306 307 // For each of the known addresses, we'll attempt to launch a 308 // persistent connection to the (pub, addr) pair. In the event that any 309 // of them connect, all the other stale requests will be canceled. 310 for _, addr := range addrs { 311 netAddr := &lnwire.NetAddress{ 312 IdentityKey: nodePub, 313 Address: addr, 314 } 315 316 ltndLog.Infof("Attempting to connect to %v for SCB restore "+ 317 "DLP", netAddr) 318 319 // Attempt to connect to the peer using this full address. If 320 // we're unable to connect to them, then we'll try the next 321 // address in place of it. 322 err := s.ConnectToPeer(netAddr, true, s.cfg.ConnectionTimeout) 323 324 // If we're already connected to this peer, then we don't 325 // consider this an error, so we'll exit here. 326 if _, ok := err.(*errPeerAlreadyConnected); ok { 327 return nil 328 329 } else if err != nil { 330 // Otherwise, something else happened, so we'll try the 331 // next address. 332 ltndLog.Errorf("unable to connect to %v to "+ 333 "complete SCB restore: %v", netAddr, err) 334 continue 335 } 336 337 // If we connected no problem, then we can exit early as our 338 // job here is done. 339 return nil 340 } 341 342 return fmt.Errorf("unable to connect to peer %x for SCB restore", 343 nodePub.SerializeCompressed()) 344 }