package migration30 import ( "bytes" "fmt" "math/rand" "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" mig25 "github.com/lightningnetwork/lnd/channeldb/migration25" mig26 "github.com/lightningnetwork/lnd/channeldb/migration26" mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/shachain" ) var ( testChainHash = chainhash.Hash{1, 2, 3} testChanType = mig25.SingleFunderTweaklessBit // testOurIndex and testTheirIndex are artificial indexes that're saved // to db during test setup. They are different from indexes populated // from the actual migration process so we can check whether a new // revocation log is overwritten or not. testOurIndex = uint32(100) testTheirIndex = uint32(200) // dummyInput is used in our commit tx. dummyInput = &wire.TxIn{ PreviousOutPoint: wire.OutPoint{ Hash: chainhash.Hash{}, Index: 0xffffffff, }, Sequence: 0xffffffff, } // htlcScript is the PkScript used in the HTLC output. This script // corresponds to revocation preimage2. htlcScript = []byte{ 0x0, 0x20, 0x3d, 0x51, 0x66, 0xda, 0x39, 0x93, 0x7b, 0x49, 0xaf, 0x2, 0xf2, 0x2f, 0x90, 0x52, 0x8e, 0x45, 0x24, 0x34, 0x8f, 0xd8, 0x76, 0x7, 0x5a, 0xfc, 0x52, 0x8d, 0x68, 0xdd, 0xbc, 0xce, 0x3e, 0x5d, } // toLocalScript is the PkScript used in to-local output. toLocalScript = []byte{ 0x0, 0x14, 0xc6, 0x9, 0x62, 0xab, 0x60, 0xbe, 0x40, 0xd, 0xab, 0x31, 0xc, 0x13, 0x14, 0x15, 0x93, 0xe6, 0xa2, 0x94, 0xe4, 0x2a, } // preimage1 defines a revocation preimage, generated from itest. preimage1 = []byte{ 0x95, 0xb4, 0x7c, 0x5a, 0x2b, 0xfd, 0x6f, 0xf4, 0x70, 0x8, 0xc, 0x70, 0x82, 0x36, 0xc8, 0x5, 0x88, 0x16, 0xaf, 0x29, 0xb5, 0x8, 0xfd, 0x5a, 0x40, 0x28, 0x24, 0xc, 0x2a, 0x7f, 0x96, 0xcd, } // commitTx1 is the tx saved in the first old revocation. commitTx1 = &wire.MsgTx{ Version: 2, // Add a dummy input. TxIn: []*wire.TxIn{dummyInput}, TxOut: []*wire.TxOut{ { Value: 990_950, PkScript: toLocalScript, }, }, } // logHeight1 is the CommitHeight used by oldLog1. logHeight1 = uint64(0) // localBalance1 is the LocalBalance used in oldLog1. localBalance1 = lnwire.MilliSatoshi(990_950_000) // remoteBalance1 is the RemoteBalance used in oldLog1. remoteBalance1 = lnwire.MilliSatoshi(0) // oldLog1 defines an old revocation that has no HTLCs. oldLog1 = mig.ChannelCommitment{ CommitHeight: logHeight1, LocalLogIndex: 0, LocalHtlcIndex: 0, RemoteLogIndex: 0, RemoteHtlcIndex: 0, LocalBalance: localBalance1, RemoteBalance: remoteBalance1, CommitTx: commitTx1, } // newLog1 is the new version of oldLog1 in the case were we don't want // to store any balance data. newLog1 = RevocationLog{ OurOutputIndex: 0, TheirOutputIndex: OutputIndexEmpty, CommitTxHash: commitTx1.TxHash(), } // newLog1WithAmts is the new version of oldLog1 in the case were we do // want to store balance data. newLog1WithAmts = RevocationLog{ OurOutputIndex: 0, TheirOutputIndex: OutputIndexEmpty, CommitTxHash: commitTx1.TxHash(), OurBalance: &localBalance1, TheirBalance: &remoteBalance1, } // preimage2 defines the second revocation preimage used in the test, // generated from itest. preimage2 = []byte{ 0xac, 0x60, 0x7a, 0x59, 0x9, 0xd6, 0x11, 0xb2, 0xf5, 0x6e, 0xaa, 0xc6, 0xb9, 0x0, 0x12, 0xdc, 0xf0, 0x89, 0x58, 0x90, 0x8a, 0xa2, 0xc6, 0xfc, 0xf1, 0x2, 0x74, 0x87, 0x30, 0x51, 0x5e, 0xea, } // commitTx2 is the tx saved in the second old revocation. commitTx2 = &wire.MsgTx{ Version: 2, // Add a dummy input. TxIn: []*wire.TxIn{dummyInput}, TxOut: []*wire.TxOut{ { Value: 100_000, PkScript: htlcScript, }, { Value: 888_800, PkScript: toLocalScript, }, }, } // rHash is the payment hash used in the htlc below. rHash = [32]byte{ 0x42, 0x5e, 0xd4, 0xe4, 0xa3, 0x6b, 0x30, 0xea, 0x21, 0xb9, 0xe, 0x21, 0xc7, 0x12, 0xc6, 0x49, 0xe8, 0x21, 0x4c, 0x29, 0xb7, 0xea, 0xf6, 0x80, 0x89, 0xd1, 0x3, 0x9c, 0x6e, 0x55, 0x38, 0x4c, } // htlc defines an HTLC that's saved in the old revocation log. htlc = mig.HTLC{ RHash: rHash, Amt: lnwire.MilliSatoshi(100_000_000), RefundTimeout: 489, OutputIndex: 0, Incoming: false, OnionBlob: bytes.Repeat([]byte{0xff}, 1366), HtlcIndex: 0, LogIndex: 0, } // logHeight2 is the CommitHeight used by oldLog2. logHeight2 = uint64(1) // localBalance2 is the LocalBalance used in oldLog2. localBalance2 = lnwire.MilliSatoshi(888_800_000) // remoteBalance2 is the RemoteBalance used in oldLog2. remoteBalance2 = lnwire.MilliSatoshi(0) // oldLog2 defines an old revocation that has one HTLC. oldLog2 = mig.ChannelCommitment{ CommitHeight: logHeight2, LocalLogIndex: 1, LocalHtlcIndex: 1, RemoteLogIndex: 0, RemoteHtlcIndex: 0, LocalBalance: localBalance2, RemoteBalance: remoteBalance2, CommitTx: commitTx2, Htlcs: []mig.HTLC{htlc}, } // newLog2 is the new version of the oldLog2 in the case were we don't // want to store any balance data. newLog2 = RevocationLog{ OurOutputIndex: 1, TheirOutputIndex: OutputIndexEmpty, CommitTxHash: commitTx2.TxHash(), HTLCEntries: []*HTLCEntry{ { RHash: rHash, RefundTimeout: 489, OutputIndex: 0, Incoming: false, Amt: btcutil.Amount(100_000), }, }, } // newLog2WithAmts is the new version of oldLog2 in the case were we do // want to store balance data. newLog2WithAmts = RevocationLog{ OurOutputIndex: 1, TheirOutputIndex: OutputIndexEmpty, CommitTxHash: commitTx2.TxHash(), OurBalance: &localBalance2, TheirBalance: &remoteBalance2, HTLCEntries: []*HTLCEntry{ { RHash: rHash, RefundTimeout: 489, OutputIndex: 0, Incoming: false, Amt: btcutil.Amount(100_000), }, }, } // newLog3 defines an revocation log that's been created after v0.15.0. newLog3 = mig.ChannelCommitment{ CommitHeight: logHeight2 + 1, LocalLogIndex: 1, LocalHtlcIndex: 1, RemoteLogIndex: 0, RemoteHtlcIndex: 0, LocalBalance: lnwire.MilliSatoshi(888_800_000), RemoteBalance: 0, CommitTx: commitTx2, Htlcs: []mig.HTLC{htlc}, } // The following public keys are taken from the itest results. localMusigKey, _ = btcec.ParsePubKey([]byte{ 0x2, 0xda, 0x42, 0xa4, 0x4a, 0x6b, 0x42, 0xfe, 0xcb, 0x2f, 0x7e, 0x35, 0x89, 0x99, 0xdd, 0x43, 0xba, 0x4b, 0xf1, 0x9c, 0xf, 0x18, 0xef, 0x9, 0x83, 0x35, 0x31, 0x59, 0xa4, 0x3b, 0xde, 0xa, 0xde, }) localRevocationBasePoint, _ = btcec.ParsePubKey([]byte{ 0x2, 0x6, 0x16, 0xd1, 0xb1, 0x4f, 0xee, 0x11, 0x86, 0x55, 0xfe, 0x31, 0x66, 0x6f, 0x43, 0x1, 0x80, 0xa8, 0xa7, 0x5c, 0x2, 0x92, 0xe5, 0x7c, 0x4, 0x31, 0xa6, 0xcf, 0x43, 0xb6, 0xdb, 0xe6, 0x10, }) localPaymentBasePoint, _ = btcec.ParsePubKey([]byte{ 0x2, 0x88, 0x65, 0x16, 0xc2, 0x37, 0x3f, 0xc5, 0x16, 0x62, 0x71, 0x0, 0xdd, 0x4d, 0x43, 0x28, 0x43, 0x32, 0x91, 0x75, 0xcc, 0xd8, 0x81, 0xb6, 0xb0, 0xd8, 0x96, 0x78, 0xad, 0x18, 0x3b, 0x16, 0xe1, }) localDelayBasePoint, _ = btcec.ParsePubKey([]byte{ 0x2, 0xea, 0x41, 0x48, 0x11, 0x2, 0x59, 0xe3, 0x5c, 0x51, 0x15, 0x90, 0x25, 0x4a, 0x61, 0x5, 0x51, 0xb3, 0x8, 0xe9, 0xd5, 0xf, 0xc6, 0x91, 0x25, 0x14, 0xd2, 0xcf, 0xc8, 0xc5, 0x5b, 0xd9, 0x88, }) localHtlcBasePoint, _ = btcec.ParsePubKey([]byte{ 0x3, 0xfa, 0x1f, 0x6, 0x3a, 0xa4, 0x75, 0x2e, 0x74, 0x3e, 0x55, 0x9, 0x20, 0x6e, 0xf6, 0xa8, 0xe1, 0xd7, 0x61, 0x50, 0x75, 0xa8, 0x34, 0x15, 0xc3, 0x6b, 0xdc, 0xb0, 0xbf, 0xaa, 0x66, 0xd7, 0xa7, }) remoteMultiSigKey, _ = btcec.ParsePubKey([]byte{ 0x2, 0x2b, 0x88, 0x7c, 0x6a, 0xf8, 0xb3, 0x51, 0x61, 0xd3, 0x1c, 0xf1, 0xe4, 0x43, 0xc2, 0x8c, 0x5e, 0xfa, 0x8e, 0xb5, 0xe9, 0xd0, 0x14, 0xb5, 0x33, 0x6a, 0xcc, 0xd, 0x11, 0x42, 0xb8, 0x4b, 0x7d, }) remoteRevocationBasePoint, _ = btcec.ParsePubKey([]byte{ 0x2, 0x6c, 0x39, 0xa3, 0x6d, 0x93, 0x69, 0xac, 0x14, 0x1f, 0xbb, 0x4, 0x86, 0x3, 0x82, 0x5, 0xe2, 0xcb, 0xb0, 0x62, 0x41, 0xa, 0x93, 0x3, 0x6c, 0x8d, 0xc0, 0x42, 0x4d, 0x9e, 0x51, 0x9b, 0x36, }) remotePaymentBasePoint, _ = btcec.ParsePubKey([]byte{ 0x3, 0xab, 0x74, 0x1e, 0x83, 0x48, 0xe3, 0xb5, 0x6, 0x25, 0x1c, 0x80, 0xe7, 0xf2, 0x3e, 0x7d, 0xb7, 0x7a, 0xc7, 0xd, 0x6, 0x3b, 0xbc, 0x74, 0x96, 0x8e, 0x9b, 0x2d, 0xd1, 0x42, 0x71, 0xa5, 0x2a, }) remoteDelayBasePoint, _ = btcec.ParsePubKey([]byte{ 0x2, 0x4b, 0xdd, 0x52, 0x46, 0x1b, 0x50, 0x89, 0xb9, 0x49, 0x4, 0xf2, 0xd2, 0x98, 0x7d, 0x51, 0xa1, 0xa6, 0x3f, 0x9b, 0xd0, 0x40, 0x7c, 0x93, 0x74, 0x3b, 0x8c, 0x4d, 0x63, 0x32, 0x90, 0xa, 0xca, }) remoteHtlcBasePoint, _ = btcec.ParsePubKey([]byte{ 0x3, 0x5b, 0x8f, 0x4a, 0x71, 0x4c, 0x2e, 0x71, 0x14, 0x86, 0x1f, 0x30, 0x96, 0xc0, 0xd4, 0x11, 0x76, 0xf8, 0xc3, 0xfc, 0x7, 0x2d, 0x15, 0x99, 0x55, 0x8, 0x69, 0xf6, 0x1, 0xa2, 0xcd, 0x6b, 0xa7, }) // withAmtData if set, will result in the amount data of the revoked // commitment transactions also being stored in the new revocation log. // The value of this variable is set randomly in the init function of // this package. withAmtData bool ) // setupTestLogs takes care of creating the related buckets and inserts testing // records. func setupTestLogs(db kvdb.Backend, c *mig26.OpenChannel, oldLogs, newLogs []mig.ChannelCommitment) error { return kvdb.Update(db, func(tx kvdb.RwTx) error { // If the open channel is nil, only create the root // bucket and skip creating the channel bucket. if c == nil { _, err := tx.CreateTopLevelBucket(openChannelBucket) return err } // Create test buckets. chanBucket, err := mig25.CreateChanBucket(tx, &c.OpenChannel) if err != nil { return err } // Save channel info. if err := mig26.PutChanInfo(chanBucket, c, false); err != nil { return fmt.Errorf("PutChanInfo got %w", err) } // Save revocation state. if err := putChanRevocationState(chanBucket, c); err != nil { return fmt.Errorf("putChanRevocationState got %w", err) } // Create old logs. err = writeOldRevocationLogs(chanBucket, oldLogs) if err != nil { return fmt.Errorf("write old logs: %w", err) } // Create new logs. return writeNewRevocationLogs(chanBucket, newLogs, !withAmtData) }, func() {}) } // createTestChannel creates an OpenChannel using the specified nodePub and // outpoint. If any of the params is nil, a random value is populated. func createTestChannel(nodePub *btcec.PublicKey) *mig26.OpenChannel { // Create a random private key that's used to provide randomness. priv, _ := btcec.NewPrivateKey() // If passed public key is nil, use the random public key. if nodePub == nil { nodePub = priv.PubKey() } // Create a random channel point. var op wire.OutPoint copy(op.Hash[:], priv.Serialize()) testProducer := shachain.NewRevocationProducer(op.Hash) store, _ := createTestStore() localCfg := mig.ChannelConfig{ ChannelConstraints: mig.ChannelConstraints{ DustLimit: btcutil.Amount(354), MaxAcceptedHtlcs: 483, CsvDelay: 4, }, MultiSigKey: keychain.KeyDescriptor{ KeyLocator: keychain.KeyLocator{ Family: 0, Index: 0, }, PubKey: localMusigKey, }, RevocationBasePoint: keychain.KeyDescriptor{ KeyLocator: keychain.KeyLocator{ Family: 1, Index: 0, }, PubKey: localRevocationBasePoint, }, HtlcBasePoint: keychain.KeyDescriptor{ KeyLocator: keychain.KeyLocator{ Family: 2, Index: 0, }, PubKey: localHtlcBasePoint, }, PaymentBasePoint: keychain.KeyDescriptor{ KeyLocator: keychain.KeyLocator{ Family: 3, Index: 0, }, PubKey: localPaymentBasePoint, }, DelayBasePoint: keychain.KeyDescriptor{ KeyLocator: keychain.KeyLocator{ Family: 4, Index: 0, }, PubKey: localDelayBasePoint, }, } remoteCfg := mig.ChannelConfig{ ChannelConstraints: mig.ChannelConstraints{ DustLimit: btcutil.Amount(354), MaxAcceptedHtlcs: 483, CsvDelay: 4, }, MultiSigKey: keychain.KeyDescriptor{ KeyLocator: keychain.KeyLocator{ Family: 0, Index: 0, }, PubKey: remoteMultiSigKey, }, RevocationBasePoint: keychain.KeyDescriptor{ KeyLocator: keychain.KeyLocator{ Family: 0, Index: 0, }, PubKey: remoteRevocationBasePoint, }, HtlcBasePoint: keychain.KeyDescriptor{ KeyLocator: keychain.KeyLocator{ Family: 0, Index: 0, }, PubKey: remoteHtlcBasePoint, }, PaymentBasePoint: keychain.KeyDescriptor{ KeyLocator: keychain.KeyLocator{ Family: 0, Index: 0, }, PubKey: remotePaymentBasePoint, }, DelayBasePoint: keychain.KeyDescriptor{ KeyLocator: keychain.KeyLocator{ Family: 0, Index: 0, }, PubKey: remoteDelayBasePoint, }, } c := &mig26.OpenChannel{ OpenChannel: mig25.OpenChannel{ OpenChannel: mig.OpenChannel{ ChainHash: testChainHash, IdentityPub: nodePub, FundingOutpoint: op, LocalChanCfg: localCfg, RemoteChanCfg: remoteCfg, // Assign dummy values. RemoteCurrentRevocation: nodePub, RevocationProducer: testProducer, RevocationStore: store, }, ChanType: testChanType, }, } return c } // writeOldRevocationLogs saves an old revocation log to db. func writeOldRevocationLogs(chanBucket kvdb.RwBucket, oldLogs []mig.ChannelCommitment) error { // Don't bother continue if the logs are empty. if len(oldLogs) == 0 { return nil } logBucket, err := chanBucket.CreateBucketIfNotExists( revocationLogBucketDeprecated, ) if err != nil { return err } for _, c := range oldLogs { if err := putOldRevocationLog(logBucket, &c); err != nil { return err } } return nil } // writeNewRevocationLogs saves a new revocation log to db. func writeNewRevocationLogs(chanBucket kvdb.RwBucket, oldLogs []mig.ChannelCommitment, noAmtData bool) error { // Don't bother continue if the logs are empty. if len(oldLogs) == 0 { return nil } logBucket, err := chanBucket.CreateBucketIfNotExists( revocationLogBucket, ) if err != nil { return err } for _, c := range oldLogs { // NOTE: we just blindly write the output indexes to db here // whereas normally, we would find the correct indexes from the // old commit tx. We do this intentionally so we can // distinguish a newly created log from an already saved one. err := putRevocationLog( logBucket, &c, testOurIndex, testTheirIndex, noAmtData, ) if err != nil { return err } } return nil } // createTestStore creates a revocation store and always saves the above // defined two preimages into the store. func createTestStore() (shachain.Store, error) { var p chainhash.Hash copy(p[:], preimage1) testStore := shachain.NewRevocationStore() if err := testStore.AddNextEntry(&p); err != nil { return nil, err } copy(p[:], preimage2) if err := testStore.AddNextEntry(&p); err != nil { return nil, err } return testStore, nil } // createNotStarted will setup a situation where we haven't started the // migration for the channel. We use the legacy to denote whether to simulate a // node with v0.15.0. func createNotStarted(cdb kvdb.Backend, c *mig26.OpenChannel, legacy bool) error { var newLogs []mig.ChannelCommitment // Create test logs. oldLogs := []mig.ChannelCommitment{oldLog1, oldLog2} // Add a new log if the node is running with v0.15.0. if !legacy { newLogs = []mig.ChannelCommitment{newLog3} } return setupTestLogs(cdb, c, oldLogs, newLogs) } // createNotFinished will setup a situation where we have un-migrated logs and // return the next migration height. We use the legacy to denote whether to // simulate a node with v0.15.0. func createNotFinished(cdb kvdb.Backend, c *mig26.OpenChannel, legacy bool) error { // Create test logs. oldLogs := []mig.ChannelCommitment{oldLog1, oldLog2} newLogs := []mig.ChannelCommitment{oldLog1} // Add a new log if the node is running with v0.15.0. if !legacy { newLogs = append(newLogs, newLog3) } return setupTestLogs(cdb, c, oldLogs, newLogs) } // createFinished will setup a situation where all the old logs have been // migrated and return a nil. We use the legacy to denote whether to simulate a // node with v0.15.0. func createFinished(cdb kvdb.Backend, c *mig26.OpenChannel, legacy bool) error { // Create test logs. oldLogs := []mig.ChannelCommitment{oldLog1, oldLog2} newLogs := []mig.ChannelCommitment{oldLog1, oldLog2} // Add a new log if the node is running with v0.15.0. if !legacy { newLogs = append(newLogs, newLog3) } return setupTestLogs(cdb, c, oldLogs, newLogs) } func init() { rand.Seed(time.Now().Unix()) if rand.Intn(2) == 0 { withAmtData = true } if withAmtData { newLog1 = newLog1WithAmts newLog2 = newLog2WithAmts } }