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.
This commit is contained in:
eugene 2022-04-04 16:09:15 -04:00
parent 3ff8eb899c
commit c9f5912601
No known key found for this signature in database
GPG key ID: 118759E83439A9B1
13 changed files with 503 additions and 28 deletions

View file

@ -177,7 +177,7 @@ func NewSingle(channel *channeldb.OpenChannel,
// to the channel ID so we can use that as height hint on restore. // to the channel ID so we can use that as height hint on restore.
chanID := channel.ShortChanID() chanID := channel.ShortChanID()
if chanID.BlockHeight == 0 { if chanID.BlockHeight == 0 {
chanID.BlockHeight = channel.FundingBroadcastHeight chanID.BlockHeight = channel.BroadcastHeight()
} }
single := Single{ single := Single{

View file

@ -16,6 +16,7 @@ import (
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/kvdb"
@ -53,6 +54,14 @@ var (
// //
outpointBucket = []byte("outpoint-bucket") 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 // historicalChannelBucket stores all channels that have seen their
// commitment tx confirm. All information from their previous open state // commitment tx confirm. All information from their previous open state
// is retained. // is retained.
@ -190,6 +199,10 @@ const (
// A tlv type used to serialize and deserialize the // A tlv type used to serialize and deserialize the
// `InitialRemoteBalance` field. // `InitialRemoteBalance` field.
initialRemoteBalanceType tlv.Type = 3 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 // 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, // 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 // a ChannelType is a bit field, with each bit denoting a modification from the
// base channel type of single funder. // base channel type of single funder.
type ChannelType uint8 type ChannelType uint64
const ( const (
// NOTE: iota isn't used here for this enum needs to be stable // 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 // period of time, constraining every output that pays to the channel
// initiator with an additional CLTV of the lease maturity. // initiator with an additional CLTV of the lease maturity.
LeaseExpirationBit ChannelType = 1 << 6 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 // 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 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 // ChannelConstraints represents a set of constraints meant to allow a node to
// limit their exposure, enact flow control and ensure that all HTLCs are // limit their exposure, enact flow control and ensure that all HTLCs are
// economically relevant. This struct will be mirrored for both sides of the // 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 // 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. // the default usable state, or a state where it shouldn't be used.
type ChannelStatus uint8 type ChannelStatus uint64
var ( var (
// ChanStatusDefault is the normal state of an open channel. // 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 // ShortChannelID encodes the exact location in the chain in which the
// channel was initially confirmed. This includes: the block height, // channel was initially confirmed. This includes: the block height,
// transaction index, and the output within the target transaction. // 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 ShortChannelID lnwire.ShortChannelID
// IsPending indicates whether a channel's funding transaction has been // IsPending indicates whether a channel's funding transaction has been
@ -739,6 +782,11 @@ type OpenChannel struct {
// have private key isolation from lnd. // have private key isolation from lnd.
RevocationKeyLocator keychain.KeyLocator 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 // TODO(roasbeef): eww
Db *ChannelStateDB Db *ChannelStateDB
@ -755,6 +803,50 @@ func (c *OpenChannel) ShortChanID() lnwire.ShortChannelID {
return c.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. // ChanStatus returns the current ChannelStatus of this channel.
func (c *OpenChannel) ChanStatus() ChannelStatus { func (c *OpenChannel) ChanStatus() ChannelStatus {
c.RLock() c.RLock()
@ -801,13 +893,25 @@ func (c *OpenChannel) hasChanStatus(status ChannelStatus) bool {
return c.chanStatus&status == status return c.chanStatus&status == status
} }
// RefreshShortChanID updates the in-memory channel state using the latest // BroadcastHeight returns the height at which the funding tx was broadcast.
// value observed on disk. func (c *OpenChannel) BroadcastHeight() uint32 {
// c.RLock()
// TODO: the name of this function should be changed to reflect the fact that defer c.RUnlock()
// it is not only refreshing the short channel id but all the channel state.
// maybe Refresh/Reload? return c.FundingBroadcastHeight
func (c *OpenChannel) RefreshShortChanID() error { }
// 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() c.Lock()
defer c.Unlock() defer c.Unlock()
@ -825,6 +929,19 @@ func (c *OpenChannel) RefreshShortChanID() error {
return fmt.Errorf("unable to fetch chan info: %v", err) 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 return nil
}, func() {}) }, func() {})
if err != nil { if err != nil {
@ -931,6 +1048,7 @@ func fetchChanBucketRw(tx kvdb.RwTx, nodeKey *btcec.PublicKey,
func (c *OpenChannel) fullSync(tx kvdb.RwTx) error { func (c *OpenChannel) fullSync(tx kvdb.RwTx) error {
// Fetch the outpoint bucket and check if the outpoint already exists. // Fetch the outpoint bucket and check if the outpoint already exists.
opBucket := tx.ReadWriteBucket(outpointBucket) opBucket := tx.ReadWriteBucket(outpointBucket)
cidBucket := tx.ReadWriteBucket(chanIDBucket)
var chanPointBuf bytes.Buffer var chanPointBuf bytes.Buffer
if err := writeOutpoint(&chanPointBuf, &c.FundingOutpoint); err != nil { if err := writeOutpoint(&chanPointBuf, &c.FundingOutpoint); err != nil {
@ -942,6 +1060,11 @@ func (c *OpenChannel) fullSync(tx kvdb.RwTx) error {
return ErrChanAlreadyExists return ErrChanAlreadyExists
} }
cid := lnwire.NewChanIDFromOutPoint(&c.FundingOutpoint)
if cidBucket.Get(cid[:]) != nil {
return ErrChanAlreadyExists
}
status := uint8(outpointOpen) status := uint8(outpointOpen)
// Write the status of this outpoint as the first entry in a tlv // 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 return err
} }
if err := cidBucket.Put(cid[:], []byte{}); err != nil {
return err
}
// First fetch the top level bucket which stores all data related to // First fetch the top level bucket which stores all data related to
// current, active channels. // current, active channels.
openChanBucket, err := tx.CreateTopLevelBucket(openChannelBucket) openChanBucket, err := tx.CreateTopLevelBucket(openChannelBucket)
@ -1035,6 +1162,71 @@ func (c *OpenChannel) MarkAsOpen(openLoc lnwire.ShortChannelID) error {
return nil 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 // MarkDataLoss marks sets the channel status to LocalDataLoss and stores the
// passed commitPoint for use to retrieve funds in case the remote force closes // passed commitPoint for use to retrieve funds in case the remote force closes
// the channel. // the channel.
@ -1101,6 +1293,22 @@ func (c *OpenChannel) MarkBorked() error {
return c.putChanStatus(ChanStatusBorked) 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 // ChanSyncMsg returns the ChannelReestablish message that should be sent upon
// reconnection with the remote peer that we're maintaining this channel with. // reconnection with the remote peer that we're maintaining this channel with.
// The information contained within this message is necessary to re-sync our // 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 " + return 0, errors.New("cannot use relative thaw " +
"height for unconfirmed channel") "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 return c.ThawHeight, nil
@ -3325,6 +3550,7 @@ func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
tlv.MakePrimitiveRecord( tlv.MakePrimitiveRecord(
initialRemoteBalanceType, &remoteBalance, initialRemoteBalanceType, &remoteBalance,
), ),
MakeScidRecord(realScidType, &channel.confirmedScid),
) )
if err != nil { if err != nil {
return err return err
@ -3541,6 +3767,7 @@ func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error {
tlv.MakePrimitiveRecord( tlv.MakePrimitiveRecord(
initialRemoteBalanceType, &remoteBalance, initialRemoteBalanceType, &remoteBalance,
), ),
MakeScidRecord(realScidType, &channel.confirmedScid),
) )
if err != nil { if err != nil {
return err 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 { func MakeKeyLocRecord(typ tlv.Type, keyLoc *keychain.KeyLocator) tlv.Record {
return tlv.MakeStaticRecord(typ, keyLoc, 8, EKeyLocator, DKeyLocator) 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,
)
}

View file

@ -1148,10 +1148,10 @@ func TestFetchWaitingCloseChannels(t *testing.T) {
} }
} }
// TestRefreshShortChanID asserts that RefreshShortChanID updates the in-memory // TestRefresh asserts that Refresh updates the in-memory state of another
// state of another OpenChannel to reflect a preceding call to MarkOpen on a // OpenChannel to reflect a preceding call to MarkOpen on a different
// different OpenChannel. // OpenChannel.
func TestRefreshShortChanID(t *testing.T) { func TestRefresh(t *testing.T) {
t.Parallel() t.Parallel()
fullDB, cleanUp, err := MakeTestDB() fullDB, cleanUp, err := MakeTestDB()
@ -1209,8 +1209,8 @@ func TestRefreshShortChanID(t *testing.T) {
state.Packager.(*ChannelPackager).source) state.Packager.(*ChannelPackager).source)
} }
// Now, refresh the short channel ID of the pending channel. // Now, refresh the state of the pending channel.
err = pendingChannel.RefreshShortChanID() err = pendingChannel.Refresh()
require.NoError(t, err, "unable to refresh short_chan_id") require.NoError(t, err, "unable to refresh short_chan_id")
// This should result in both OpenChannel's now having the same // This should result in both OpenChannel's now having the same

View file

@ -14,6 +14,7 @@ import (
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain" "github.com/lightningnetwork/lnd/shachain"
"github.com/lightningnetwork/lnd/tlv"
) )
// writeOutpoint writes an outpoint to the passed writer using the minimal // 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) return binary.Write(w, byteOrder, false)
case ChannelType: 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 return err
} }
@ -194,7 +196,8 @@ func WriteElement(w io.Writer, element interface{}) error {
} }
case ChannelStatus: 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 return err
} }
@ -270,10 +273,14 @@ func ReadElement(r io.Reader, element interface{}) error {
} }
case *ChannelType: 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 return err
} }
*e = ChannelType(ctype)
case *chainhash.Hash: case *chainhash.Hash:
if _, err := io.ReadFull(r, e[:]); err != nil { if _, err := io.ReadFull(r, e[:]); err != nil {
return err return err
@ -419,10 +426,14 @@ func ReadElement(r io.Reader, element interface{}) error {
*e = msg *e = msg
case *ChannelStatus: 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 return err
} }
*e = ChannelStatus(status)
case *ClosureType: case *ClosureType:
if err := binary.Read(r, byteOrder, e); err != nil { if err := binary.Read(r, byteOrder, e); err != nil {
return err return err

View file

@ -22,6 +22,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb/migration25" "github.com/lightningnetwork/lnd/channeldb/migration25"
"github.com/lightningnetwork/lnd/channeldb/migration26" "github.com/lightningnetwork/lnd/channeldb/migration26"
"github.com/lightningnetwork/lnd/channeldb/migration27" "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/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/kvdb"
@ -226,6 +227,14 @@ var (
number: 27, number: 27,
migration: migration27.MigrateHistoricalBalances, 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 // Big endian is the preferred byte order, due to cursor scans over
@ -352,6 +361,7 @@ var dbTopLevelBuckets = [][]byte{
metaBucket, metaBucket,
closeSummaryBucket, closeSummaryBucket,
outpointBucket, outpointBucket,
chanIDBucket,
historicalChannelBucket, historicalChannelBucket,
} }

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -243,7 +243,7 @@ func (c *chanDBRestorer) RestoreChansFromSingles(backups ...chanbackup.Single) e
// funding broadcast height to a reasonable value that we // funding broadcast height to a reasonable value that we
// determined earlier. // determined earlier.
case channel.ShortChanID().BlockHeight == 0: case channel.ShortChanID().BlockHeight == 0:
channel.FundingBroadcastHeight = firstChanHeight channel.SetBroadcastHeight(firstChanHeight)
// Fallback case 2: It is extremely unlikely at this point that // Fallback case 2: It is extremely unlikely at this point that
// a channel we are trying to restore has a coinbase funding TX. // 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. // unconfirmed one here.
case channel.ShortChannelID.TxIndex == 0: case channel.ShortChannelID.TxIndex == 0:
broadcastHeight := channel.ShortChannelID.BlockHeight broadcastHeight := channel.ShortChannelID.BlockHeight
channel.FundingBroadcastHeight = broadcastHeight channel.SetBroadcastHeight(broadcastHeight)
channel.ShortChannelID.BlockHeight = 0 channel.ShortChannelID.BlockHeight = 0
} }
} }

View file

@ -269,7 +269,7 @@ func (c *chainWatcher) Start() error {
// at. // at.
heightHint := c.cfg.chanState.ShortChanID().BlockHeight heightHint := c.cfg.chanState.ShortChanID().BlockHeight
if heightHint == 0 { if heightHint == 0 {
heightHint = chanState.FundingBroadcastHeight heightHint = chanState.BroadcastHeight()
} }
localKey := chanState.LocalChanCfg.MultiSigKey.PubKey.SerializeCompressed() localKey := chanState.LocalChanCfg.MultiSigKey.PubKey.SerializeCompressed()

View file

@ -2306,7 +2306,7 @@ func (f *Manager) waitForFundingConfirmation(
numConfs := uint32(completeChan.NumConfsRequired) numConfs := uint32(completeChan.NumConfsRequired)
confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn( confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn(
&txid, fundingScript, numConfs, &txid, fundingScript, numConfs,
completeChan.FundingBroadcastHeight, completeChan.BroadcastHeight(),
) )
if err != nil { if err != nil {
log.Errorf("Unable to register for confirmation of "+ log.Errorf("Unable to register for confirmation of "+
@ -2392,7 +2392,8 @@ func (f *Manager) waitForTimeout(completeChan *channeldb.OpenChannel,
defer epochClient.Cancel() defer epochClient.Cancel()
// On block maxHeight we will cancel the funding confirmation wait. // On block maxHeight we will cancel the funding confirmation wait.
maxHeight := completeChan.FundingBroadcastHeight + maxWaitNumBlocksFundingConf broadcastHeight := completeChan.BroadcastHeight()
maxHeight := broadcastHeight + maxWaitNumBlocksFundingConf
for { for {
select { select {
case epoch, ok := <-epochClient.Epochs: case epoch, ok := <-epochClient.Epochs:
@ -2738,7 +2739,7 @@ func (f *Manager) annAfterSixConfs(completeChan *channeldb.OpenChannel,
// funding transaction reaches at least 6 confirmations. // funding transaction reaches at least 6 confirmations.
confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn( confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn(
&txid, fundingScript, numConfs, &txid, fundingScript, numConfs,
completeChan.FundingBroadcastHeight, completeChan.BroadcastHeight(),
) )
if err != nil { if err != nil {
return fmt.Errorf("unable to register for "+ return fmt.Errorf("unable to register for "+

View file

@ -2243,7 +2243,7 @@ func (l *channelLink) UpdateShortChanID() (lnwire.ShortChannelID, error) {
// Refresh the channel state's short channel ID by loading it from disk. // Refresh the channel state's short channel ID by loading it from disk.
// This ensures that the channel state accurately reflects the updated // This ensures that the channel state accurately reflects the updated
// short channel ID. // short channel ID.
err := l.channel.State().RefreshShortChanID() err := l.channel.State().Refresh()
if err != nil { if err != nil {
l.log.Errorf("unable to refresh short_chan_id for chan_id=%v: "+ l.log.Errorf("unable to refresh short_chan_id for chan_id=%v: "+
"%v", chanID, err) "%v", chanID, err)