From c9f591260121ed4bc896ac09f6505147ef93f8ab Mon Sep 17 00:00:00 2001 From: eugene Date: Mon, 4 Apr 2022 16:09:15 -0400 Subject: [PATCH] channeldb: BigSize migration, store zero-conf, scid-alias bits This introduces a BigSize migration that is used to expand the width of the ChannelStatus and ChannelType fields. Three channel "types" are added - ZeroConfBit, ScidAliasChanBit, and ScidAliasFeatureBit. ScidAliasChanBit denotes that the scid-alias channel type was negotiated for the channel. ScidAliasFeatureBit denotes that the scid-alias feature bit was negotiated during the *lifetime* of the channel. Several helper functions on the OpenChannel struct are exposed to aid callers from different packages. The RefreshShortChanID has been renamed to Refresh. A new function BroadcastHeight is used to guard access to the mutable FundingBroadcastHeight member. This prevents data races. --- chanbackup/single.go | 2 +- channeldb/channel.go | 256 +++++++++++++++++++++++- channeldb/channel_test.go | 12 +- channeldb/codec.go | 19 +- channeldb/db.go | 10 + channeldb/migration29/codec.go | 66 ++++++ channeldb/migration29/log.go | 12 ++ channeldb/migration29/migration.go | 72 +++++++ channeldb/migration29/migration_test.go | 67 +++++++ chanrestore.go | 4 +- contractcourt/chain_watcher.go | 2 +- funding/manager.go | 7 +- htlcswitch/link.go | 2 +- 13 files changed, 503 insertions(+), 28 deletions(-) create mode 100644 channeldb/migration29/codec.go create mode 100644 channeldb/migration29/log.go create mode 100644 channeldb/migration29/migration.go create mode 100644 channeldb/migration29/migration_test.go diff --git a/chanbackup/single.go b/chanbackup/single.go index 143516317..949af708f 100644 --- a/chanbackup/single.go +++ b/chanbackup/single.go @@ -177,7 +177,7 @@ func NewSingle(channel *channeldb.OpenChannel, // to the channel ID so we can use that as height hint on restore. chanID := channel.ShortChanID() if chanID.BlockHeight == 0 { - chanID.BlockHeight = channel.FundingBroadcastHeight + chanID.BlockHeight = channel.BroadcastHeight() } single := Single{ diff --git a/channeldb/channel.go b/channeldb/channel.go index a40aa79aa..8c04ccdc2 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -16,6 +16,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" @@ -53,6 +54,14 @@ var ( // outpointBucket = []byte("outpoint-bucket") + // chanIDBucket stores all of the 32-byte channel ID's we know about. + // These could be derived from outpointBucket, but it is more + // convenient to have these in their own bucket. + // + // chanID -> tlv stream. + // + chanIDBucket = []byte("chan-id-bucket") + // historicalChannelBucket stores all channels that have seen their // commitment tx confirm. All information from their previous open state // is retained. @@ -190,6 +199,10 @@ const ( // A tlv type used to serialize and deserialize the // `InitialRemoteBalance` field. initialRemoteBalanceType tlv.Type = 3 + + // A tlv type definition used to serialize and deserialize the + // confirmed ShortChannelID for a zero-conf channel. + realScidType tlv.Type = 4 ) // indexStatus is an enum-like type that describes what state the @@ -211,7 +224,7 @@ const ( // fee negotiation, channel closing, the format of HTLCs, etc. Structure-wise, // a ChannelType is a bit field, with each bit denoting a modification from the // base channel type of single funder. -type ChannelType uint8 +type ChannelType uint64 const ( // NOTE: iota isn't used here for this enum needs to be stable @@ -254,6 +267,17 @@ const ( // period of time, constraining every output that pays to the channel // initiator with an additional CLTV of the lease maturity. LeaseExpirationBit ChannelType = 1 << 6 + + // ZeroConfBit indicates that the channel is a zero-conf channel. + ZeroConfBit ChannelType = 1 << 7 + + // ScidAliasChanBit indicates that the channel has negotiated the + // scid-alias channel type. + ScidAliasChanBit ChannelType = 1 << 8 + + // ScidAliasFeatureBit indicates that the scid-alias feature bit was + // negotiated during the lifetime of this channel. + ScidAliasFeatureBit ChannelType = 1 << 9 ) // IsSingleFunder returns true if the channel type if one of the known single @@ -303,6 +327,22 @@ func (c ChannelType) HasLeaseExpiration() bool { return c&LeaseExpirationBit == LeaseExpirationBit } +// HasZeroConf returns true if the channel is a zero-conf channel. +func (c ChannelType) HasZeroConf() bool { + return c&ZeroConfBit == ZeroConfBit +} + +// HasScidAliasChan returns true if the scid-alias channel type was negotiated. +func (c ChannelType) HasScidAliasChan() bool { + return c&ScidAliasChanBit == ScidAliasChanBit +} + +// HasScidAliasFeature returns true if the scid-alias feature bit was +// negotiated during the lifetime of this channel. +func (c ChannelType) HasScidAliasFeature() bool { + return c&ScidAliasFeatureBit == ScidAliasFeatureBit +} + // ChannelConstraints represents a set of constraints meant to allow a node to // limit their exposure, enact flow control and ensure that all HTLCs are // economically relevant. This struct will be mirrored for both sides of the @@ -476,7 +516,7 @@ type ChannelCommitment struct { // ChannelStatus is a bit vector used to indicate whether an OpenChannel is in // the default usable state, or a state where it shouldn't be used. -type ChannelStatus uint8 +type ChannelStatus uint64 var ( // ChanStatusDefault is the normal state of an open channel. @@ -604,6 +644,9 @@ type OpenChannel struct { // ShortChannelID encodes the exact location in the chain in which the // channel was initially confirmed. This includes: the block height, // transaction index, and the output within the target transaction. + // + // If IsZeroConf(), then this will the "base" (very first) ALIAS scid + // and the confirmed SCID will be stored in ConfirmedScid. ShortChannelID lnwire.ShortChannelID // IsPending indicates whether a channel's funding transaction has been @@ -739,6 +782,11 @@ type OpenChannel struct { // have private key isolation from lnd. RevocationKeyLocator keychain.KeyLocator + // confirmedScid is the confirmed ShortChannelID for a zero-conf + // channel. If the channel is unconfirmed, then this will be the + // default ShortChannelID. This is only set for zero-conf channels. + confirmedScid lnwire.ShortChannelID + // TODO(roasbeef): eww Db *ChannelStateDB @@ -755,6 +803,50 @@ func (c *OpenChannel) ShortChanID() lnwire.ShortChannelID { return c.ShortChannelID } +// ZeroConfRealScid returns the zero-conf channel's confirmed scid. This should +// only be called if IsZeroConf returns true. +func (c *OpenChannel) ZeroConfRealScid() lnwire.ShortChannelID { + c.RLock() + defer c.RUnlock() + + return c.confirmedScid +} + +// ZeroConfConfirmed returns whether the zero-conf channel has confirmed. This +// should only be called if IsZeroConf returns true. +func (c *OpenChannel) ZeroConfConfirmed() bool { + c.RLock() + defer c.RUnlock() + + return c.confirmedScid != hop.Source +} + +// IsZeroConf returns whether the option_zeroconf channel type was negotiated. +func (c *OpenChannel) IsZeroConf() bool { + c.RLock() + defer c.RUnlock() + + return c.ChanType.HasZeroConf() +} + +// IsOptionScidAlias returns whether the option_scid_alias channel type was +// negotiated. +func (c *OpenChannel) IsOptionScidAlias() bool { + c.RLock() + defer c.RUnlock() + + return c.ChanType.HasScidAliasChan() +} + +// NegotiatedAliasFeature returns whether the option-scid-alias feature bit was +// negotiated. +func (c *OpenChannel) NegotiatedAliasFeature() bool { + c.RLock() + defer c.RUnlock() + + return c.ChanType.HasScidAliasFeature() +} + // ChanStatus returns the current ChannelStatus of this channel. func (c *OpenChannel) ChanStatus() ChannelStatus { c.RLock() @@ -801,13 +893,25 @@ func (c *OpenChannel) hasChanStatus(status ChannelStatus) bool { return c.chanStatus&status == status } -// RefreshShortChanID updates the in-memory channel state using the latest -// value observed on disk. -// -// TODO: the name of this function should be changed to reflect the fact that -// it is not only refreshing the short channel id but all the channel state. -// maybe Refresh/Reload? -func (c *OpenChannel) RefreshShortChanID() error { +// BroadcastHeight returns the height at which the funding tx was broadcast. +func (c *OpenChannel) BroadcastHeight() uint32 { + c.RLock() + defer c.RUnlock() + + return c.FundingBroadcastHeight +} + +// SetBroadcastHeight sets the FundingBroadcastHeight. +func (c *OpenChannel) SetBroadcastHeight(height uint32) { + c.Lock() + defer c.Unlock() + + c.FundingBroadcastHeight = height +} + +// Refresh updates the in-memory channel state using the latest state observed +// on disk. +func (c *OpenChannel) Refresh() error { c.Lock() defer c.Unlock() @@ -825,6 +929,19 @@ func (c *OpenChannel) RefreshShortChanID() error { return fmt.Errorf("unable to fetch chan info: %v", err) } + // Also populate the channel's commitment states for both sides + // of the channel. + if err := fetchChanCommitments(chanBucket, c); err != nil { + return fmt.Errorf("unable to fetch chan commitments: "+ + "%v", err) + } + + // Also retrieve the current revocation state. + if err := fetchChanRevocationState(chanBucket, c); err != nil { + return fmt.Errorf("unable to fetch chan revocations: "+ + "%v", err) + } + return nil }, func() {}) if err != nil { @@ -931,6 +1048,7 @@ func fetchChanBucketRw(tx kvdb.RwTx, nodeKey *btcec.PublicKey, func (c *OpenChannel) fullSync(tx kvdb.RwTx) error { // Fetch the outpoint bucket and check if the outpoint already exists. opBucket := tx.ReadWriteBucket(outpointBucket) + cidBucket := tx.ReadWriteBucket(chanIDBucket) var chanPointBuf bytes.Buffer if err := writeOutpoint(&chanPointBuf, &c.FundingOutpoint); err != nil { @@ -942,6 +1060,11 @@ func (c *OpenChannel) fullSync(tx kvdb.RwTx) error { return ErrChanAlreadyExists } + cid := lnwire.NewChanIDFromOutPoint(&c.FundingOutpoint) + if cidBucket.Get(cid[:]) != nil { + return ErrChanAlreadyExists + } + status := uint8(outpointOpen) // Write the status of this outpoint as the first entry in a tlv @@ -962,6 +1085,10 @@ func (c *OpenChannel) fullSync(tx kvdb.RwTx) error { return err } + if err := cidBucket.Put(cid[:], []byte{}); err != nil { + return err + } + // First fetch the top level bucket which stores all data related to // current, active channels. openChanBucket, err := tx.CreateTopLevelBucket(openChannelBucket) @@ -1035,6 +1162,71 @@ func (c *OpenChannel) MarkAsOpen(openLoc lnwire.ShortChannelID) error { return nil } +// MarkRealScid marks the zero-conf channel's confirmed ShortChannelID. This +// should only be done if IsZeroConf returns true. +func (c *OpenChannel) MarkRealScid(realScid lnwire.ShortChannelID) error { + c.Lock() + defer c.Unlock() + + if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { + chanBucket, err := fetchChanBucketRw( + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, + ) + if err != nil { + return err + } + + channel, err := fetchOpenChannel( + chanBucket, &c.FundingOutpoint, + ) + if err != nil { + return err + } + + channel.confirmedScid = realScid + + return putOpenChannel(chanBucket, channel) + }, func() {}); err != nil { + return err + } + + c.confirmedScid = realScid + + return nil +} + +// MarkScidAliasNegotiated adds ScidAliasFeatureBit to ChanType in-memory and +// in the database. +func (c *OpenChannel) MarkScidAliasNegotiated() error { + c.Lock() + defer c.Unlock() + + if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { + chanBucket, err := fetchChanBucketRw( + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, + ) + if err != nil { + return err + } + + channel, err := fetchOpenChannel( + chanBucket, &c.FundingOutpoint, + ) + if err != nil { + return err + } + + channel.ChanType |= ScidAliasFeatureBit + return putOpenChannel(chanBucket, channel) + }, func() {}); err != nil { + return err + } + + c.ChanType |= ScidAliasFeatureBit + + return nil +} + // MarkDataLoss marks sets the channel status to LocalDataLoss and stores the // passed commitPoint for use to retrieve funds in case the remote force closes // the channel. @@ -1101,6 +1293,22 @@ func (c *OpenChannel) MarkBorked() error { return c.putChanStatus(ChanStatusBorked) } +// SecondCommitmentPoint returns the second per-commitment-point for use in the +// funding_locked message. +func (c *OpenChannel) SecondCommitmentPoint() (*btcec.PublicKey, error) { + c.RLock() + defer c.RUnlock() + + // Since we start at commitment height = 0, the second per commitment + // point is actually at the 1st index. + revocation, err := c.RevocationProducer.AtIndex(1) + if err != nil { + return nil, err + } + + return input.ComputeCommitmentPoint(revocation[:]), nil +} + // ChanSyncMsg returns the ChannelReestablish message that should be sent upon // reconnection with the remote peer that we're maintaining this channel with. // The information contained within this message is necessary to re-sync our @@ -3095,7 +3303,24 @@ func (c *OpenChannel) AbsoluteThawHeight() (uint32, error) { return 0, errors.New("cannot use relative thaw " + "height for unconfirmed channel") } - return c.ShortChannelID.BlockHeight + c.ThawHeight, nil + + // For non-zero-conf channels, this is the base height to use. + blockHeightBase := c.ShortChannelID.BlockHeight + + // If this is a zero-conf channel, the ShortChannelID will be + // an alias. + if c.IsZeroConf() { + if !c.ZeroConfConfirmed() { + return 0, errors.New("cannot use relative " + + "height for unconfirmed zero-conf " + + "channel") + } + + // Use the confirmed SCID's BlockHeight. + blockHeightBase = c.confirmedScid.BlockHeight + } + + return blockHeightBase + c.ThawHeight, nil } return c.ThawHeight, nil @@ -3325,6 +3550,7 @@ func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error { tlv.MakePrimitiveRecord( initialRemoteBalanceType, &remoteBalance, ), + MakeScidRecord(realScidType, &channel.confirmedScid), ) if err != nil { return err @@ -3541,6 +3767,7 @@ func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error { tlv.MakePrimitiveRecord( initialRemoteBalanceType, &remoteBalance, ), + MakeScidRecord(realScidType, &channel.confirmedScid), ) if err != nil { return err @@ -3744,3 +3971,12 @@ func DKeyLocator(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { func MakeKeyLocRecord(typ tlv.Type, keyLoc *keychain.KeyLocator) tlv.Record { return tlv.MakeStaticRecord(typ, keyLoc, 8, EKeyLocator, DKeyLocator) } + +// MakeScidRecord creates a Record out of a ShortChannelID using the passed +// Type and the EShortChannelID and DShortChannelID functions. The size will +// always be 8 for the ShortChannelID. +func MakeScidRecord(typ tlv.Type, scid *lnwire.ShortChannelID) tlv.Record { + return tlv.MakeStaticRecord( + typ, scid, 8, lnwire.EShortChannelID, lnwire.DShortChannelID, + ) +} diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go index 3deed674a..eb7cff6f3 100644 --- a/channeldb/channel_test.go +++ b/channeldb/channel_test.go @@ -1148,10 +1148,10 @@ func TestFetchWaitingCloseChannels(t *testing.T) { } } -// TestRefreshShortChanID asserts that RefreshShortChanID updates the in-memory -// state of another OpenChannel to reflect a preceding call to MarkOpen on a -// different OpenChannel. -func TestRefreshShortChanID(t *testing.T) { +// TestRefresh asserts that Refresh updates the in-memory state of another +// OpenChannel to reflect a preceding call to MarkOpen on a different +// OpenChannel. +func TestRefresh(t *testing.T) { t.Parallel() fullDB, cleanUp, err := MakeTestDB() @@ -1209,8 +1209,8 @@ func TestRefreshShortChanID(t *testing.T) { state.Packager.(*ChannelPackager).source) } - // Now, refresh the short channel ID of the pending channel. - err = pendingChannel.RefreshShortChanID() + // Now, refresh the state of the pending channel. + err = pendingChannel.Refresh() require.NoError(t, err, "unable to refresh short_chan_id") // This should result in both OpenChannel's now having the same diff --git a/channeldb/codec.go b/channeldb/codec.go index 832b91923..a79ef8558 100644 --- a/channeldb/codec.go +++ b/channeldb/codec.go @@ -14,6 +14,7 @@ import ( "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" + "github.com/lightningnetwork/lnd/tlv" ) // writeOutpoint writes an outpoint to the passed writer using the minimal @@ -85,7 +86,8 @@ func WriteElement(w io.Writer, element interface{}) error { return binary.Write(w, byteOrder, false) case ChannelType: - if err := binary.Write(w, byteOrder, e); err != nil { + var buf [8]byte + if err := tlv.WriteVarInt(w, uint64(e), &buf); err != nil { return err } @@ -194,7 +196,8 @@ func WriteElement(w io.Writer, element interface{}) error { } case ChannelStatus: - if err := binary.Write(w, byteOrder, e); err != nil { + var buf [8]byte + if err := tlv.WriteVarInt(w, uint64(e), &buf); err != nil { return err } @@ -270,10 +273,14 @@ func ReadElement(r io.Reader, element interface{}) error { } case *ChannelType: - if err := binary.Read(r, byteOrder, e); err != nil { + var buf [8]byte + ctype, err := tlv.ReadVarInt(r, &buf) + if err != nil { return err } + *e = ChannelType(ctype) + case *chainhash.Hash: if _, err := io.ReadFull(r, e[:]); err != nil { return err @@ -419,10 +426,14 @@ func ReadElement(r io.Reader, element interface{}) error { *e = msg case *ChannelStatus: - if err := binary.Read(r, byteOrder, e); err != nil { + var buf [8]byte + status, err := tlv.ReadVarInt(r, &buf) + if err != nil { return err } + *e = ChannelStatus(status) + case *ClosureType: if err := binary.Read(r, byteOrder, e); err != nil { return err diff --git a/channeldb/db.go b/channeldb/db.go index ac0ba9642..998ddf9ce 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -22,6 +22,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb/migration25" "github.com/lightningnetwork/lnd/channeldb/migration26" "github.com/lightningnetwork/lnd/channeldb/migration27" + "github.com/lightningnetwork/lnd/channeldb/migration29" "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/kvdb" @@ -226,6 +227,14 @@ var ( number: 27, migration: migration27.MigrateHistoricalBalances, }, + { + number: 28, + migration: mig.CreateTLB(chanIDBucket), + }, + { + number: 29, + migration: migration29.MigrateChanID, + }, } // Big endian is the preferred byte order, due to cursor scans over @@ -352,6 +361,7 @@ var dbTopLevelBuckets = [][]byte{ metaBucket, closeSummaryBucket, outpointBucket, + chanIDBucket, historicalChannelBucket, } diff --git a/channeldb/migration29/codec.go b/channeldb/migration29/codec.go new file mode 100644 index 000000000..fe11348a6 --- /dev/null +++ b/channeldb/migration29/codec.go @@ -0,0 +1,66 @@ +package migration29 + +import ( + "encoding/binary" + "encoding/hex" + "io" + + "github.com/btcsuite/btcd/wire" +) + +var ( + byteOrder = binary.BigEndian +) + +// ChannelID is a series of 32-bytes that uniquely identifies all channels +// within the network. The ChannelID is computed using the outpoint of the +// funding transaction (the txid, and output index). Given a funding output the +// ChannelID can be calculated by XOR'ing the big-endian serialization of the +// txid and the big-endian serialization of the output index, truncated to +// 2 bytes. +type ChannelID [32]byte + +// String returns the string representation of the ChannelID. This is just the +// hex string encoding of the ChannelID itself. +func (c ChannelID) String() string { + return hex.EncodeToString(c[:]) +} + +// NewChanIDFromOutPoint converts a target OutPoint into a ChannelID that is +// usable within the network. In order to convert the OutPoint into a ChannelID, +// we XOR the lower 2-bytes of the txid within the OutPoint with the big-endian +// serialization of the Index of the OutPoint, truncated to 2-bytes. +func NewChanIDFromOutPoint(op *wire.OutPoint) ChannelID { + // First we'll copy the txid of the outpoint into our channel ID slice. + var cid ChannelID + copy(cid[:], op.Hash[:]) + + // With the txid copied over, we'll now XOR the lower 2-bytes of the + // partial channelID with big-endian serialization of output index. + xorTxid(&cid, uint16(op.Index)) + + return cid +} + +// xorTxid performs the transformation needed to transform an OutPoint into a +// ChannelID. To do this, we expect the cid parameter to contain the txid +// unaltered and the outputIndex to be the output index +func xorTxid(cid *ChannelID, outputIndex uint16) { + var buf [2]byte + binary.BigEndian.PutUint16(buf[:], outputIndex) + + cid[30] ^= buf[0] + cid[31] ^= buf[1] +} + +// readOutpoint reads an outpoint from the passed reader. +func readOutpoint(r io.Reader, o *wire.OutPoint) error { + if _, err := io.ReadFull(r, o.Hash[:]); err != nil { + return err + } + if err := binary.Read(r, byteOrder, &o.Index); err != nil { + return err + } + + return nil +} diff --git a/channeldb/migration29/log.go b/channeldb/migration29/log.go new file mode 100644 index 000000000..b9ae19a5f --- /dev/null +++ b/channeldb/migration29/log.go @@ -0,0 +1,12 @@ +package migration29 + +import "github.com/btcsuite/btclog" + +// log is a logger that is initialized as disabled. This means the package will +// not perform any logging by default until a logger is set. +var log = btclog.Disabled + +// UseLogger uses a specific Logger to output package logging info. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/channeldb/migration29/migration.go b/channeldb/migration29/migration.go new file mode 100644 index 000000000..7ca3a8f89 --- /dev/null +++ b/channeldb/migration29/migration.go @@ -0,0 +1,72 @@ +package migration29 + +import ( + "bytes" + + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/kvdb" +) + +var ( + // outpointBucket is the bucket that stores the set of outpoints we + // know about. + outpointBucket = []byte("outpoint-bucket") + + // chanIDBucket is the bucket that stores the set of ChannelID's we + // know about. + chanIDBucket = []byte("chan-id-bucket") +) + +// MigrateChanID populates the ChannelID index by using the set of outpoints +// retrieved from the outpoint bucket. +func MigrateChanID(tx kvdb.RwTx) error { + log.Info("Populating ChannelID index") + + // First we'll retrieve the set of outpoints we know about. + ops, err := fetchOutPoints(tx) + if err != nil { + return err + } + + return populateChanIDIndex(tx, ops) +} + +// fetchOutPoints loops through the outpointBucket and returns each stored +// outpoint. +func fetchOutPoints(tx kvdb.RwTx) ([]*wire.OutPoint, error) { + var ops []*wire.OutPoint + + bucket := tx.ReadBucket(outpointBucket) + + err := bucket.ForEach(func(k, _ []byte) error { + var op wire.OutPoint + r := bytes.NewReader(k) + if err := readOutpoint(r, &op); err != nil { + return err + } + + ops = append(ops, &op) + return nil + }) + if err != nil { + return nil, err + } + + return ops, nil +} + +// populateChanIDIndex uses the set of retrieved outpoints and populates the +// ChannelID index. +func populateChanIDIndex(tx kvdb.RwTx, ops []*wire.OutPoint) error { + bucket := tx.ReadWriteBucket(chanIDBucket) + + for _, op := range ops { + chanID := NewChanIDFromOutPoint(op) + + if err := bucket.Put(chanID[:], []byte{}); err != nil { + return err + } + } + + return nil +} diff --git a/channeldb/migration29/migration_test.go b/channeldb/migration29/migration_test.go new file mode 100644 index 000000000..d0a13fd5d --- /dev/null +++ b/channeldb/migration29/migration_test.go @@ -0,0 +1,67 @@ +package migration29 + +import ( + "testing" + + "github.com/lightningnetwork/lnd/channeldb/migtest" + "github.com/lightningnetwork/lnd/kvdb" +) + +var ( + hexStr = migtest.Hex + + outpoint1 = hexStr("81b637d8fcd2c6da6859e6963113a1170de793e4b725b84d1e0b4cf99ec58ce90fb463ad") + outpoint2 = hexStr("81b637d8fcd2c6da6859e6963113a1170de793e4b725b84d1e0b4cf99ec58ce952d6c6c7") + + chanID1 = hexStr("81b637d8fcd2c6da6859e6963113a1170de793e4b725b84d1e0b4cf99ec5ef44") + chanID2 = hexStr("81b637d8fcd2c6da6859e6963113a1170de793e4b725b84d1e0b4cf99ec54a2e") + + // These tlv streams are used to populate the outpoint bucket at the + // start of the test. + tlvOutpointOpen = hexStr("000100") + tlvOutpointClosed = hexStr("000101") + + // outpointData is used to populate the outpoint bucket. + outpointData = map[string]interface{}{ + outpoint1: tlvOutpointOpen, + outpoint2: tlvOutpointClosed, + } + + // chanIDBefore is the ChannelID bucket before the migration. + chanIDBefore = map[string]interface{}{} + + // post is the expected data in the ChannelID bucket after the + // migration. + post = map[string]interface{}{ + chanID1: "", + chanID2: "", + } +) + +// TestMigrateChannelIDIndex asserts that the ChannelID index is properly +// populated. +func TestMigrateChannelIDIndex(t *testing.T) { + // Prime the database with the populated outpoint bucket. We create the + // ChannelID bucket since the prior migration creates it anyways. + before := func(tx kvdb.RwTx) error { + err := migtest.RestoreDB(tx, outpointBucket, outpointData) + if err != nil { + return err + } + + return migtest.RestoreDB(tx, chanIDBucket, chanIDBefore) + } + + // After the migration, ensure that the ChannelID bucket is properly + // populated. + after := func(tx kvdb.RwTx) error { + err := migtest.VerifyDB(tx, outpointBucket, outpointData) + if err != nil { + return err + } + + return migtest.VerifyDB(tx, chanIDBucket, post) + } + + migtest.ApplyMigration(t, before, after, MigrateChanID, false) +} diff --git a/chanrestore.go b/chanrestore.go index 54825de56..c2b93070c 100644 --- a/chanrestore.go +++ b/chanrestore.go @@ -243,7 +243,7 @@ func (c *chanDBRestorer) RestoreChansFromSingles(backups ...chanbackup.Single) e // funding broadcast height to a reasonable value that we // determined earlier. case channel.ShortChanID().BlockHeight == 0: - channel.FundingBroadcastHeight = firstChanHeight + channel.SetBroadcastHeight(firstChanHeight) // Fallback case 2: It is extremely unlikely at this point that // a channel we are trying to restore has a coinbase funding TX. @@ -255,7 +255,7 @@ func (c *chanDBRestorer) RestoreChansFromSingles(backups ...chanbackup.Single) e // unconfirmed one here. case channel.ShortChannelID.TxIndex == 0: broadcastHeight := channel.ShortChannelID.BlockHeight - channel.FundingBroadcastHeight = broadcastHeight + channel.SetBroadcastHeight(broadcastHeight) channel.ShortChannelID.BlockHeight = 0 } } diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index 6ba4c2795..49067fbd2 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -269,7 +269,7 @@ func (c *chainWatcher) Start() error { // at. heightHint := c.cfg.chanState.ShortChanID().BlockHeight if heightHint == 0 { - heightHint = chanState.FundingBroadcastHeight + heightHint = chanState.BroadcastHeight() } localKey := chanState.LocalChanCfg.MultiSigKey.PubKey.SerializeCompressed() diff --git a/funding/manager.go b/funding/manager.go index 648448a02..07dd16019 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -2306,7 +2306,7 @@ func (f *Manager) waitForFundingConfirmation( numConfs := uint32(completeChan.NumConfsRequired) confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn( &txid, fundingScript, numConfs, - completeChan.FundingBroadcastHeight, + completeChan.BroadcastHeight(), ) if err != nil { log.Errorf("Unable to register for confirmation of "+ @@ -2392,7 +2392,8 @@ func (f *Manager) waitForTimeout(completeChan *channeldb.OpenChannel, defer epochClient.Cancel() // On block maxHeight we will cancel the funding confirmation wait. - maxHeight := completeChan.FundingBroadcastHeight + maxWaitNumBlocksFundingConf + broadcastHeight := completeChan.BroadcastHeight() + maxHeight := broadcastHeight + maxWaitNumBlocksFundingConf for { select { case epoch, ok := <-epochClient.Epochs: @@ -2738,7 +2739,7 @@ func (f *Manager) annAfterSixConfs(completeChan *channeldb.OpenChannel, // funding transaction reaches at least 6 confirmations. confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn( &txid, fundingScript, numConfs, - completeChan.FundingBroadcastHeight, + completeChan.BroadcastHeight(), ) if err != nil { return fmt.Errorf("unable to register for "+ diff --git a/htlcswitch/link.go b/htlcswitch/link.go index fe1e8fb3c..86390e5b2 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -2243,7 +2243,7 @@ func (l *channelLink) UpdateShortChanID() (lnwire.ShortChannelID, error) { // Refresh the channel state's short channel ID by loading it from disk. // This ensures that the channel state accurately reflects the updated // short channel ID. - err := l.channel.State().RefreshShortChanID() + err := l.channel.State().Refresh() if err != nil { l.log.Errorf("unable to refresh short_chan_id for chan_id=%v: "+ "%v", chanID, err)