mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-04 09:48:19 +01:00
channeldb+migration: add migration to save the initial balances
This commit adds a migration to patch the newly added fields, `InitialLocalBalance` and `InitialRemoteBalance` to channel info.
This commit is contained in:
parent
8b289e79f5
commit
c9843fd206
4 changed files with 954 additions and 0 deletions
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/channeldb/migration21"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration23"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration24"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration25"
|
||||
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
|
@ -205,6 +206,12 @@ var (
|
|||
number: 24,
|
||||
migration: migration24.MigrateFwdPkgCleanup,
|
||||
},
|
||||
{
|
||||
// Save the initial local/remote balances in channel
|
||||
// info.
|
||||
number: 25,
|
||||
migration: migration25.MigrateInitialBalances,
|
||||
},
|
||||
}
|
||||
|
||||
// Big endian is the preferred byte order, due to cursor scans over
|
||||
|
|
722
channeldb/migration25/channel.go
Normal file
722
channeldb/migration25/channel.go
Normal file
|
@ -0,0 +1,722 @@
|
|||
package migration25
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
|
||||
mig24 "github.com/lightningnetwork/lnd/channeldb/migration24"
|
||||
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
const (
|
||||
// A tlv type definition used to serialize and deserialize a KeyLocator
|
||||
// from the database.
|
||||
keyLocType tlv.Type = 1
|
||||
)
|
||||
|
||||
var (
|
||||
// chanCommitmentKey can be accessed within the sub-bucket for a
|
||||
// particular channel. This key stores the up to date commitment state
|
||||
// for a particular channel party. Appending a 0 to the end of this key
|
||||
// indicates it's the commitment for the local party, and appending a 1
|
||||
// to the end of this key indicates it's the commitment for the remote
|
||||
// party.
|
||||
chanCommitmentKey = []byte("chan-commitment-key")
|
||||
|
||||
// revocationLogBucketLegacy is the legacy bucket where we store the
|
||||
// revocation log in old format.
|
||||
revocationLogBucketLegacy = []byte("revocation-log-key")
|
||||
|
||||
// localUpfrontShutdownKey can be accessed within the bucket for a
|
||||
// channel (identified by its chanPoint). This key stores an optional
|
||||
// upfront shutdown script for the local peer.
|
||||
localUpfrontShutdownKey = []byte("local-upfront-shutdown-key")
|
||||
|
||||
// remoteUpfrontShutdownKey can be accessed within the bucket for a
|
||||
// channel (identified by its chanPoint). This key stores an optional
|
||||
// upfront shutdown script for the remote peer.
|
||||
remoteUpfrontShutdownKey = []byte("remote-upfront-shutdown-key")
|
||||
|
||||
// lastWasRevokeKey is a key that stores true when the last update we
|
||||
// sent was a revocation and false when it was a commitment signature.
|
||||
// This is nil in the case of new channels with no updates exchanged.
|
||||
lastWasRevokeKey = []byte("last-was-revoke")
|
||||
|
||||
// ErrNoChanInfoFound is returned when a particular channel does not
|
||||
// have any channels state.
|
||||
ErrNoChanInfoFound = fmt.Errorf("no chan info found")
|
||||
|
||||
// ErrNoPastDeltas is returned when the channel delta bucket hasn't been
|
||||
// created.
|
||||
ErrNoPastDeltas = fmt.Errorf("channel has no recorded deltas")
|
||||
|
||||
// ErrLogEntryNotFound is returned when we cannot find a log entry at
|
||||
// the height requested in the revocation log.
|
||||
ErrLogEntryNotFound = fmt.Errorf("log entry not found")
|
||||
|
||||
// ErrNoCommitmentsFound is returned when a channel has not set
|
||||
// commitment states.
|
||||
ErrNoCommitmentsFound = fmt.Errorf("no commitments found")
|
||||
)
|
||||
|
||||
// ChannelType is an enum-like type that describes one of several possible
|
||||
// channel types. Each open channel is associated with a particular type as the
|
||||
// channel type may determine how higher level operations are conducted such as
|
||||
// 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
|
||||
|
||||
const (
|
||||
// NOTE: iota isn't used here for this enum needs to be stable
|
||||
// long-term as it will be persisted to the database.
|
||||
|
||||
// SingleFunderBit represents a channel wherein one party solely funds
|
||||
// the entire capacity of the channel.
|
||||
SingleFunderBit ChannelType = 0
|
||||
|
||||
// DualFunderBit represents a channel wherein both parties contribute
|
||||
// funds towards the total capacity of the channel. The channel may be
|
||||
// funded symmetrically or asymmetrically.
|
||||
DualFunderBit ChannelType = 1 << 0
|
||||
|
||||
// SingleFunderTweaklessBit is similar to the basic SingleFunder channel
|
||||
// type, but it omits the tweak for one's key in the commitment
|
||||
// transaction of the remote party.
|
||||
SingleFunderTweaklessBit ChannelType = 1 << 1
|
||||
|
||||
// NoFundingTxBit denotes if we have the funding transaction locally on
|
||||
// disk. This bit may be on if the funding transaction was crafted by a
|
||||
// wallet external to the primary daemon.
|
||||
NoFundingTxBit ChannelType = 1 << 2
|
||||
|
||||
// AnchorOutputsBit indicates that the channel makes use of anchor
|
||||
// outputs to bump the commitment transaction's effective feerate. This
|
||||
// channel type also uses a delayed to_remote output script.
|
||||
AnchorOutputsBit ChannelType = 1 << 3
|
||||
|
||||
// FrozenBit indicates that the channel is a frozen channel, meaning
|
||||
// that only the responder can decide to cooperatively close the
|
||||
// channel.
|
||||
FrozenBit ChannelType = 1 << 4
|
||||
|
||||
// ZeroHtlcTxFeeBit indicates that the channel should use zero-fee
|
||||
// second-level HTLC transactions.
|
||||
ZeroHtlcTxFeeBit ChannelType = 1 << 5
|
||||
|
||||
// LeaseExpirationBit indicates that the channel has been leased for a
|
||||
// period of time, constraining every output that pays to the channel
|
||||
// initiator with an additional CLTV of the lease maturity.
|
||||
LeaseExpirationBit ChannelType = 1 << 6
|
||||
)
|
||||
|
||||
// IsSingleFunder returns true if the channel type if one of the known single
|
||||
// funder variants.
|
||||
func (c ChannelType) IsSingleFunder() bool {
|
||||
return c&DualFunderBit == 0
|
||||
}
|
||||
|
||||
// IsDualFunder returns true if the ChannelType has the DualFunderBit set.
|
||||
func (c ChannelType) IsDualFunder() bool {
|
||||
return c&DualFunderBit == DualFunderBit
|
||||
}
|
||||
|
||||
// IsTweakless returns true if the target channel uses a commitment that
|
||||
// doesn't tweak the key for the remote party.
|
||||
func (c ChannelType) IsTweakless() bool {
|
||||
return c&SingleFunderTweaklessBit == SingleFunderTweaklessBit
|
||||
}
|
||||
|
||||
// HasFundingTx returns true if this channel type is one that has a funding
|
||||
// transaction stored locally.
|
||||
func (c ChannelType) HasFundingTx() bool {
|
||||
return c&NoFundingTxBit == 0
|
||||
}
|
||||
|
||||
// HasAnchors returns true if this channel type has anchor outputs on its
|
||||
// commitment.
|
||||
func (c ChannelType) HasAnchors() bool {
|
||||
return c&AnchorOutputsBit == AnchorOutputsBit
|
||||
}
|
||||
|
||||
// ZeroHtlcTxFee returns true if this channel type uses second-level HTLC
|
||||
// transactions signed with zero-fee.
|
||||
func (c ChannelType) ZeroHtlcTxFee() bool {
|
||||
return c&ZeroHtlcTxFeeBit == ZeroHtlcTxFeeBit
|
||||
}
|
||||
|
||||
// IsFrozen returns true if the channel is considered to be "frozen". A frozen
|
||||
// channel means that only the responder can initiate a cooperative channel
|
||||
// closure.
|
||||
func (c ChannelType) IsFrozen() bool {
|
||||
return c&FrozenBit == FrozenBit
|
||||
}
|
||||
|
||||
// HasLeaseExpiration returns true if the channel originated from a lease.
|
||||
func (c ChannelType) HasLeaseExpiration() bool {
|
||||
return c&LeaseExpirationBit == LeaseExpirationBit
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
var (
|
||||
// ChanStatusDefault is the normal state of an open channel.
|
||||
ChanStatusDefault ChannelStatus
|
||||
|
||||
// ChanStatusBorked indicates that the channel has entered an
|
||||
// irreconcilable state, triggered by a state desynchronization or
|
||||
// channel breach. Channels in this state should never be added to the
|
||||
// htlc switch.
|
||||
ChanStatusBorked ChannelStatus = 1
|
||||
|
||||
// ChanStatusCommitBroadcasted indicates that a commitment for this
|
||||
// channel has been broadcasted.
|
||||
ChanStatusCommitBroadcasted ChannelStatus = 1 << 1
|
||||
|
||||
// ChanStatusLocalDataLoss indicates that we have lost channel state
|
||||
// for this channel, and broadcasting our latest commitment might be
|
||||
// considered a breach.
|
||||
//
|
||||
// TODO(halseh): actually enforce that we are not force closing such a
|
||||
// channel.
|
||||
ChanStatusLocalDataLoss ChannelStatus = 1 << 2
|
||||
|
||||
// ChanStatusRestored is a status flag that signals that the channel
|
||||
// has been restored, and doesn't have all the fields a typical channel
|
||||
// will have.
|
||||
ChanStatusRestored ChannelStatus = 1 << 3
|
||||
|
||||
// ChanStatusCoopBroadcasted indicates that a cooperative close for
|
||||
// this channel has been broadcasted. Older cooperatively closed
|
||||
// channels will only have this status set. Newer ones will also have
|
||||
// close initiator information stored using the local/remote initiator
|
||||
// status. This status is set in conjunction with the initiator status
|
||||
// so that we do not need to check multiple channel statues for
|
||||
// cooperative closes.
|
||||
ChanStatusCoopBroadcasted ChannelStatus = 1 << 4
|
||||
|
||||
// ChanStatusLocalCloseInitiator indicates that we initiated closing
|
||||
// the channel.
|
||||
ChanStatusLocalCloseInitiator ChannelStatus = 1 << 5
|
||||
|
||||
// ChanStatusRemoteCloseInitiator indicates that the remote node
|
||||
// initiated closing the channel.
|
||||
ChanStatusRemoteCloseInitiator ChannelStatus = 1 << 6
|
||||
)
|
||||
|
||||
// chanStatusStrings maps a ChannelStatus to a human friendly string that
|
||||
// describes that status.
|
||||
var chanStatusStrings = map[ChannelStatus]string{
|
||||
ChanStatusDefault: "ChanStatusDefault",
|
||||
ChanStatusBorked: "ChanStatusBorked",
|
||||
ChanStatusCommitBroadcasted: "ChanStatusCommitBroadcasted",
|
||||
ChanStatusLocalDataLoss: "ChanStatusLocalDataLoss",
|
||||
ChanStatusRestored: "ChanStatusRestored",
|
||||
ChanStatusCoopBroadcasted: "ChanStatusCoopBroadcasted",
|
||||
ChanStatusLocalCloseInitiator: "ChanStatusLocalCloseInitiator",
|
||||
ChanStatusRemoteCloseInitiator: "ChanStatusRemoteCloseInitiator",
|
||||
}
|
||||
|
||||
// orderedChanStatusFlags is an in-order list of all that channel status flags.
|
||||
var orderedChanStatusFlags = []ChannelStatus{
|
||||
ChanStatusBorked,
|
||||
ChanStatusCommitBroadcasted,
|
||||
ChanStatusLocalDataLoss,
|
||||
ChanStatusRestored,
|
||||
ChanStatusCoopBroadcasted,
|
||||
ChanStatusLocalCloseInitiator,
|
||||
ChanStatusRemoteCloseInitiator,
|
||||
}
|
||||
|
||||
// String returns a human-readable representation of the ChannelStatus.
|
||||
func (c ChannelStatus) String() string {
|
||||
// If no flags are set, then this is the default case.
|
||||
if c == ChanStatusDefault {
|
||||
return chanStatusStrings[ChanStatusDefault]
|
||||
}
|
||||
|
||||
// Add individual bit flags.
|
||||
statusStr := ""
|
||||
for _, flag := range orderedChanStatusFlags {
|
||||
if c&flag == flag {
|
||||
statusStr += chanStatusStrings[flag] + "|"
|
||||
c -= flag
|
||||
}
|
||||
}
|
||||
|
||||
// Remove anything to the right of the final bar, including it as well.
|
||||
statusStr = strings.TrimRight(statusStr, "|")
|
||||
|
||||
// Add any remaining flags which aren't accounted for as hex.
|
||||
if c != 0 {
|
||||
statusStr += "|0x" + strconv.FormatUint(uint64(c), 16)
|
||||
}
|
||||
|
||||
// If this was purely an unknown flag, then remove the extra bar at the
|
||||
// start of the string.
|
||||
statusStr = strings.TrimLeft(statusStr, "|")
|
||||
|
||||
return statusStr
|
||||
}
|
||||
|
||||
// OpenChannel embeds a mig.OpenChannel with the extra update-to-date fields.
|
||||
//
|
||||
// NOTE: doesn't have the Packager field as it's not used in current migration.
|
||||
type OpenChannel struct {
|
||||
mig.OpenChannel
|
||||
|
||||
// ChanType denotes which type of channel this is.
|
||||
ChanType ChannelType
|
||||
|
||||
// chanStatus is the current status of this channel. If it is not in
|
||||
// the state Default, it should not be used for forwarding payments.
|
||||
chanStatus ChannelStatus
|
||||
|
||||
// InitialLocalBalance is the balance we have during the channel
|
||||
// opening. When we are not the initiator, this value represents the
|
||||
// push amount.
|
||||
InitialLocalBalance lnwire.MilliSatoshi
|
||||
|
||||
// InitialRemoteBalance is the balance they have during the channel
|
||||
// opening.
|
||||
InitialRemoteBalance lnwire.MilliSatoshi
|
||||
|
||||
// LocalShutdownScript is set to a pre-set script if the channel was
|
||||
// opened by the local node with option_upfront_shutdown_script set. If
|
||||
// the option was not set, the field is empty.
|
||||
LocalShutdownScript lnwire.DeliveryAddress
|
||||
|
||||
// RemoteShutdownScript is set to a pre-set script if the channel was
|
||||
// opened by the remote node with option_upfront_shutdown_script set.
|
||||
// If the option was not set, the field is empty.
|
||||
RemoteShutdownScript lnwire.DeliveryAddress
|
||||
|
||||
// ThawHeight is the height when a frozen channel once again becomes a
|
||||
// normal channel. If this is zero, then there're no restrictions on
|
||||
// this channel. If the value is lower than 500,000, then it's
|
||||
// interpreted as a relative height, or an absolute height otherwise.
|
||||
ThawHeight uint32
|
||||
|
||||
// LastWasRevoke is a boolean that determines if the last update we
|
||||
// sent was a revocation (true) or a commitment signature (false).
|
||||
LastWasRevoke bool
|
||||
|
||||
// RevocationKeyLocator stores the KeyLocator information that we will
|
||||
// need to derive the shachain root for this channel. This allows us to
|
||||
// have private key isolation from lnd.
|
||||
RevocationKeyLocator keychain.KeyLocator
|
||||
}
|
||||
|
||||
func (c *OpenChannel) hasChanStatus(status ChannelStatus) bool {
|
||||
// Special case ChanStatusDefualt since it isn't actually flag, but a
|
||||
// particular combination (or lack-there-of) of flags.
|
||||
if status == ChanStatusDefault {
|
||||
return c.chanStatus == ChanStatusDefault
|
||||
}
|
||||
|
||||
return c.chanStatus&status == status
|
||||
}
|
||||
|
||||
// FundingTxPresent returns true if expect the funding transcation to be found
|
||||
// on disk or already populated within the passed open channel struct.
|
||||
func (c *OpenChannel) FundingTxPresent() bool {
|
||||
chanType := c.ChanType
|
||||
|
||||
return chanType.IsSingleFunder() && chanType.HasFundingTx() &&
|
||||
c.IsInitiator &&
|
||||
!c.hasChanStatus(ChanStatusRestored)
|
||||
}
|
||||
|
||||
// fetchChanInfo deserializes the channel info based on the legacy boolean.
|
||||
func fetchChanInfo(chanBucket kvdb.RBucket, c *OpenChannel, legacy bool) error {
|
||||
infoBytes := chanBucket.Get(chanInfoKey)
|
||||
if infoBytes == nil {
|
||||
return ErrNoChanInfoFound
|
||||
}
|
||||
r := bytes.NewReader(infoBytes)
|
||||
|
||||
var (
|
||||
chanType mig.ChannelType
|
||||
chanStatus mig.ChannelStatus
|
||||
)
|
||||
|
||||
if err := mig.ReadElements(r,
|
||||
&chanType, &c.ChainHash, &c.FundingOutpoint,
|
||||
&c.ShortChannelID, &c.IsPending, &c.IsInitiator,
|
||||
&chanStatus, &c.FundingBroadcastHeight,
|
||||
&c.NumConfsRequired, &c.ChannelFlags,
|
||||
&c.IdentityPub, &c.Capacity, &c.TotalMSatSent,
|
||||
&c.TotalMSatReceived,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.ChanType = ChannelType(chanType)
|
||||
c.chanStatus = ChannelStatus(chanStatus)
|
||||
|
||||
// If this is not the legacy format, we need to read the extra two new
|
||||
// fields.
|
||||
if !legacy {
|
||||
if err := mig.ReadElements(r,
|
||||
&c.InitialLocalBalance, &c.InitialRemoteBalance,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// For single funder channels that we initiated and have the funding
|
||||
// transaction to, read the funding txn.
|
||||
if c.FundingTxPresent() {
|
||||
if err := mig.ReadElement(r, &c.FundingTxn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := mig.ReadChanConfig(r, &c.LocalChanCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mig.ReadChanConfig(r, &c.RemoteChanCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Retrieve the boolean stored under lastWasRevokeKey.
|
||||
lastWasRevokeBytes := chanBucket.Get(lastWasRevokeKey)
|
||||
if lastWasRevokeBytes == nil {
|
||||
// If nothing has been stored under this key, we store false in
|
||||
// the OpenChannel struct.
|
||||
c.LastWasRevoke = false
|
||||
} else {
|
||||
// Otherwise, read the value into the LastWasRevoke field.
|
||||
revokeReader := bytes.NewReader(lastWasRevokeBytes)
|
||||
err := mig.ReadElements(revokeReader, &c.LastWasRevoke)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
keyLocRecord := MakeKeyLocRecord(keyLocType, &c.RevocationKeyLocator)
|
||||
tlvStream, err := tlv.NewStream(keyLocRecord)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tlvStream.Decode(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Finally, read the optional shutdown scripts.
|
||||
if err := GetOptionalUpfrontShutdownScript(
|
||||
chanBucket, localUpfrontShutdownKey, &c.LocalShutdownScript,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return GetOptionalUpfrontShutdownScript(
|
||||
chanBucket, remoteUpfrontShutdownKey, &c.RemoteShutdownScript,
|
||||
)
|
||||
}
|
||||
|
||||
// fetchChanInfo serializes the channel info based on the legacy boolean and
|
||||
// saves it to disk.
|
||||
func putChanInfo(chanBucket kvdb.RwBucket, c *OpenChannel, legacy bool) error {
|
||||
var w bytes.Buffer
|
||||
if err := mig.WriteElements(&w,
|
||||
mig.ChannelType(c.ChanType), c.ChainHash, c.FundingOutpoint,
|
||||
c.ShortChannelID, c.IsPending, c.IsInitiator,
|
||||
mig.ChannelStatus(c.chanStatus), c.FundingBroadcastHeight,
|
||||
c.NumConfsRequired, c.ChannelFlags,
|
||||
c.IdentityPub, c.Capacity, c.TotalMSatSent,
|
||||
c.TotalMSatReceived,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If this is not legacy format, we need to write the extra two fields.
|
||||
if !legacy {
|
||||
if err := mig.WriteElements(&w,
|
||||
c.InitialLocalBalance, c.InitialRemoteBalance,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// For single funder channels that we initiated, and we have the
|
||||
// funding transaction, then write the funding txn.
|
||||
if c.FundingTxPresent() {
|
||||
if err := mig.WriteElement(&w, c.FundingTxn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := mig.WriteChanConfig(&w, &c.LocalChanCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mig.WriteChanConfig(&w, &c.RemoteChanCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the RevocationKeyLocator as the first entry in a tlv stream.
|
||||
keyLocRecord := MakeKeyLocRecord(
|
||||
keyLocType, &c.RevocationKeyLocator,
|
||||
)
|
||||
|
||||
tlvStream, err := tlv.NewStream(keyLocRecord)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tlvStream.Encode(&w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Finally, add optional shutdown scripts for the local and remote peer
|
||||
// if they are present.
|
||||
if err := PutOptionalUpfrontShutdownScript(
|
||||
chanBucket, localUpfrontShutdownKey, c.LocalShutdownScript,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return PutOptionalUpfrontShutdownScript(
|
||||
chanBucket, remoteUpfrontShutdownKey, c.RemoteShutdownScript,
|
||||
)
|
||||
}
|
||||
|
||||
// EKeyLocator is an encoder for keychain.KeyLocator.
|
||||
func EKeyLocator(w io.Writer, val interface{}, buf *[8]byte) error {
|
||||
if v, ok := val.(*keychain.KeyLocator); ok {
|
||||
err := tlv.EUint32T(w, uint32(v.Family), buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tlv.EUint32T(w, v.Index, buf)
|
||||
}
|
||||
return tlv.NewTypeForEncodingErr(val, "keychain.KeyLocator")
|
||||
}
|
||||
|
||||
// DKeyLocator is a decoder for keychain.KeyLocator.
|
||||
func DKeyLocator(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
|
||||
if v, ok := val.(*keychain.KeyLocator); ok {
|
||||
var family uint32
|
||||
err := tlv.DUint32(r, &family, buf, 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Family = keychain.KeyFamily(family)
|
||||
|
||||
return tlv.DUint32(r, &v.Index, buf, 4)
|
||||
}
|
||||
return tlv.NewTypeForDecodingErr(val, "keychain.KeyLocator", l, 8)
|
||||
}
|
||||
|
||||
// MakeKeyLocRecord creates a Record out of a KeyLocator using the passed
|
||||
// Type and the EKeyLocator and DKeyLocator functions. The size will always be
|
||||
// 8 as KeyFamily is uint32 and the Index is uint32.
|
||||
func MakeKeyLocRecord(typ tlv.Type, keyLoc *keychain.KeyLocator) tlv.Record {
|
||||
return tlv.MakeStaticRecord(typ, keyLoc, 8, EKeyLocator, DKeyLocator)
|
||||
}
|
||||
|
||||
// PutOptionalUpfrontShutdownScript adds a shutdown script under the key
|
||||
// provided if it has a non-zero length.
|
||||
func PutOptionalUpfrontShutdownScript(chanBucket kvdb.RwBucket, key []byte,
|
||||
script []byte) error {
|
||||
// If the script is empty, we do not need to add anything.
|
||||
if len(script) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var w bytes.Buffer
|
||||
if err := mig.WriteElement(&w, script); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return chanBucket.Put(key, w.Bytes())
|
||||
}
|
||||
|
||||
// GetOptionalUpfrontShutdownScript reads the shutdown script stored under the
|
||||
// key provided if it is present. Upfront shutdown scripts are optional, so the
|
||||
// function returns with no error if the key is not present.
|
||||
func GetOptionalUpfrontShutdownScript(chanBucket kvdb.RBucket, key []byte,
|
||||
script *lnwire.DeliveryAddress) error {
|
||||
|
||||
// Return early if the bucket does not exit, a shutdown script was not
|
||||
// set.
|
||||
bs := chanBucket.Get(key)
|
||||
if bs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var tempScript []byte
|
||||
r := bytes.NewReader(bs)
|
||||
if err := mig.ReadElement(r, &tempScript); err != nil {
|
||||
return err
|
||||
}
|
||||
*script = tempScript
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchChanCommitments fetches both the local and remote commitments. This
|
||||
// function is exported so it can be used by later migrations.
|
||||
func FetchChanCommitments(chanBucket kvdb.RBucket, channel *OpenChannel) error {
|
||||
var err error
|
||||
|
||||
// If this is a restored channel, then we don't have any commitments to
|
||||
// read.
|
||||
if channel.hasChanStatus(ChanStatusRestored) {
|
||||
return nil
|
||||
}
|
||||
|
||||
channel.LocalCommitment, err = FetchChanCommitment(chanBucket, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
channel.RemoteCommitment, err = FetchChanCommitment(chanBucket, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchChanCommitment fetches a channel commitment. This function is exported
|
||||
// so it can be used by later migrations.
|
||||
func FetchChanCommitment(chanBucket kvdb.RBucket,
|
||||
local bool) (mig.ChannelCommitment, error) {
|
||||
|
||||
commitKey := chanCommitmentKey
|
||||
if local {
|
||||
commitKey = append(commitKey, byte(0x00))
|
||||
} else {
|
||||
commitKey = append(commitKey, byte(0x01))
|
||||
}
|
||||
|
||||
commitBytes := chanBucket.Get(commitKey)
|
||||
if commitBytes == nil {
|
||||
return mig.ChannelCommitment{}, ErrNoCommitmentsFound
|
||||
}
|
||||
|
||||
r := bytes.NewReader(commitBytes)
|
||||
return mig.DeserializeChanCommit(r)
|
||||
}
|
||||
|
||||
func PutChanCommitment(chanBucket kvdb.RwBucket, c *mig.ChannelCommitment,
|
||||
local bool) error {
|
||||
|
||||
commitKey := chanCommitmentKey
|
||||
if local {
|
||||
commitKey = append(commitKey, byte(0x00))
|
||||
} else {
|
||||
commitKey = append(commitKey, byte(0x01))
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := mig.SerializeChanCommit(&b, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return chanBucket.Put(commitKey, b.Bytes())
|
||||
}
|
||||
|
||||
func PutChanCommitments(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
|
||||
// If this is a restored channel, then we don't have any commitments to
|
||||
// write.
|
||||
if channel.hasChanStatus(ChanStatusRestored) {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := PutChanCommitment(
|
||||
chanBucket, &channel.LocalCommitment, true,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return PutChanCommitment(
|
||||
chanBucket, &channel.RemoteCommitment, false,
|
||||
)
|
||||
}
|
||||
|
||||
// balancesAtHeight returns the local and remote balances on our commitment
|
||||
// transactions as of a given height. This function is not exported as it's
|
||||
// deprecated.
|
||||
//
|
||||
// NOTE: these are our balances *after* subtracting the commitment fee and
|
||||
// anchor outputs.
|
||||
func (c *OpenChannel) balancesAtHeight(chanBucket kvdb.RBucket,
|
||||
height uint64) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
|
||||
|
||||
// If our current commit is as the desired height, we can return our
|
||||
// current balances.
|
||||
if c.LocalCommitment.CommitHeight == height {
|
||||
return c.LocalCommitment.LocalBalance,
|
||||
c.LocalCommitment.RemoteBalance, nil
|
||||
}
|
||||
|
||||
// If our current remote commit is at the desired height, we can return
|
||||
// the current balances.
|
||||
if c.RemoteCommitment.CommitHeight == height {
|
||||
return c.RemoteCommitment.LocalBalance,
|
||||
c.RemoteCommitment.RemoteBalance, nil
|
||||
}
|
||||
|
||||
// If we are not currently on the height requested, we need to look up
|
||||
// the previous height to obtain our balances at the given height.
|
||||
commit, err := c.FindPreviousStateLegacy(chanBucket, height)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
return commit.LocalBalance, commit.RemoteBalance, nil
|
||||
}
|
||||
|
||||
// FindPreviousStateLegacy scans through the append-only log in an attempt to
|
||||
// recover the previous channel state indicated by the update number. This
|
||||
// method is intended to be used for obtaining the relevant data needed to
|
||||
// claim all funds rightfully spendable in the case of an on-chain broadcast of
|
||||
// the commitment transaction.
|
||||
func (c *OpenChannel) FindPreviousStateLegacy(chanBucket kvdb.RBucket,
|
||||
updateNum uint64) (*mig.ChannelCommitment, error) {
|
||||
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
logBucket := chanBucket.NestedReadBucket(revocationLogBucketLegacy)
|
||||
if logBucket == nil {
|
||||
return nil, ErrNoPastDeltas
|
||||
}
|
||||
|
||||
commit, err := fetchChannelLogEntry(logBucket, updateNum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &commit, nil
|
||||
}
|
||||
|
||||
func fetchChannelLogEntry(log kvdb.RBucket,
|
||||
updateNum uint64) (mig.ChannelCommitment, error) {
|
||||
|
||||
logEntrykey := mig24.MakeLogKey(updateNum)
|
||||
commitBytes := log.Get(logEntrykey[:])
|
||||
if commitBytes == nil {
|
||||
return mig.ChannelCommitment{}, ErrLogEntryNotFound
|
||||
}
|
||||
|
||||
commitReader := bytes.NewReader(commitBytes)
|
||||
return mig.DeserializeChanCommit(commitReader)
|
||||
}
|
14
channeldb/migration25/log.go
Normal file
14
channeldb/migration25/log.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package migration25
|
||||
|
||||
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 specified Logger to output package logging info.
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
log = logger
|
||||
}
|
211
channeldb/migration25/migration.go
Normal file
211
channeldb/migration25/migration.go
Normal file
|
@ -0,0 +1,211 @@
|
|||
package migration25
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
mig "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
)
|
||||
|
||||
var (
|
||||
// openChanBucket stores all the currently open channels. This bucket
|
||||
// has a second, nested bucket which is keyed by a node's ID. Within
|
||||
// that node ID bucket, all attributes required to track, update, and
|
||||
// close a channel are stored.
|
||||
openChannelBucket = []byte("open-chan-bucket")
|
||||
|
||||
// chanInfoKey can be accessed within the bucket for a channel
|
||||
// (identified by its chanPoint). This key stores all the static
|
||||
// information for a channel which is decided at the end of the
|
||||
// funding flow.
|
||||
chanInfoKey = []byte("chan-info-key")
|
||||
|
||||
// ErrNoChanDBExists is returned when a channel bucket hasn't been
|
||||
// created.
|
||||
ErrNoChanDBExists = fmt.Errorf("channel db has not yet been created")
|
||||
|
||||
// ErrNoActiveChannels is returned when there is no active (open)
|
||||
// channels within the database.
|
||||
ErrNoActiveChannels = fmt.Errorf("no active channels exist")
|
||||
|
||||
// ErrChannelNotFound is returned when we attempt to locate a channel
|
||||
// for a specific chain, but it is not found.
|
||||
ErrChannelNotFound = fmt.Errorf("channel not found")
|
||||
)
|
||||
|
||||
// MigrateInitialBalances patches the two new fields, InitialLocalBalance and
|
||||
// InitialRemoteBalance, for all the open channels. It does so by reading the
|
||||
// revocation log at height 0 to learn the initial balances and then updates
|
||||
// the channel's info.
|
||||
// The channel info is saved in the nested bucket which is accessible via
|
||||
// nodePub:chainHash:chanPoint. If any of the sub-buckets turns out to be nil,
|
||||
// we will log the error and continue to process the rest.
|
||||
func MigrateInitialBalances(tx kvdb.RwTx) error {
|
||||
log.Infof("Migrating initial local and remote balances...")
|
||||
|
||||
openChanBucket := tx.ReadWriteBucket(openChannelBucket)
|
||||
|
||||
// If no bucket is found, we can exit early.
|
||||
if openChanBucket == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read a list of open channels.
|
||||
channels, err := findOpenChannels(openChanBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Migrate the balances.
|
||||
for _, c := range channels {
|
||||
if err := migrateBalances(tx, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// findOpenChannels finds all open channels.
|
||||
func findOpenChannels(openChanBucket kvdb.RBucket) ([]*OpenChannel, error) {
|
||||
channels := []*OpenChannel{}
|
||||
|
||||
// readChannel is a helper closure that reads the channel info from the
|
||||
// channel bucket.
|
||||
readChannel := func(chainBucket kvdb.RBucket, cp []byte) error {
|
||||
c := &OpenChannel{}
|
||||
|
||||
// Read the sub-bucket level 3.
|
||||
chanBucket := chainBucket.NestedReadBucket(
|
||||
cp,
|
||||
)
|
||||
if chanBucket == nil {
|
||||
log.Errorf("unable to read bucket for chanPoint=%x", cp)
|
||||
return nil
|
||||
}
|
||||
// Get the old channel info.
|
||||
if err := fetchChanInfo(chanBucket, c, true); err != nil {
|
||||
return fmt.Errorf("unable to fetch chan info: %v", err)
|
||||
}
|
||||
|
||||
// Fetch the channel commitments, which are useful for freshly
|
||||
// open channels as they don't have any revocation logs and
|
||||
// their current commitments reflect the initial balances.
|
||||
if err := FetchChanCommitments(chanBucket, c); err != nil {
|
||||
return fmt.Errorf("unable to fetch chan commits: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
channels = append(channels, c)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Iterate the root bucket.
|
||||
err := openChanBucket.ForEach(func(nodePub, v []byte) error {
|
||||
// Ensure that this is a key the same size as a pubkey, and
|
||||
// also that it leads directly to a bucket.
|
||||
if len(nodePub) != 33 || v != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read the sub-bucket level 1.
|
||||
nodeChanBucket := openChanBucket.NestedReadBucket(nodePub)
|
||||
if nodeChanBucket == nil {
|
||||
log.Errorf("no bucket for node %x", nodePub)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Iterate the bucket.
|
||||
return nodeChanBucket.ForEach(func(chainHash, _ []byte) error {
|
||||
// Read the sub-bucket level 2.
|
||||
chainBucket := nodeChanBucket.NestedReadBucket(
|
||||
chainHash,
|
||||
)
|
||||
if chainBucket == nil {
|
||||
log.Errorf("unable to read bucket for chain=%x",
|
||||
chainHash)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Iterate the bucket.
|
||||
return chainBucket.ForEach(func(cp, _ []byte) error {
|
||||
return readChannel(chainBucket, cp)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
// migrateBalances queries the revocation log at height 0 to find the initial
|
||||
// balances and save them to the channel info.
|
||||
func migrateBalances(tx kvdb.RwTx, c *OpenChannel) error {
|
||||
// Get the bucket.
|
||||
chanBucket, err := fetchChanBucket(tx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Get the initial balances.
|
||||
localAmt, remoteAmt, err := c.balancesAtHeight(chanBucket, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get initial balances: %v", err)
|
||||
}
|
||||
|
||||
c.InitialLocalBalance = localAmt
|
||||
c.InitialRemoteBalance = remoteAmt
|
||||
|
||||
// Update the channel info.
|
||||
if err := putChanInfo(chanBucket, c, false); err != nil {
|
||||
return fmt.Errorf("unable to put chan info: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetchChanBucket is a helper function that returns the bucket where a
|
||||
// channel's data resides in given: the public key for the node, the outpoint,
|
||||
// and the chainhash that the channel resides on.
|
||||
func fetchChanBucket(tx kvdb.RwTx, c *OpenChannel) (kvdb.RwBucket, error) {
|
||||
// First fetch the top level bucket which stores all data related to
|
||||
// current, active channels.
|
||||
openChanBucket := tx.ReadWriteBucket(openChannelBucket)
|
||||
if openChanBucket == nil {
|
||||
return nil, ErrNoChanDBExists
|
||||
}
|
||||
|
||||
// Within this top level bucket, fetch the bucket dedicated to storing
|
||||
// open channel data specific to the remote node.
|
||||
nodePub := c.IdentityPub.SerializeCompressed()
|
||||
nodeChanBucket := openChanBucket.NestedReadWriteBucket(nodePub)
|
||||
if nodeChanBucket == nil {
|
||||
return nil, ErrNoActiveChannels
|
||||
}
|
||||
|
||||
// We'll then recurse down an additional layer in order to fetch the
|
||||
// bucket for this particular chain.
|
||||
chainBucket := nodeChanBucket.NestedReadWriteBucket(c.ChainHash[:])
|
||||
if chainBucket == nil {
|
||||
return nil, ErrNoActiveChannels
|
||||
}
|
||||
|
||||
// With the bucket for the node and chain fetched, we can now go down
|
||||
// another level, for this channel itself.
|
||||
var chanPointBuf bytes.Buffer
|
||||
err := mig.WriteOutpoint(&chanPointBuf, &c.FundingOutpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chanBucket := chainBucket.NestedReadWriteBucket(chanPointBuf.Bytes())
|
||||
if chanBucket == nil {
|
||||
return nil, ErrChannelNotFound
|
||||
}
|
||||
|
||||
return chanBucket, nil
|
||||
}
|
Loading…
Add table
Reference in a new issue