mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-03 17:26:57 +01:00
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:
parent
3ff8eb899c
commit
c9f5912601
13 changed files with 503 additions and 28 deletions
|
@ -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{
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
66
channeldb/migration29/codec.go
Normal file
66
channeldb/migration29/codec.go
Normal 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
|
||||
}
|
12
channeldb/migration29/log.go
Normal file
12
channeldb/migration29/log.go
Normal 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
|
||||
}
|
72
channeldb/migration29/migration.go
Normal file
72
channeldb/migration29/migration.go
Normal 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
|
||||
}
|
67
channeldb/migration29/migration_test.go
Normal file
67
channeldb/migration29/migration_test.go
Normal 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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 "+
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue